美文网首页一些收藏
Java多线程21 JMM与Lock

Java多线程21 JMM与Lock

作者: 香沙小熊 | 来源:发表于2020-02-02 09:53 被阅读0次

    II. 缓存一致性
    MESI协议中的缓存状态

    状态 含义 监听任务
    M 被修改
    Modified
    因为缓存行刚被修改,数据应是独一无二的,与主存中的数据不一致,与其他CPU的缓存中对应数据也不相同。但是一定会在将来某个时间点写回主存中,这个时间点一定是在其他CPU读取自己的(主存相应的)缓存之前。 被修改的缓存行必须时刻监听所有想读该缓存行对应的主存/其他CPU缓存的操作,因为要确保在CPU读取操作之前把被修改的缓存行写回主存并将状态变为 S。
    E 独享的
    Exclusive
    被修改状态的缓存行要将数据写回主存,此时可以认为是独享的状态。只有自己的缓存和主存中数据一致,其他CPU对应的缓存行还没有更新。但是一定会在将来其他CPU读取对应的缓存行之前变为共享状态。 独享的缓存行也必须监听其他CPU缓存读取主存中对应缓存行的操作,一旦有了这种操作,该缓存行需要变成 S 状态。
    S 共享的
    Shared
    该状态意味该缓存行可能被多个CPU缓存,并且各个缓存中的数据与主存中的数据是一致的。当有一个CPU修改了该缓存行中的数据,该缓存行变为被修改状态,其他CPU中对应的缓存作废,变为无效状态 I。 对于该缓存行来说,要监听其他缓存使该缓存行无效。
    I 无效的
    Invalid 缓存行作废,无效。
    image.png

    Heap(堆):java 里的堆是一个运行时的数据区,堆是由垃圾回收来负责的,堆的优势是可以动态的分配内存的大小,生存期也不必事先告诉编译器,因为他是在运行时动态分配内存的,java的垃圾回收器会定时收走不用的数据,缺点是由于要在运行时动态分配,所有存取速度可能会慢一些。
    Stack(栈):栈的优势是存取速度比堆要快,仅次于计算机里的寄存器,栈的数据是可以共享的,缺点是存在栈中的数据的大小与生存期必须是确定的,缺乏一些灵活性
    栈中主要存放一些基本类型的变量,比如int,short,long,byte,double,float,boolean,char
    对象句柄。

    Java内存模型
    Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。执行线程必须先在自己的工作线程中对变量i所在的缓存行进行赋值操作,然后再写入主存当中。

    原子性:Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。

    x = 10; //语句1
    y = x; //语句2
    x++; //语句3
    x = x + 1; //语句4

    1)语句1是直接将数值10赋值给x,也就是说线程执行这个语句的会直接将数值10写入到工作内存中。
    2)语句2实际上包含2个操作,它先要去读取x的值,再将x的值写入工作内存,虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作,但是合起来就不是原子性操作了。
    3)同样的,x++和 x = x+1包括3个操作:读取x的值,进行加1操作,写入新的值。所以上面4个语句只有语句1的操作具备原子性。

    可见性
    Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

    另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

    有序性
    在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

    在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

        private volatile boolean stop = false;
    
        public void shutDown() {
            stop = true;
        }
    
        public void doWork() {
            while (!stop) {
            }
            System.out.println("你能读到我们...");
        }
    
        public static void main(String[] args) throws InterruptedException {
            Volatile01 volatile01 = new Volatile01();
            new Thread(() -> {
                volatile01.doWork();
            }).start();
    
            Thread.sleep(2000);
    
            new Thread(() -> {
                volatile01.shutDown();
            }).start();
        }
    

    运行结果:

    你能读到我们...
    

    去掉volatile在去执行
    运行结果无输出

    方法和代码块(对象锁和类锁):
    对于普通同步方法,锁是当前实例对象。
    对于静态同步方法,锁是当前类的Class对象。
    对于同步方法块,锁是Synchonized括号里配置的对象。

    public class Sync01 implements Runnable{
        static int i = 0;
        @Override
        public void run() {
            add();
        }
    
        private synchronized void add(){
            for(int j = 0;j < 10000;j++){
                i++;
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Sync01 sync01 = new Sync01();
            Sync01 sync02 = new Sync01();
    
            Thread thread = new Thread(sync01);
            Thread thread2 = new Thread(sync02);
    
            thread.start();
            thread2.start();
    
            thread.join();
            thread2.join();
    
            System.out.println(i);
        }
    }
    

    结果输出 值<20000

    添加static关键字

        private synchronized static void add(){
            for(int j = 0;j < 10000;j++){
                i++;
            }
        }
    

    结果输出 值=20000

        private void add() {
            synchronized (Sync01.class) {
                for (int j = 0; j < 10000; j++) {
                    i++;
                }
            }
        }
    
        private void add() {
            synchronized (this) {
                for (int j = 0; j < 10000; j++) {
                    i++;
                }
            }
        }
    

    结果输出 值<20000

    public class ReentrantLock01 {
    
        static int i = 0;
    
        public ReentrantLock lock = new ReentrantLock();
    
        public static void main(String[] args) throws InterruptedException {
            ReentrantLock01 reentrantLock01 = new ReentrantLock01();
            ReentrantLock01 reentrantLock02 = new ReentrantLock01();
            new Thread(() -> {
                reentrantLock01.add();
            }).start();
            //Thread.sleep(1000);
            new Thread(() -> {
                reentrantLock02.add();
            }).start();
            Thread.sleep(1000);
            System.out.println(i);
        }
    
        private void add() {
            try {
                lock.lock();
                for (int j = 0; j < 10000; j++) {
                    i++;
                }
            } finally {
                lock.unlock();
            }
        }
    }
    

    结果输出 值=20000

    public class ReentrantLock04 implements Runnable {
    
        public static ReentrantLock reentrantLock = new ReentrantLock();
        @Override
        public void run() {
            try{
                if(reentrantLock.tryLock(5, TimeUnit.SECONDS)){
                    Thread.sleep(6000);
                    System.out.println("获取");
                }else {
                    System.out.println("获取失败");
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                if(reentrantLock.isHeldByCurrentThread()){
                    reentrantLock.unlock();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
         ReentrantLock04 reentrantLock04 = new ReentrantLock04();
            IntStream.range(0,2).forEach(i->new Thread(reentrantLock04){}.start());
    
        }
    }
    

    结果输出

    获取失败
    获取
    
    Synchronized和ReetrantLock区别
    实现方式

    Lock是代码级别的,synchronized是JVM级别的

    公平
         Lock可以是公平所,也可以是不公平锁,默认是非公平锁,synchronized是非公平锁
    
    释放
         Lock的释放必须手动调用unlock()方法,而synchronized在代码出了代码块或方法之后就会自动释放锁。
    
    等待中断
         Lock中如果持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,但是synchronized不会
    
    条件变量
         Lock中可以有多个Condition来实现线程间通信,而synchronized是能通过当前锁来进行线程通信。
    
    什么时候选择用ReetrantLock代替Synchronized

    1、 在确实需要一些synchronized锁没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票的时候

    2、 优先推荐synchronized开发,如果事实证明synchronized确实不合适,再用ReetrantLock开发。

    相关文章

      网友评论

        本文标题:Java多线程21 JMM与Lock

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