浅谈Java并发编程-1-Java内存模型(JMM)

目录

 

一、可见性简介

二、synchronized实现可见性原理

三、volatile保证可见性

四、volatile适用场景

五、synchronized和volatile比较

六、总结


 

 

一、可见性简介

1、可见性:一个线程对共享变量值的修改,能够及时地被其他线程看到。

2、共享变量:如果一个变量在多个线程的工作内存中都存有副本,那么这个变量就是共享变量。

3、java内存模型(JMM)

所有的变量都存在主内存中,每个线程都有自己的工作内存,里面保存着该线程使用到的变量副本(就是主内存中该变量的一份拷贝)。

 

Java内存模型有两条规定:

1、线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接对主内存中进行读写

2、不同线程之间无法访问其他线程工作内存中的变量,线程间变量的值传递都需要通过主内存来完成。

 

二、synchronized实现可见性原理

synchronized可以实现互斥锁,可以保证在同一个时刻都只有一个线程在执行锁里面的代码。

synchronized能够实现:原子性(同步)、可见性

(一)JMM关于synchronized的两条规定

1、线程解锁前,必须把共享变量的最新值刷新到主内存中

2、线程加锁时,将情况工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值。

 

(二)线程执行互斥代码的过程:

  1. 获得互斥锁

  2. 清空工作内存

  3. 从主内存拷贝变量的最新副本到线程工作内存

  4. 执行代码

  5. 将更改后的最新的共享变量的值刷新到主内存

  6. 释放互斥锁

 

(重排序:代码书写的顺序与实际执行的顺序不同,指令重排序是编译器或处理器为了提高程序性而做的优化)

as-if-serial:无论如何重排序,程序执行的结果都应该和代码顺序执行的结果一致。

试着用synchronized关键字写一个读写的demo: 

package com.jqs.sync;

public class SynchronizedDemo {

    //共享变量
    private int result = 0;
    private boolean ready = false;
    private int num = 1;

    //写操作
    public synchronized void write() { //synchronized在此可以保证共享变量的内存可见性,从而线程安全
        ready = true;
        num = 2;

    }

    //读操作
    public synchronized void read() { //synchronized在此可以保证共享变量的内存可见性,从而线程安全
        if (ready)
            result = num * 3;
        System.out.println("result中的结果为:" + result);

    }

    private class ReadWriteThread extends Thread {
        private boolean flag;

        public ReadWriteThread(boolean flag) {
            this.flag = flag;
        }

        @Override
        public void run() {
            if (flag)
                write();
            else
                read();
        }


    }

    public static void main(String args[]) {


        SynchronizedDemo demo = new SynchronizedDemo();
        demo.new ReadWriteThread(true).start();
        try {
            Thread.sleep(1000);
            /*
             * synchronized可以保证线程之间不会交叉执行,但是仍有可能因为读线程先执行run方法导致结果为0。(即是写线程不是一定就会在读线程执行之前先执行完)
             * 所以在写线程启动之后加入主线程的休眠语句,这样写线程就可以基本在读线程执行之前执行完!!!正确结果应该是6
             * */
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        demo.new ReadWriteThread(false).start();


    }

}

注:当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将会被阻塞

 

三、volatile保证可见性

volatile关键字:能够保证volatile变量的可见性,不能保证volatile变量复合操作的原子性

如何实现内存可见性:通过加入内存屏障和禁止重排序优化来实现

简单的说就是,volatile变量在每次被线程访问时,都强迫从主内存中读取该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存,这样任何时刻,不同的线程总能看到该变量的最新值。

注意:volatile不能保证volatile变量复合操作的原子性

(原子性: 原子性就是指该操作是不可再分的。不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。比如 a = 1;):

private volatile int number=1;

number++; 
//不是原子操作

//分为三步:1、读number值  2、number值加1 3、写入number的值

试着写一个demo

package com.jqs.sync;

public class VolatileDemo {
    private volatile int number = 0;

    public int getNumber() {
        return this.number;
    }

    public void increase() {

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.number++;
    }

    public static void main(String args[]) {
        final VolatileDemo demo = new VolatileDemo();
        for (int i = 0; i < 500; i++) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    demo.increase();
                }
            }).start();
        }


        /*
         * 如果还有子线程还在执行,主线程就让出cpu资源
         * 直到所有的子线程都运行完了,主线程再往下执行
         * */
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }

        System.out.println("number:" + demo.getNumber());
    }
    //由于number++不是原子操作,对应的三步操作有可能会有线程交叉执行,最终导致结果可能不为500

}

存在这种情况的原因,就是线程A读取到了num值,但是执行时被B占用了cup,num被B加一并且写入了主内存,但是此时A线程中的num值还没有加一,之后A再加一将值写入,就不对了。

想要解决的话,就把number++替换为

synchronized (this){

this.number++;

}

或者用到lock,具体看代码


Lock lock=new ReentrantLock();

lock.lock();

try {

this.number++;

}finally {

lock.unlock();

}

 

四、volatile适用场景

1、对变量的写入操作不依赖其当前值

不满足的:number++、count=count*5

满足的:boolean变量、温度记录变量等

2、该变量没有包含在具有其他变量的不变式中

如 low<up

 

五、synchronized和volatile比较

1、volatile不需要加锁,比synchronized更轻量级,不会阻塞线程

2、从内存可见性的角度讲,volatile读相当于加锁,volatile写相当于解锁

3、synchronized既能保证可见性,又可保证原子性,而volatile只能保证可见性,不能保证原子性。总结来说,就是synchronized可以保证线程安全,但是volatile没办法保证线程安全

六、总结

如果既想要线程安全又想性能好的话,synchronized和volatile要结合具体情况使用。

synchronized并不是万能的,在加锁时能够锁局部就不要锁住整体,能够锁变量就不要锁方法。在保证线程安全的前提下,加锁的范围越小越好。

 

参考:《Java并发编程实践》

 

如有错误,欢迎指出,以上。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值