美文网首页
多线程volatile和synchronized

多线程volatile和synchronized

作者: 二狗不是狗 | 来源:发表于2020-02-06 15:27 被阅读0次

    一. 内存模型

    • 寄存器是中央处理器内的组成部份。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和位址。在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC)。在中央处理器的算术及逻辑部件中,包含的寄存器有累加器(ACC)。
    • Cache即高速缓冲存储器,是位于CPU与主内存间的一种容量较小但速度很高的存储器。用于解决内存速率低于CPU的运算速率的冲突。一般分为L1/L2/L3三级缓存。
    image.png

    二.内存模型的三个特性

    1.原子性
    • 在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
    • 只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。
    • Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
    // 原子性,直接将数值10赋值给x,也就是说线程执行这个语句的会直接将数值10写入到工作内存中
    x = 10;
    
    // 它先要去读取x的值,再将x的值写入工作内存
    // 虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作,但是合起来就不是原子性操作了。
    y = x;
    
    // 包括3个操作:读取x的值,进行加1操作,写入新的值
    x++;
    
    // 包括3个操作:读取x的值,进行加1操作,写入新的值
    x = x + 1;
    
    2.可见性
    • 对于可见性,Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
    • 而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
    • 另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
    3.有序性
    • java内存模型规定如下的部分顺序,不符合以下规则的需要通过使用volitile 或者synchronized Lock进行操控。
    • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的
    • 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
    • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
    • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
    • 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
    • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
    • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
    • 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

    三.volatile关键字

    • 保证可见性:使用volatile修饰的变量,保证了不同线程之间对共享变量操作时的可见性,也就是说当一个线程修改后,另一个线程会立即看到最新的值。
    • 保证有序性:禁止对指令进行重新排序操作
    • 不能保证原子性:像num++这个操作上,因为num++不是个原子性的操作,而是个复合操作:1.读取;2.加一;3.赋值。所以,在多线程环境下,有可能线程A将num读取到本地内存中,此时其他线程可能已经将num增大了很多,线程A依然对过期的num进行自加,重新写到主存中,最终导致了num的结果不合预期。

    四.synchronized关键字

    • synchroized可以保证原子性,可见性以及有序性
    public class RespUtils {
        private static int sum = 0;
    
        private Integer objectSum() {
            for (int m = 1; m<=100000; m++) { sum++; }
            System.out.println("函数内返回=" + sum);
            return sum;
        }
    
        private synchronized Integer synObjectSum() {
            for (int m = 1; m<=100000; m++) { sum++; }
            System.out.println("函数内返回=" + sum);
            return sum;
        }
    
        private Integer synObjectBlockSum() {
            synchronized (this) {
                for (int m = 1; m<=100000; m++) { sum++; }
                System.out.println("函数内返回=" + sum);
                return sum;
            }
        }
    
        private static Integer classSum() {
            for (int m = 1; m<=100000; m++) { sum++; }
            System.out.println("函数内返回=" + sum);
            return sum;
        }
    
        private synchronized static Integer synClasssSum() {
            for (int m = 1; m<=100000; m++) { sum++; }
            System.out.println("函数内返回=" + sum);
            return sum;
        }
    
        private static Integer synClassBlockSum() {
            synchronized (RespUtils.class) {
                for (int m = 1; m<=100000; m++) { sum++; }
                System.out.println("函数内返回=" + sum);
                return sum;
            }
        }
    
        public static void main(String[] args) throws Exception {
            final RespUtils respUtils = new RespUtils();
    
            Callable<Integer> callable = new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return respUtils.objectSum();
                }
            };
    
            new Thread(new FutureTask<>(callable)).start();
            new Thread(new FutureTask<>(callable)).start();
        }
    }
    
    1.实例锁-普通方法
    private Integer objectSum() {
        for (int m = 1; m<=100000; m++) { sum++; }
        System.out.println("函数内返回=" + sum);
        return sum;
    }
    

    上面代码输出打印(结果不确定):
    函数内返回=116780
    函数内返回=116780

    2.实例锁-对象本身
    final RespUtils respUtils = new RespUtils();
    
    Callable<Integer> callable = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            synchronized (respUtils) {
                return respUtils.objectSum();
            }
        }
    };
    
    new Thread(new FutureTask<>(callable)).start();
    new Thread(new FutureTask<>(callable)).start();
    

    上面代码输出打印(结果确定):
    函数内返回=100000
    函数内返回=200000

    3.实例锁-对象方法
    private synchronized Integer synObjectSum() {
        for (int m = 1; m<=100000; m++) { sum++; }
        System.out.println("函数内返回=" + sum);
        return sum;
    }
    

    上面代码输出打印(结果确定):
    函数内返回=100000
    函数内返回=200000

    4.实例锁-对象代码块
    private Integer synObjectBlockSum() {
        synchronized (this) {
            for (int m = 1; m<=100000; m++) { sum++; }
            System.out.println("函数内返回=" + sum);
            return sum;
        }
    }
    

    上面代码输出打印(结果确定):
    函数内返回=100000
    函数内返回=200000

    5.全局锁-普通方法
    Callable<Integer> callable = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            return RespUtils.classSum();
        }
    };
    
    new Thread(new FutureTask<>(callable)).start();
    new Thread(new FutureTask<>(callable)).start();
    

    上面代码输出打印(结果不确定):
    函数内返回=114195
    函数内返回=114195

    6.全局锁-类本身
    Callable<Integer> callable = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            synchronized (RespUtils.class) {
                return RespUtils.classSum();
            }
        }
    };
    
    new Thread(new FutureTask<>(callable)).start();
    new Thread(new FutureTask<>(callable)).start();
    

    上面代码输出打印(结果确定):
    函数内返回=100000
    函数内返回=200000

    7.全局锁-类方法
    private synchronized static Integer synClasssSum() {
        for (int m = 1; m<=100000; m++) { sum++; }
        System.out.println("函数内返回=" + sum);
        return sum;
    }
    

    上面代码输出打印(结果确定):
    函数内返回=100000
    函数内返回=200000

    8.全局锁-类代码块
    private static Integer synClassBlockSum() {
        synchronized (RespUtils.class) {
            for (int m = 1; m<=100000; m++) { sum++; }
            System.out.println("函数内返回=" + sum);
            return sum;
        }
    }
    

    上面代码输出打印(结果确定):
    函数内返回=100000
    函数内返回=200000

    五.使用注意事项

    1.实例锁-注意事项
    • 实例锁的意思是要执行某段代码必须获得具体对象实例的锁。实例锁-对象本身是指要执行线程synchronized (object){ }内部代码必须获得对象实例的锁;实例锁-对象方法是指要执行对象方法内部代码必须获得对象实例的锁;实例锁-对象代码块是指要执行代码块synchronized (this) { }内部代码必须获得对象实例的锁。
    • 实例锁只针对需要获取对象实例锁才能执行的代码,而不针对不需要获取对象实例锁的代码。比如说调用了某对象的synObjectSum方法,还能同时访问该对象的objectSum方法。
    • 实例锁是针对对象的,对于两个不同的对象可以并发访问。比如说创建了两个对象respUtils1和respUtils2,多线程可以并发访问这两个对象的synObjectSum()方法。
    2.全局锁-注意事项
    • 全局锁的意思是要执行某段代码必须获得Class全局的锁。全局锁-类本身是指要执行线程synchronized (RespUtils.class) { }内部代码必须获得Class全局的锁;全局锁-类方法是指要执行类方法内部代码必须获得Class全局的锁;全局锁-类代码块是指要执行代码块synchronized (RespUtils.class) { }内部代码必须获得Class全局的锁。
    • 全局锁只针对需要获取Class全局的锁才能执行的代码,而不针对不需要Class全局的锁的代码。比如说调用了某类的synClasssSum方法,还能同时访问该对象的classSum方法。
    3.注意事项
    • 实例锁和全局锁是不同的锁,所以两个线程可以分别获取到实例锁和全局锁并发同时执行
    new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (new RespUtils()) {
                new RespUtils().objectSum();
            }
        }
    }, "线程1").start();
    
    new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (RespUtils.class) {
                new RespUtils().objectSum();
            }
        }
    }, "线程2").start();
    

    上面代码输出打印(结果不确定):
    函数内返回=114195
    函数内返回=114195

    相关文章

      网友评论

          本文标题:多线程volatile和synchronized

          本文链接:https://www.haomeiwen.com/subject/hmcuxhtx.html