ReentrantLock是怎么保证线程安全中的可见性的?

ReentrantLock是怎么保证线程安全中的可见性的?

众所周知,可重入锁ReentrantLock可以用来实现线程安全(可见性,有序性,原子性),其中有序性和原子性都能理解,那么可见性是如何保证的呢?

1.锁中更改volatile变量

class MyTest{
    static class MyClass{
        public volatile int a = 0;
        public int b = 0;
    }
    public static void main(String[] args) throws InterruptedException {
        MyClass clazz = new MyClass();
        ReentrantLock lock = new ReentrantLock();
        new Thread(()->{
            try{
                lock.lock();
                Thread.sleep(300);
                clazz.a++;
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }).start();
        while(clazz.a == 0){
        }
        System.out.println("A结束");
        while(clazz.b==0){
        }
        System.out.println("b结束");
    }
    //运行结果:
    //A结束
}

从结果可以看出来,在子线程中修改了MyClass类中的a变量,Main线程中是可见的,而变量b没有被修改,所以最后会一直循环,程序不会结束。

2.锁中修改非volatile变量

如果将上面代码中的变量a改为非volatile,那么结果是什么呢?


// 运行结果:
// 

久久等不来想要的输出,Main线程还惦记着它的工作内存里的变量a呢。说明可重入锁里修改了变量其他线程无法立马知道。那么让Main线程先sleep一会呢?

class MyTest{
    static class MyClass{
        public int a = 0;
        public int b = 0;
    }
    public static void main(String[] args) throws InterruptedException {
        MyClass clazz = new MyClass();
        ReentrantLock lock = new ReentrantLock();
        new Thread(()->{
            try{
                lock.lock();
                Thread.sleep(1000);
                clazz.a++;
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }).start();
        Thread.sleep(1000);
        System.out.println(clazz.a);
        while(clazz.a == 0){
        }
        System.out.println("A结束");
        while(clazz.b==0){
        }
        System.out.println("b结束");
    }
    //运行结果:
    //1
    //A结束
}

说明可重入锁修改的变量,在Mian线程里还是可以发现的,只是**不知道什么原因没法立即发现。**猜测是Main线程不会重新读取JMM中主内存的变量,而如果使用sleep方法则会重新读取。

3.多个可重入锁间变量修改了可见吗

虽然上面的结论有点出乎我的意料,不过上面的代码其实并不常用。我们使用可重入锁的时候大多是互斥的访问临界区,而之前的经验中这样的代码似乎没有出过可见性的问题。

class MyTest{
    static class MyClass{
        public int a = 0;
        public int b = 0;
    }
    public static void main(String[] args) throws InterruptedException {
        MyClass clazz = new MyClass();
        ReentrantLock lock = new ReentrantLock();
        new Thread(()->{
            try{
                lock.lock();
            }finally {
                lock.unlock();
            }
        }).start();
        new Thread(()->{
            try{
                lock.lock();
                System.out.println(clazz.a);
            }finally {
                lock.unlock();
            }
        }).start();

        new Thread(()->{
            try{
                lock.lock();
                System.out.println(clazz.a);
            }finally {
                lock.unlock();
            }
        }).start();
    }
    //运行结果:
    //1
    //1
}

这里的结果到是符合想象,一个加锁线程里共享变量被修改了,其余加锁线程是可以知道的。

4.线程池里的尝试

上面的线程数太少了可能不正确,这里尝试一下线程池进行大量的计算,看看结果还是正确的吗?

class MyTest{
    static class MyClass{
        public int a = 0;
        public int b = 0;
    }
    public static void main(String[] args) throws InterruptedException {
        MyClass clazz = new MyClass();
        ReentrantLock lock = new ReentrantLock();
        ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 100, 10000, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<>());
        for (int i = 0; i < 10000; i++) {
            pool.submit(() -> {
                try {
                    lock.lock();
                    clazz.a++;
                }finally {
                    lock.unlock();
                }
            });
        }
        pool.shutdown();
        Thread.sleep(10000);
        System.out.println(clazz.a);
    }
    
    //运行结果:
    //10000
}

很明显,通过可重入锁加锁的变量被修改后,在这些加了可重入的线程里是可见的。而对于其他没=有加可重入锁的线程,如Main线程,就不一定可见了。

5.最后

还是挺好奇为什么Mian线程里没法保证可见性,希望有大佬赐教。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值