线程中止
线程自然终止
要么是 run 执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。
stop
暂停、恢复和停止操作对应在线程 Thread 的 API 就是 suspend()、resume() 和 stop()。但是这些API 是过期的,也就是不建议使用的。不建议使用的原因主要有:以 suspend() 方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop() 方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为 suspend()、 resume()和 stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。
中断
安全的中止则是其他线程通过调用某个线程 A 的 interrupt()方法对其进行中 断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程 A 会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。 因为 java 里的线程是协作式的,不是抢占式的。线程通过检查自身的中断标志位是否被置为 true 来进行响应。
(1)区别interrupt()、isInterrupted()和Thread.interrupted()这三个方法
调用interrupt()不会强制中断线程,而是设置中断标识位,最终结果由线程本身决定,这也就说明线程之间是协作式的,不是抢占式。线程是否执行中断,通过isInterrupted()来判断,如果线程执行了中断,isInterrupted()置为true。
Thread.interrupted() 这个静态方法同样可以检测到中断标志位,但它会把true改成false。
我们通过代码来看下他们之间的区别:
首先我们用isInterrupted()来判断:
public class EndThread {
private static class UserThread extends Thread {
public UserThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start interrupt flag = " + isInterrupted());
while (!isInterrupted()) {
//while (!Thread.interrupted()) {
System.out.println(threadName + " is running flag " + isInterrupted());
}
System.out.println(threadName + " end interrupt flag = " + isInterrupted());
}
}
public static void main (String[] args) throws InterruptedException {
UserThread endThread = new UserThread("endThread");
endThread.start();
Thread.sleep(1);
endThread.interrupt(); //中断线程,其实设置了中断标志位
}
}
执行结果:
endThread start interrupt flag = false
endThread is running flag false
endThread is running flag false
endThread is running flag false
endThread is running flag false
....
endThread end interrupt flag = true
Process finished with exit code 0
通过结果,我们看到,启动线程时,interrupt标志位为false,当我们执行interrupt()进行中断时,interrupt标志位置为true,程序被中断结束。
再看第二种,用静态方法Thread.interrupted()做判断条件:
直接看结果:
endThread start interrupt flag = false
endThread is running flag false
endThread is running flag false
endThread is running flag false
.....
endThread end interrupt flag = false
Process finished with exit code 0
Thread.interrupted()同样可以检测到中断标志位,但它会把true改成false。
调用sleep()等阻塞类方法,当抛出InterruptedException被捕获到时,会把isInterrupted()标志位由true变为false,所以sleep()方法并不会中断线程,如果想要中断,需要在sleep()的catch语句块中,手动调用interrupt()。
注意:处于死锁状态的线程无法被中断
public class SleepThread {
private static class UserThread extends Thread {
public UserThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start interrupt flag = " + isInterrupted());
while (!isInterrupted()) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
//资源释放
interrupt();
e.printStackTrace();
}
System.out.println(threadName + " is running flag " + isInterrupted());
}
System.out.println(threadName + " end interrupt flag = " + isInterrupted());
}
}
public static void main (String[] args) throws InterruptedException {
Thread endThread = new UserThread("endThread");
endThread.start();
Thread.sleep(5);
endThread.interrupt(); //中断线程,其实设置了中断标志位
}
}
执行结果:
endThread start interrupt flag = false
endThread is running flag true
endThread end interrupt flag = true
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.company.Android3.SleepThread$UserThread.run(SleepThread.java:19)
Process finished with exit code 0
守护线程不一定会执行,看CPU时间片分配到它没。所以当设置当前线程为守护线程时,finally语句块的代码不一定会执行。所以在finally里执行逻辑业务时,要区分开守护线程和用户线程。
public class DaemonThread {
private static class UserThread extends Thread {
public UserThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " interrupt flag = " + isInterrupted());
try {
while (!isInterrupted()) {
System.out.println(threadName + " is running flag " + isInterrupted());
}
System.out.println(threadName + " interrupt flag = " + isInterrupted());
} finally {
//守护线程中finally不一定起作用
System.out.println("............finally");
}
}
}
public static void main (String[] args) throws InterruptedException {
UserThread userThread = new UserThread("endThread");
userThread.setDaemon(true);
userThread.start();
Thread.sleep(1);
//userThread.interrupt(); //中断线程,其实设置了中断标志位
}
}
代码里使用userThread.setDaemon(true)来设置当前线程为守护线程,当主线程结束后,finally语句块代码不一定执行。
线程间的共享
线程同步 加锁
synchronized关键字,可以对方法加锁,也可以对代码块加锁,不管是对方法加锁还是对代码块加锁,锁的都是某一个对象,也就是对象锁。
类锁,静态方法加锁。每个类在加载时候都会有一个class对象,所以类锁锁的是class对象,本质上还是对象锁。
volatile保证的是可见性 old value --> new value
ThreadLocal辨析
ThreadLocal 为每个线程都提供了变量的副本,使得每个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享,保证了线程的安全性。
ThreadLocal 类接口很简单,只有 4 个方法,我们先来了解一下:
- void set(Object value)
设置当前线程的线程局部变量的值。
- public Object get()
该方法返回当前线程所对应的线程局部变量。
- public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK 5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动 被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它 可以加快内存回收的速度。
- protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为 了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一 个 null。
- public final static ThreadLocal RESOURCE = new ThreadLocal();
RESOURCE代表一个能够存放String类型的ThreadLocal对象。 此时不论什么一个线程能够并发访问这个变量,对它进行写入、读取操作,都是线程安全的。
测试代码:
/**
* 演示ThreadLocal的使用
*/
public class UseThreadLocal {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 1;
}
};
/**
* 运行3个线程
*/
public void runThreadArray() {
Thread[] threads = new Thread[3];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new TestThread(i));
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
}
/**
* 测试线程,线程的工作是将ThreadLocal的值发生变化,并写回,看看线程之间是否会互相影响
*/
public static class TestThread implements Runnable {
int id;
public TestThread(int id) {
this.id = id;
}
@Override
public void run() {
Integer s = threadLocal.get();
s = s + id;
threadLocal.set(s);
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
}
}
public static void main(String[] args) {
UseThreadLocal useThreadLocal = new UseThreadLocal();
useThreadLocal.runThreadArray();
}
}
执行结果:
Thread-1:2
Thread-2:3
Thread-0:1
从结果中看到,使用了ThreadLocal后,每个线程都单独拥有一个变量的副本,所以得到的结果是:id在这三个线程中是互不影响的,每次执行+1操作后,id的值会自加一。
ThreadLocal实现源码解析
上面先取到当前线程,然后调用getMap方法获取对应的ThreadLocalMap,ThreadLocalMap是ThreadLocal的静态内部类,然后Thread类中有一个这样的类型成员,所以getMap直接返回Thread的成员。
再看下ThreadLocal的内部类ThreadLocalMap的源码:
可以看到有个Entry静态内部类,它继承了WeakReference,总之它记录了两个信息,一个是ThreadLocal<?>类型,一个是Object类型的值。
getEntry方法则是获取某个ThreadLocal对应的值,set方法就是更新或赋值相应的ThreadLocal对应的值。
回顾我们的get方法,其实就是拿到每个线程独有的ThreadLocalMap,然后再用ThreadLocal的当前实例,拿到Map中相应的Entry,然后就可以拿到相应的值返回出去。当然,如果Map为空,还会先进行map的创建、初始化工作。
线程间的协作
等待/通知机制
是指一个线程 A 调用了对象 O 的 wait()方法进入等待状态,而另一个线程 B 调用了对象 O 的 notify()或者 notifyAll()方法,线程 A 收到通知后从对象 O 的 wait() 方法返回,进而执行后续操作。上述两个线程通过对象 O 来完成交互,而对象 上的 wait()和 notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通 知方之间的交互工作。
- notify()
通知一个在对象上等待的线程,使其从 wait 方法返回,而返回的前提是该线程 获取到了对象的锁,没有获得锁的线程重新进入 WAITING 状态。
- notifyAll()
通知所有等待在该对象上的线程 。
- wait()
调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回。需要注意:调用 wait()方法后,会释放对象的锁。
- wait(long)
超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n 毫秒,如果没有通知就超时返回 。
- wait (long,int)
对于超时时间更细粒度的控制,可以达到纳秒
等待和通知的标准范式
等待:
synchronized (obj) {
while(条件不满足) {
obj.wait();
}
//业务逻辑
}
通知:
synchronized (obj) {
//业务逻辑,改变条件
obj.notify()/notifyAll();
//do sth;
}
在调用 wait()、notify()系列方法之前,线程必须要获得该对象的对象级 别锁,即只能在同步方法或同步块中调用 wait()方法、notify()系列方法,进 入 wait() 方法后,当前线程释放锁,在从 wait() 返回前,线程与其他线程竞争重新获得锁,执行 notify() 系列方法的线程退出调用了 notifyAll 的 synchronized 代码块的时候后,他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。
notify 和 notifyAll 应该用谁
尽可能用 notifyAll(),谨慎使用 notify(),因为 notify()只会唤醒一个线程,我 们无法确保被唤醒的这个线程一定就是我们需要唤醒的线程。