怎么优雅的停止线程呢?先给出一个
错误的方式1
public class ThreadStop {
//失败的方法1 这种在一秒后stopThread虽然被修改了,但是backThread并没有获取到该信息
public static boolean stopThread;
public static void main(String[] args) throws InterruptedException {
Thread backThread = new Thread(new Runnable(){
@Override
public void run() {
int i=0;
while(!stopThread){
i++;
// 有打印语句的的时候,代码会自动停止,很奇怪!!!
// System.out.println("ii");
}
// 没有上面被注掉的打印语句的时候,代码会被虚拟机转化成,所以不会自动停止:
// if(!stopThread)
// while(true)
// {
// i++;
// }
}
});
backThread.start();
TimeUnit.SECONDS.sleep(1);
stopThread=true;
}
}
执行上面的方法会发现 1秒之后这个线程并不会停止,而是一直执行。这是因为stopThread变量在主线程中修改的时候,修改后的结果并没有通知到另一个线程。很自然下面有一种
正确的方式2:
public static volatile boolean stopThread;
public static void main(String[] args) throws InterruptedException {
Thread backThread = new Thread(new Runnable(){
@Override
public void run() {
int i=0;
while(!stopThread){
i++;
}
}
});
backThread.start();
TimeUnit.SECONDS.sleep(1);
stopThread=true;
}
修改之处仅仅是对stopThread增加了volatile声明,此时修改后的信息会被另一线程获取
volatile到底起了什么作用呢?
其实程序在执行的时候,主线程会有一个主内存,变量最终的值会被放到主内存中,但是
每个子线程都会有单独自己的内存,对变量的修改一般都是各自维护各自内存中的值,在将变量修改为volatile之后,所有涉及到对该变量的修改和读取都是直接从主内存获取,这样就解决了同一变量在不同线程内存值不一致的问题。
volatile有一个错误的用法,如下
volatile int i;在多个线程中有i++, 则无法保证线程安全,因为++操作不是原子性的,是可分的,分为三步,获取当前i的值,对i加1获取一个新的值,将新的值赋给i;
在Java中,对 volatile
变量的读写操作具有内存屏障(Memory Barrier)的效果。内存屏障是一种CPU指令,它提供了一些特定的内存可见性保证。对于 volatile
变量的操作,Java内存模型定义了以下内存屏障:
-
写入屏障(Store Barrier) - 当写入一个
volatile
变量时,会在写操作后插入一种称为“Store Barrier”的内存屏障,确保这次写入对其他线程立即可见,并且写入之前的所有操作都不会被重排序到写入之后。 -
读取屏障(Load Barrier) - 当读取一个
volatile
变量时,会在读操作前插入一种称为“Load Barrier”的内存屏障,确保这次读取能看到最新的写入,并且读取之后的所有操作都不会被重排序到读取之前。
具体的内存屏障类型和实现细节依赖于底层的硬件平台和JVM的实现。
下面是synchronized实现的正确方式
错误的方式4如下:
public static boolean stopThread;
public static void changeValue(){
stopThread = true;
}
public static void main(String[] args) throws InterruptedException {
Thread backThread = new Thread(new Runnable(){
@Override
public void run() {
int i=0;
while(!getValue()){
i++;
}
}
//非同步方法读,此时会读本线程内存中的未修改的值
public boolean getValue(){
return stopThread;
}
});
backThread.start();
TimeUnit.SECONDS.sleep(1);
changeValue();
}
不难理解,和方式1一样,stophread仅仅修改了方主线程内存中的值,子线程中读取的还是老的值;
正确的方式5如下:
public static boolean stopThread;
public static void changeValue(){
stopThread = true;
}
public static void main(String[] args) throws InterruptedException {
Thread backThread = new Thread(new Runnable(){
@Override
public void run() {
int i=0;
while(!getValue()){
i++;
}
}
//同步方法读,此时会读最新的
public synchronized boolean getValue(){
return stopThread;
}
});
backThread.start();
TimeUnit.SECONDS.sleep(1);
changeValue();
}
此方法为什么能实现呢?这要谈下synchronized的作用:
1)锁功能,防止同一段代码被多个线程同时执行,单个时间只能被一个线程执行
2)类似volatile的功能,从主线程对应内存中读取最新的数据
synchronized的第二点作用是不被熟知的,这里强调下。