构造方法
Thread类提供了丰富的构造方法,具体如下:
Thread()
Thread(Runnable target)
Thread(Runnable target, String name)
Thread(String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
Thread(ThreadGroup group, String name)
线程的命名
下面几个构造方法中都有一个name参数来指定线程的名字:
Thread(Runnable target, String name)
Thread(String name)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
Thread(ThreadGroup group, String name)
那么,如果没有为线程指定名字,线程的名字是怎么命名的呢?
查看Thread类默认构造方法的源码:
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
从源码可得知,如果没有为线程指定名字,线程的名字以Thread-
开头,后面加一个从0开始自增的数字。
在线程启动之前还可以使用setName()对线程的名字进行修改。
线程的父子关系
Thread所有的构造方法都会调用一个init()方法,其源码片段如下:
...
Thread parent = currentThread(); // 当前线程为父线程
...
从源码可以得出以下结论:
- 每个线程都有一个父线程。
- 子线程由父线程创建。
- 如果没有指定父线程,父线程一般都是main线程。
Thread与ThreadGroup
Thread的init()方法,有这么一个源码片段:
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
... ...
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
在默认构造方法中,子线程会与父线程有一样的ThreadGroup,一样的优先级,一样的daemon。
main线程所在的ThreadGroup的名称为main。
Thread与虚拟机栈
Thread的构造方法中有个参数stackSize,这个参数可以指定线程对应的虚拟机栈大小,与在java程序启动时,指定-Xss参数效果一致。
下面一段代码演示Thread构造方法中stackSize的作用:
package com.morris.concurrent.thread.api;
public class StackSizeDemo {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
recurse(0);
}
private void recurse(int i) {
System.out.println(i);
recurse(i + 1);
}
};
// Thread t = new Thread(null, r, "Thread-StackSizeDemo", 10 * 1024 * 1024); // 18370
// Thread t = new Thread(null, r, "Thread-StackSizeDemo", 10 * 1000 * 1000); // 186899
// -Xss1m->18373
// -Xss10m->186898
Thread t = new Thread(null, r, "Thread-StackSizeDemo");
t.start();
}
}
从运行结果可以发现:
- stackSize与-Xss效果一致。
- stackSize越大,递归调用的深度就越大,栈中可容纳的栈帧就越多。栈的容量就变大了,可创建的最大线程数量就会变少。
- 当栈的空间不足时,会发生StackOverflowError异常。
成员方法
setPriority()
setPriority设置线程的优先级,级别为1-10,默认为5,数字越大,优先级越高,设置了也不一定有效果。
package com.morris.concurrent.thread.api;
import java.util.concurrent.TimeUnit;
public class PriorityDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T1");
}
});
Thread t2 = new Thread(() -> {
while (true) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T2");
}
});
t1.setPriority(10);
t2.setPriority(1);
t1.start();
t2.start();
}
}
join()
A线程调用B线程的join方法,那么A线程会阻塞等到B线程执行完才会继续往下走。
package com.morris.concurrent.thread.api;
import java.util.concurrent.TimeUnit;
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
try {
TimeUnit.SECONDS.sleep(5);
System.out.println("t thread is exit.");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
t.join();
System.out.println("main thread is exit.");
}
}
查看join方法的源码,发现join底层是使用的wait方法:
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
守护线程
setDaemon()可以设置线程是否为守护线程,默认为false。
package com.morris.concurrent.thread.api;
import java.util.concurrent.TimeUnit;
/**
* 守护线程的使用
*/
public class DaemonDemo {
public static void main(String[] args) {
Thread t = new Thread(()->{
try {
System.out.println("daemon thread run begin...");
TimeUnit.SECONDS.sleep(5);
System.out.println("daemon thread run end.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("daemon thread finally block.");
}
});
t.setDaemon(true);
t.start();
TimeUnit.SECONDS.sleep(3);
}
}
守护线程会随着运行它的线程一起死亡,如果守护线程没执行完的话,finally代码块不会执行,如果要释放资源的话,这个线程不适宜设置为守护线程。
给程序注入钩子程序
可以给应用程序注入钩子程序,在应用关闭时执行一些逻辑。
package com.morris.concurrent.thread.api;
import java.util.concurrent.TimeUnit;
/**
* 给应用程序注入钩子程序
* 在应用关闭时执行一些逻辑
*/
public class HookDemo {
public static void main(String[] args) throws InterruptedException {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("i am a hook thread, begin task");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i am a hook thread, end task");
}));
for (int i = 0; i < 100; i++) {
System.out.println("I am working...");
TimeUnit.SECONDS.sleep(1);
}
}
}
在Linux下kill应用程序时候,钩子程序会执行(kill -9强制杀死的不会执行)。
ThreadGroup
可以将线程放入一个ThreadGroup,方便管理,如果不显示的指定ThreadGroup,默认与父线程在同一个ThreadGroup中。
package com.morris.concurrent.thread.api;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
/**
* 演示ThreadGroup的使用
*/
public class ThreadGroupDemo {
public static void main(String[] args) {
// parent ThreadGroup is main
ThreadGroup tg1 = new ThreadGroup("tg1");
new Thread(tg1, () -> {
System.out.println(Thread.currentThread().getName() + "->" + Thread.currentThread().getThreadGroup());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
// parent ThreadGroup is tg1
ThreadGroup tg2 = new ThreadGroup(tg1, "tg2");
new Thread(tg2, () -> {
System.out.println(Thread.currentThread().getName() + "->" + Thread.currentThread().getThreadGroup());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2").start();
ThreadGroup mainThreadGroup = Thread.currentThread().getThreadGroup();
Thread[] threads = new Thread[mainThreadGroup.activeCount()];
//mainThreadGroup.enumerate(threads, true); // 递归subgroup
mainThreadGroup.enumerate(threads, false); // 不递归
Arrays.asList(threads).forEach(System.out::println);
// 中断所有线程,包括subgroup
mainThreadGroup.interrupt();
}
}
静态方法
sleep()
sleep()方法会使当期线程进入指定毫秒数的休眠状态,注意此方法不会释放锁的所有权。
package com.morris.concurrent.thread.api;
import java.util.concurrent.TimeUnit;
/**
* 演示Thread.sleep()方法
*/
public class SleepDemo {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(1000); // 休眠1s
TimeUnit.SECONDS.sleep(1); // 休眠1s
}
}
可以使用TimeUnit工具类代替sleep(),省去时间单位的换算。
wait()与sleep()的区别:
- wait()是Object类的方法,sleep()是Thread类的方法。
- sleep()不会释放锁,而wait()需要释放锁。
- wait()方法执行前需要持有锁,而sleep()不需要。
- sleep()到时会自动唤醒,而wait()需要notify()/notifyAll()来唤醒(wait(time)方法除外)。
yeild()
yield()会放弃cpu的执行权,使得当前线程进入就绪状态。
package com.morris.concurrent.thread.api;
import java.util.stream.IntStream;
/**
* 演示Thread.yield()方法
*/
public class YieldDemo {
public static void main(String[] args) {
IntStream.rangeClosed(1, 2).mapToObj(YieldDemo::createThread).forEach(Thread::start);
}
public static Thread createThread(int index) {
return new Thread(()->{
if(1 == index) {
Thread.yield(); // 注释这一行可能会出现不同的结果
}
System.out.println(index);
});
}
}