zoukankan      html  css  js  c++  java
  • synchronized和ReentrantLock锁住了谁?

    一、synchronized

      案例1:

    public class LockDemo{
        
        public static void main(String[] args) throws Exception {
            Human human = new Human();
            new Thread(() -> {
                try {
                    human.drink();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            },"A").start();
            
            Thread.sleep(100);//确保A线程先启动
            
            new Thread(() -> {
                Human.sleep();
            },"B").start();
        }
    }
    class Human{
        public void eat() {
            System.out.println(Thread.currentThread().getName()+ ": *****eat*****");
        }
        public synchronized void drink() throws Exception {
            TimeUnit.SECONDS.sleep(3);
            System.out.println(Thread.currentThread().getName()+": *****drink*****");
        }
        public synchronized static void sleep() {
            System.out.println(Thread.currentThread().getName()+": *****sleep*****");
        }
    }

      由于输出结果是动态的不好截图,是能口述输出结果:先输出B:******sleep*****,2.9秒后输出A:******drink*****

      在main方法中,使用Thread.sleep(100)秒让主线程睡眠,确保A线程先于B线程拿到资源。首先,我们知道sleep方法并不会是释放锁对象,按理说输出结果应该是三秒后同时输出A:******drink*****和B:******sleep*****,怎么会出现上面的结果呢?原因很简单,dink方法上的synchronized和sleep方法上的synchronized锁的不是同一个资源!

      当在非静态方法上加锁,锁的是类的实例对象。当在静态方法上加锁,所得就是类的对象。也就是说当线程A调用加锁方法drink后,其他线程不能再调用此方法的加锁资源,但是线程B之所以可以调用sleep方法,是因为线程B拿到的是类对象的锁,两者并不冲突,就好像两个人进两扇门,谁也不碍着谁。

      案例2:

    public class LockDemo{
        
        public static void main(String[] args) throws Exception {
            Human human = new Human();
            new Thread(() -> {
                try {
                    human.drink();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            },"A").start();
            
            Thread.sleep(100);//确保A线程先启动
            
            new Thread(() -> {
                human.eat();
            },"B").start();
        }
    }
    class Human{
        public void eat() {
            System.out.println(Thread.currentThread().getName()+ ": *****eat*****");
        }
        public synchronized void drink() throws Exception {
            TimeUnit.SECONDS.sleep(3);
            System.out.println(Thread.currentThread().getName()+": *****drink*****");
        }
        public synchronized static void sleep() {
            System.out.println(Thread.currentThread().getName()+": *****sleep*****");
        }
    }

      输出结果是:先输出B:******eat*****,2.9秒后输出A:******drink*****

      分析:首先不用多虑,A线程拿到资源后,锁住了贡献资源human对象,但是B线程访问的并不是加了锁的方法,而是普通方法,这就好像两个人去上厕所,一个人要蹲大号,一个人是小号,小号完成后,蹲大的才刚刚开始【如果你在吃饭,请你原谅我,实在想不出什么形象的案例】

      过多的案例不再多举,只需要搞明白一点:锁是对象的一部分,而不是线程的一部分。线程只是暂时的持有锁,在线程持有锁的这段时间里,其他线程不能访问此对象的同步资源,可以访问此对象的费同步资源。

    二、线程通信以及while

      上面的案例中并未涉及到线程通信,然而现实的业务纷繁复杂,通常都是线程之间的协作完成业务的处理,最典型的就是———生产者消费者模式

      实现复杂的业务需要更加灵活的锁——Lock,Lock接口有多个实现类,提供了更加灵活的结构,可以为同一把锁“配多把钥匙”。

      案例1:

    public class LockDemo{
        
        public static void main(String[] args) throws Exception {
            Shop shop = new Shop();
            
            new Thread(() -> {
                    try {
                        shop.produce();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
            },"P_A").start();
            new Thread(() -> {
                    try {
                        shop.produce();
                    } catch (Exception e) {
                        e.printStackTrace();
                }
            },"P_B").start();
            new Thread(() -> {
                    try {
                        shop.consume();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
            },"C_C").start();
            new Thread(() -> {
                    try {
                        shop.consume();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
            },"C_D").start();
        }
    }
    class Shop{
        int number = 1;
        
        public synchronized void produce() throws Exception {
            if(number != 0) {
                wait();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+": "+number);
            notifyAll();
        }
        public synchronized void consume() throws Exception {
            if(number == 0) {
                wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+": "+number);
            notifyAll();
        }
    }

    输出:

    C_C: 0
    P_B: 1
    P_A: 2
    C_D: 1

      灵魂质问:为什么会输出 2 ?

      情况可以这样发生:当线程P_A抢到资源后,发现初始库存为1,于是进入wait状态,释放锁资源,此时线程C_C抢到资源,执行number--,输出C_C:0,然后唤醒所有线程,此时P_B抢到资源,发现number=0,于是执行number++,然后输出P_B:1,然后释放锁,唤醒其他线程,此时CPU转给了P_A,线程P_A先加载上下文,不会再去进行if判断,因为之前判断过了,于是执行number++,输出了P_A:2。问题就出在这个if,所以在同步方法的flag判断是否执行时,杜绝使用if,一律使用while。当使用了while后,当线程P_A加载完上下文继续执行时,会再执行一遍判断,只有当while循环条件不成立时,才会执行后续代码。

      在这里再提一下notify和notifyAll的区别:当有多个线程时,notify会随机唤醒一个线程,被唤醒的线程百分百获得资源,但是具体唤醒哪一个是不确定的。而是用notifyAll时,会唤醒其实所有线程,所有线程再次抢占同步资源,谁抢到谁执行。

      案例2:

      上面的案例只是简答演示了,可以通过定义flag的方式,控制线程的执行。但是涉及到更加复杂情况时,还可以使用更加优秀的解决办法:组合使用Lock和Condition

    public class LockDemo{
        
        public static void main(String[] args) throws Exception {
            Shop shop = new Shop();
            
            new Thread(() -> {
                    try {
                        for (int i = 0; i < 10; i++) {
                            shop.superproduce();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
            },"P_A").start();
            new Thread(() -> {
                    try {
                        for (int i = 0; i < 10; i++) {
                            shop.produce();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                }
            },"P_B").start();
            new Thread(() -> {
                    try {
                        for (int i = 0; i < 10; i++) {
                            shop.consume();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
            },"C_C").start();
        }
    }
    class Shop{
        int number = 0;
        Lock lock = new ReentrantLock();
        Condition c1 = lock.newCondition();
        Condition c2 = lock.newCondition();
        Condition c3 = lock.newCondition();
        
        public void superproduce() throws InterruptedException{
            lock.lock();
            try {
                while(number != 0) {
                    c1.await();
                }
                number+=2;
                System.out.println(Thread.currentThread().getName()+": "+number);
                c2.signal();
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
            
        }
        
        public void produce() throws InterruptedException{
            lock.lock();
            try {
                while(number != 2) {
                    c2.await();
                }
                number++;
                System.out.println(Thread.currentThread().getName()+": "+number);
                c3.signal();
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
        
        public void consume() throws InterruptedException{
            lock.lock();
            try {
                while(number != 3) {
                    c3.await();
                }
                number-=3;
                System.out.println(Thread.currentThread().getName()+": "+number);
                c1.signal();
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }

      本案例大概意思为:当库存为0时,加快生产,当库存为2时,普通生产,当库存为3时,提供给消费者消费,轮番十次。由于初始初始状态为0,就算是P_B和C_C线程先抢到了资源,由于条件不符合,也只能将执行权交给P_A,当P_A执行完成后,标记线程P_B的执行。

      以此类推

     

      

  • 相关阅读:
    PHP的MySQL扩展:PHP访问MySQL的常用扩展函数
    PHP的MySQL扩展:MySQL数据库概述
    JQuery笔记:JQuery和JavaScript的联系与区别
    《千与千寻》给读者带来了什么?
    Canvas入门(3):图像处理和绘制文字
    Canvas入门(2):图形渐变和图像形变换
    Canvas入门(1):绘制矩形、圆、直线、曲线等基本图形
    python第三十二天----6.3作业中…………
    python第三十一天-----类的封装、继承,多态.....
    python第三十天-类
  • 原文地址:https://www.cnblogs.com/superlsj/p/11655580.html
Copyright © 2011-2022 走看看