java多线程之synchronized 和 volatile - (effective java item66中的一处错误)

本文探讨了如何优雅地停止Java线程,对比了错误与正确的做法。通过使用volatile关键字和synchronized块来确保线程间变量可见性和同步。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

怎么优雅的停止线程呢?先给出一个

错误的方式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内存模型定义了以下内存屏障:

  1. 写入屏障(Store Barrier) - 当写入一个 volatile 变量时,会在写操作后插入一种称为“Store Barrier”的内存屏障,确保这次写入对其他线程立即可见,并且写入之前的所有操作都不会被重排序到写入之后。

  2. 读取屏障(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的第二点作用是不被熟知的,这里强调下。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值