多用线程池少用 Thread 这个类,下面是各个版本的线程池:
Java 中线程的生命周期和线程的状态可以理解为:在其生命周期中,线程会经历各种状态。
类似于下面这张图是最常见的:
使用 new Thread()
创建出来的线程就是 NEW 状态,这个时候线程并没有启动;当调用 start()
方法时,线程状态就会变成 RUNNABLE 状态。但是要注意,RUNNABLE 状态又分为两种,一种是准备运行(I/O 阻塞),另一种是运行。
当 t1 获得锁,t2 会等待 t1 释放锁,这个时候 t2 线程的状态就是 BLOCKED 阻塞状态。
当调用了线程的 Object#wait()
、Thread#join()
或 LockSupport#park()
方法时,就会变成 WAITTING 状态;如果调用的是带有超时参数的就会变成 TIMED_WAITTING 状态。
线程执行结束就是 TERMINATED
状态。
在 Java 中线程一共就分为 6 种状态并且可以通过 state()
方法查看线程状态。
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread run");
}
});
上面是 Java 创建一个线程的例子,下面代码展示了这个线程具体是怎么创建的,Thread
构造方法最终都会调用 init
方法。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
/* 每一个线程必须有一个名字,默认的线程名称是:Thread- 加上线程ID。
线程ID 是存放在 threadInitNumber 变量中,每次创建 Thread 实例时,都会加1。*/
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
/* 假设在线程 A 中,调用了 new Thread 创建了线程 B。
这里获取线程 A 是为了,将线程 A 的一些配置传递给线程 B。*/
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
/* 每一个线程必须有一个线程组,如果没有指定则会先获取安全管理器中的线程组,否则再回去父级(线程A)线程组。 */
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
/* 判断当前线程(线程A) 有没有操作线程组的权限,如果没有就会抛出 SecurityException。
因为 Thread#start() 方法会操作线程组。*/
g.checkAccess();
if (security != null) {
/* 主要用来判断子类有没有重写 getContextClassLoader 和 setContextClassLoader 方法,如果重写了则返回 true。*/
if (isCCLOverridden(getClass())) {
/* 既然重写了那么就判断一下有没有重写权限(enableContextClassLoaderOverride),如果没有则抛出 SecurityException。*/
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
/* 将线程组中的 未启动线程 变量加 1。*/
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
/* 如果重写了就调用子类的 getContextClassLoader() 方法,否则就直接获取父类的 ClassLoader 就可以了。*/
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
/* 我也不清楚为什么要获取这个。*/
this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();
/* 我也不清楚为什么要获取这个。*/
this.target = target;
/* 设置线程优先级。*/
setPriority(priority);
/* 继承父级的 ThreadLocal.ThreadLocalMap。
当然自己的线程数据可以放到 ThreadLocal.ThreadLocalMap threadLocals 中。*/
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
tid = nextThreadID();
}
下面是简化的实例化流程:
ThreadGroup#nUnstartedThreads
变量加 1Thread#stop()
方法已经不推荐使用了,但可以使用 Thread#interrupt()
方法停止一个线程。
但是当调用目标线程的 interrupt() 方法后只会将目标线程打上中断标记,而目标线程并不会立即终止。也就是说,只是一个中断标记,JVM 不会强制要求终止线程。下面就是一个不会立即终止的例子:
public class ThreadWithoutIntterupt {
public static void main(String[] args) {
Thread t = new Thread(new PrintEvenNumbers());
t.start();
// 调用目标线程 t 的 interrupt() 方法。
t.interrupt();
}
}
class PrintEvenNumbers implements Runnable {
@Override
public void run() {
System.out.println("Printing even numbers till 20 :");
for (int i = 2; i <= 20; i = i + 2) {
System.out.println(i);
}
}
}
上面这个例子中,目标线程并没有立即终止,而是正常停止(打印完了所有偶数)。也就是说,终止目标线程的执行,必须要目标线程自己终止,这样才能保证一致性。目标线程可以使用 Thread#interrupted()
方法来判断是否要终止自身。将 PrintEvenNumbers 类改成如下:
class PrintEvenNumbers implements Runnable {
@Override
public void run() {
System.out.println("Printing even numbers till 20 :");
for (int i = 2; i <= 20; i = i + 2) {
System.out.println(i);
if (Thread.interrupted()) {
System.out.println("保证数据一致性,并终止线程。");
return;
}
}
}
}
值得注意的是:当调用 Thread.interrupted()
方法时,会清除中断标记(也就是再次设置为 false),再次调用时该方法会返回 false。但是调用 t1.isInterrupted()
方法则不会清除中断标记。
还有一点就是无论是否先调用了目标线程的 interrupt() 方法,Object#wait(...)
Thread#join(...)
Thread#sleep(...)
都会抛出 InterruptedException
并且会清空 interrupt 状态。如果是 I/O 操作的话则会抛出 ClosedByInterruptException
异常。
class PrintEvenNumbers implements Runnable {
@Override
public void run() {
System.out.println("Printing even numbers till 20 :");
try {
Thread.sleep(10_000);
} catch (InterruptedException e) {
throw new InternalError("Thraed is interrupted");
}
}
}
值得注意的是:如果 interrupt() 方法在 start() 方法之前调用则无效。
在当前线程中调用目标线程的 join(...)
方法时,当前线程进入等待状态,直到目标的线程终止、 join 超时或调用了目标线程的 interrupt 方法,当前线程才会继续执行。
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t2 = new SampleThread(1);
t2.start();
System.out.println("Invoking join");
t2.join();
System.out.println("Returned from join");
System.out.println(t2.isAlive());
}
}
class SampleThread extends Thread {
public int processingCount = 0;
SampleThread(int processingCount) {
this.processingCount = processingCount;
System.out.println("Thread Created");;
}
@Override
public void run() {
System.out.println("Thread " + this.getName() + " started");
while (processingCount > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread " + this.getName() + " interrupted");
}
processingCount--;
}
System.out.println("Thread " + this.getName() + " exiting");;
}
}
下面是一个带有超时时间的 join 代码示例:
Thread t2 = new SampleThread(10);
t2.start();
System.out.println("Invoking join");
t2.join(1000);
System.out.println("Returned from join");
System.out.println(t2.isAlive());
值得注意的是:join(...) 方法满足 Happens-before 关系。
当我们调用 wait() 方法时,会使当前线程进行等待,直到其他某个线程在同一对象上调用 notify()、notifyAll() 或 interrupt() 方法为止。
值得注意的是:notify() 和 wait() 方法只能从同步方法或同步代码块中被调用。另外,wait 方法也可以设置超时时间,当设置超时时间为 wait(0) 时与调用 wait() 的作用是相同的。
原文:https://www.cnblogs.com/daihao-g/p/14792858.html