zoukankan      html  css  js  c++  java
  • 线程应用:(七)Lock & Condition同步通信

    一、线程锁:Lock 代替synchronized

      Lock的作用类似于传统线程模型中的synchronized,更体现面向对象的思想,两个线程执行的代码片段要实现同步互斥的效果,必须要用同一个Lock对象。

    1)ReentrantLock

    //用Lock替换synchronized互斥
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ThreadTest1 {
        public static void main(String[] args) {
            new ThreadTest1().init();
        }
    
        //起了两个线程,一个一直输出"AAAAAA",一个一直输出"BBBBBB",但调用方法的对象是同一个
        public void init(){
            final Outputer outputer = new Outputer();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        outputer.output("AAAAAA");
                    }
                }
            }).start();
            
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        outputer.output("BBBBBB");
                    }
                }
            }).start();
        }
    }
    
    class Outputer {
        Lock lock = new ReentrantLock();    //这里两个线程调用方法的是同一个对象,所以是用的同一把锁
        public void output(String name){
            //synchronized (this) {
            lock.lock();    
            try{
                int len = name.length();
                for(int i=0;i<len;i++){
                    System.out.print(name.charAt(i));
                }
                System.out.println("");
            }finally{
                lock.unlock();    //无论如何都要释放锁
            }
            //}
        }
    }

      锁中的代码出现异常可能会出现死锁,最好finally释放锁。

    2)读写锁 ReentrantReadWriteLock

      分为读锁和写锁,使用读写锁可以提高效率,还能实现互斥。ReentrantLock是互斥锁,一个线程获得后,别的线程无论读写都不允许。

      多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥。

    规则:
    读中可以有读,
    读中不能有写,
    写中不能有读,写中不能有写。

      只是读的时候不用互斥,用读锁即可,不用synchronized。

    /**
     * 起3个线程不停写,3个线程不停读
     */
    public class ReadWriteLockTest {
        public static void main(String[] args) {
            final Queue q = new Queue();
            for(int i=0;i<3;i++){
                new Thread(){
                    @Override
                    public void run() {
                        while(true){
                            q.get();
                        }
                    }
                }.start();
                
                new Thread(){
                    @Override
                    public void run() {
                        while(true){
                            q.put(new Random().nextInt(10000));;
                        }
                    }
                }.start();
            }
        }
    }
    
    /**
     *读时可以有读,读时不能有写
     *写时不能有读,也不能有写。
     */
    class Queue {
        private Object data = null;  //共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据
        private ReadWriteLock rw1 = new ReentrantReadWriteLock();    //创建一个读写锁
        
        public void get() {
            rw1.readLock().lock();  //从读写锁中获得读锁
            try {
                System.out.println(Thread.currentThread().getName() + "开始读数据");
                Thread.sleep((long)(Math.random()*1000));
                System.out.println(Thread.currentThread().getName() + "已读完数据" + data);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                rw1.readLock().unlock();  //释放读锁
            }
        }
        
        public void put(Object data) {
            rw1.writeLock().lock();  //获得写锁
            try {
                System.out.println(Thread.currentThread().getName() + "开始写数据");
                Thread.sleep((long)(Math.random()*1000));
                this.data = data;
                System.out.println(Thread.currentThread().getName() + "已写数据" + data);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                rw1.writeLock().unlock();  //释放写锁
            }
        }
    }

      利用读写锁来模拟缓存案例。(结合到单例模式下)

    /**
     * 模拟缓存
     * 读和写间、写和写间互斥,又可以并发的读,性能高
     */
    public class CacheDemo {
        private Map<String, Object> cache = new HashMap<String, Object>();
        private ReadWriteLock rw1 = new ReentrantReadWriteLock();    //读写锁
        
        public Object getData(String key){
            rw1.readLock().lock();    //读1,多个读时应该要可以并发,不会造成数据的破坏
            Object value = null;
            try {
                value = cache.get(key);
                if(value == null){    //如果缓存中没数据,要重新赋值,要先写锁
                    rw1.readLock().unlock(); //先释放读1
                    rw1.writeLock().lock();    //写2,如果有多个线程到这一步,也只有一个能获得写锁
                    try{
                        if(value==null){ //赋值前再判断一次
                            value = "aaa";  //模拟取数据库取数据
                        }
                    } finally {
                        rw1.writeLock().unlock();  //写2
                    }
                    rw1.readLock().lock();  //读3
                }
            } finally {
                rw1.readLock().unlock();  //读3
            }
            return value;
        }
    }

    二、Condition代替wait/notify实现同步通信

      waitnotify必须和synchronized一起用,并且synchronized和wait、notify要用同一个锁对象。Condition可以用来代替wait、notify实现同步通信,Condition是在Lock对象的基础上使用的。

      使用Lock、Condition代替wait、notify、synchronized,案例代码如下。

    public class ConditionTest {
        public static void main(String[] args) throws InterruptedException {
            final Business business = new Business();
            
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i=1;i<=50;i++){        //循环50轮
                        try {
                            business.forSub(i);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
            
            for(int i=1;i<=50;i++){        //循环50轮
                business.forMain(i);
            }
        }
    }
    
    class Business {
        Lock lock = new ReentrantLock();    //用Lock代替synchronized
        private boolean subGo = true;
        Condition condition = lock.newCondition();    //Condition基于锁之上
        
        public void forSub(int i) throws InterruptedException{
            lock.lock();
            try{
                while(!subGo){    //防止虚假唤醒
                    //this.wait();
                    condition.await();     //别写成condition.wait();,是有区别的
                }
                for(int j=1;j<=10;j++){
                    System.out.println("subThread of, 第"+i+"轮, 序列为:"+j);
                }
                subGo = false;
                condition.signal();    //this.notify();
            }finally{
                lock.unlock();
            }
        }
        
        public void forMain(int i) throws InterruptedException{
            lock.lock();
            try{
                while(subGo){
                    condition.await();    //this.wait();
                }
                for(int j=1;j<=100;j++){
                    System.out.println("mainThread of, 第"+i+"轮, 序列为:"+j);
                }
                subGo = true;
                condition.signal();    //this.notify();
            }finally{
                lock.unlock();
            }
        }
    }

      一个锁内部可以有多个Condition,可实现多路等待和唤醒。

      只用一个Condition的不足:假如有5个线程用来存馒头,1个线程用来取馒头,当笼屉馒头满的时候,5个存的线程等待,当1个取的线程取了一个馒头后,唤醒了5个存线程中的一个继续存,存完后,本来应该要唤醒取的线程,但如果这里只有1个Condition,就不能进行区分,可能会唤醒另外4个存线程中的一个。

      当有三个线程要按顺序交替进行,代码如下。

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ThreeConditionTest {
        public static void main(String[] args) throws InterruptedException {
            final ConditionBusiness business = new ConditionBusiness();
            
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i=1;i<=50;i++){        //循环50轮
                        try {
                            business.sub2(i);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
            
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i=1;i<=50;i++){        //循环50轮
                        try {
                            business.sub3(i);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
            
            for(int i=1;i<=50;i++){        //循环50轮
                business.forMain(i);
            }
        }
    }
    
    //用于三个线程的同步通信,主线程-sub2-sub3的顺序交替进行
    class ConditionBusiness {
        Lock lock = new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Condition condition3 = lock.newCondition();
        private int flag = 1;    //设定1先走
        
        public void forMain(int i) throws InterruptedException{
            lock.lock();
            try{
                while(flag != 1){
                    condition1.await();
                }
                for(int j=1;j<=10;j++){
                    System.out.println("main, 第"+i+"轮, 序列为:"+j);
                }
                flag = 2;
                condition2.signal();    //线程1走完唤醒线程2
            }finally{
                lock.unlock();
            }
        }
        
        public void sub2(int i) throws InterruptedException{
            lock.lock();
            try{
                while(flag != 2){
                    condition2.await();
                }
                for(int j=1;j<=10;j++){
                    System.out.println("sub2, 第"+i+"轮, 序列为:"+j);
                }
                flag = 3;
                condition3.signal();    //线程2走完唤醒线程3
            }finally{
                lock.unlock();
            }
        }
        
        public void sub3(int i) throws InterruptedException{
            lock.lock();
            try{
                while(flag != 3){
                    condition3.await();
                }
                for(int j=1;j<=10;j++){
                    System.out.println("sub3, 第"+i+"轮, 序列为:"+j);
                }
                flag = 1;
                condition1.signal();    //线程3走完唤醒线程1
            }finally{
                lock.unlock();
            }
        }
    }
  • 相关阅读:
    ATOMac
    基于Python3 + appium的Ui自动化测试框架
    记 被难到的第一个算法题
    Requests发Post请求data里面嵌套字典
    Struts,Sping和Spirng MVC等框架分析
    雷军的留名,不是以程序员身份
    你有考虑过如果不做程序员,你会从事什么职业吗?或者你现在正在发展什么第二职业?
    java中的运算运算符 与或非(转)
    记录常用函数
    SQLServer记录常用函数(转)
  • 原文地址:https://www.cnblogs.com/zjxiang/p/9432809.html
Copyright © 2011-2022 走看看