• 【Java线程】Lock、Condition


    http://www.infoq.com/cn/articles/java-memory-model-5  深入理解Java内存模型(五)——锁

    http://www.ibm.com/developerworks/cn/java/j-jtp10264/  Java 理论与实践: JDK 5.0 中更灵活、更具可伸缩性的锁定机制

    http://blog.csdn.net/ghsau/article/details/7481142


    Lock与synchronized

    Lock可以实现synchronized的相同功能,它能以更优雅的方式处理线程同步问题。例:

    class Outputter1 {  
        private Lock lock = new ReentrantLock();// 锁对象  
    
        public void output(String name) {         
            lock.lock();      // 得到锁  
    
            try {  
                for(int i = 0; i < name.length(); i++) {  
                    System.out.print(name.charAt(i));  
                }  
            } finally {  
                lock.unlock();// 释放锁  
            }  
        }  
    }  


    需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而是用Lock需要我们 手动释放锁,所以为了保证锁最终被释放(发生异常情况), 要把互斥区放在try内,释放锁放在finally内。


    上例中展示的是和synchronized相同的功能,那Lock的优势在哪里?

    例如一个类对其内部共享数据data提供了get()和set()方法,如果用synchronized,则代码如下:

    class syncData {      
        private int data;// 共享数据      
        public synchronized void set(int data) {  
            System.out.println(Thread.currentThread().getName() + "准备写入数据");  
            try {  
                Thread.sleep(20);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            this.data = data;  
            System.out.println(Thread.currentThread().getName() + "写入" + this.data);  
        }     
        public synchronized  void get() {  
            System.out.println(Thread.currentThread().getName() + "准备读取数据");  
            try {  
                Thread.sleep(20);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            System.out.println(Thread.currentThread().getName() + "读取" + this.data);  
        }  
    }  


    然后写个测试类来用多个线程分别读写这个共享数据:

    public static void main(String[] args) {  
    //        final Data data = new Data();  
              final syncData data = new syncData();  
    //        final RwLockData data = new RwLockData();  
            
            //写入
            for (int i = 0; i < 3; i++) {  
                Thread t = new Thread(new Runnable() {  
                    @Override
    		public void run() {  
                        for (int j = 0; j < 5; j++) {  
                            data.set(new Random().nextInt(30));  
                        }  
                    }  
                });
                t.setName("Thread-W" + i);
                t.start();
            }  
            //读取
            for (int i = 0; i < 3; i++) {  
            	Thread t = new Thread(new Runnable() {  
                    @Override
    		public void run() {  
                        for (int j = 0; j < 5; j++) {  
                            data.get();  
                        }  
                    }  
                });  
            	t.setName("Thread-R" + i);
            	t.start();
            }  
        }  


    运行结果:

    Thread-W0准备写入数据
    Thread-W0写入0
    Thread-W0准备写入数据
    Thread-W0写入1
    Thread-R1准备读取数据
    Thread-R1读取1
    Thread-R1准备读取数据
    Thread-R1读取1
    Thread-R1准备读取数据
    Thread-R1读取1
    Thread-R1准备读取数据
    Thread-R1读取1
    Thread-R1准备读取数据
    Thread-R1读取1
    Thread-R2准备读取数据
    Thread-R2读取1
    Thread-R2准备读取数据
    Thread-R2读取1
    Thread-R2准备读取数据
    Thread-R2读取1
    Thread-R2准备读取数据
    Thread-R2读取1
    Thread-R2准备读取数据
    Thread-R2读取1
    Thread-R0准备读取数据 //R0和R2可以同时读取,不应该互斥!
    Thread-R0读取1
    Thread-R0准备读取数据
    Thread-R0读取1
    Thread-R0准备读取数据
    Thread-R0读取1
    Thread-R0准备读取数据
    Thread-R0读取1
    Thread-R0准备读取数据
    Thread-R0读取1
    Thread-W1准备写入数据
    Thread-W1写入18
    Thread-W1准备写入数据
    Thread-W1写入16
    Thread-W1准备写入数据
    Thread-W1写入19
    Thread-W1准备写入数据
    Thread-W1写入21
    Thread-W1准备写入数据
    Thread-W1写入4
    Thread-W2准备写入数据
    Thread-W2写入10
    Thread-W2准备写入数据
    Thread-W2写入4
    Thread-W2准备写入数据
    Thread-W2写入1
    Thread-W2准备写入数据
    Thread-W2写入14
    Thread-W2准备写入数据
    Thread-W2写入2
    Thread-W0准备写入数据
    Thread-W0写入4
    Thread-W0准备写入数据
    Thread-W0写入20
    Thread-W0准备写入数据
    Thread-W0写入29
    

    读写锁ReadWriteLock

    现在一切都看起来很好!各个线程互不干扰!等等。。读取线程和写入线程互不干扰是正常的,但是两个读取线程是否需要互不干扰??

    对!读取线程不应该互斥!

    我们可以用读写锁ReadWriteLock实现:

    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;


        class Data {      
            private int data;// 共享数据  
            private ReadWriteLock rwl = new ReentrantReadWriteLock();     
            public void set(int data) {  
                rwl.writeLock().lock();// 取到写锁  
                try {  
                    System.out.println(Thread.currentThread().getName() + "准备写入数据");  
                    try {  
                        Thread.sleep(20);  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                    this.data = data;  
                    System.out.println(Thread.currentThread().getName() + "写入" + this.data);  
                } finally {  
                    rwl.writeLock().unlock();// 释放写锁  
                }  
            }     
    
            public void get() {  
                rwl.readLock().lock();// 取到读锁  
                try {  
                    System.out.println(Thread.currentThread().getName() + "准备读取数据");  
                    try {  
                        Thread.sleep(20);  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                    System.out.println(Thread.currentThread().getName() + "读取" + this.data);  
                } finally {  
                    rwl.readLock().unlock();// 释放读锁  
                }  
            }  
        }  

    测试结果:

    Thread-W1准备写入数据
    Thread-W1写入9
    Thread-W1准备写入数据
    Thread-W1写入24
    Thread-W1准备写入数据
    Thread-W1写入12
    Thread-W0准备写入数据
    Thread-W0写入22
    Thread-W0准备写入数据
    Thread-W0写入15
    Thread-W0准备写入数据
    Thread-W0写入6
    Thread-W0准备写入数据
    Thread-W0写入13
    Thread-W0准备写入数据
    Thread-W0写入0
    Thread-W2准备写入数据
    Thread-W2写入23
    Thread-W2准备写入数据
    Thread-W2写入24
    Thread-W2准备写入数据
    Thread-W2写入24
    Thread-W2准备写入数据
    Thread-W2写入17
    Thread-W2准备写入数据
    Thread-W2写入11
    Thread-R2准备读取数据
    Thread-R1准备读取数据
    Thread-R0准备读取数据
    Thread-R0读取11
    Thread-R1读取11
    Thread-R2读取11
    Thread-W1准备写入数据
    Thread-W1写入18
    Thread-W1准备写入数据
    Thread-W1写入1
    Thread-R0准备读取数据
    Thread-R2准备读取数据
    Thread-R1准备读取数据
    Thread-R2读取1
    Thread-R2准备读取数据
    Thread-R1读取1
    Thread-R0读取1
    Thread-R1准备读取数据
    Thread-R0准备读取数据
    Thread-R0读取1
    Thread-R2读取1
    Thread-R2准备读取数据
    Thread-R1读取1
    Thread-R0准备读取数据
    Thread-R1准备读取数据
    Thread-R0读取1
    Thread-R2读取1
    Thread-R1读取1
    Thread-R0准备读取数据
    Thread-R1准备读取数据
    Thread-R2准备读取数据
    Thread-R1读取1
    Thread-R2读取1
    Thread-R0读取1
    


    与互斥锁定相比,读-写锁定允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)


    从理论上讲,与互斥锁定相比,使用读-写锁定所允许的并发性增强将带来更大的性能提高。

    在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。——例如,某个最初用数据填充并且之后不经常对其进行修改的 collection,因为经常对其进行搜索(比如搜索某种目录),所以这样的 collection 是使用读-写锁定的理想候选者。


    线程间通信Condition

    Condition可以替代传统的线程间通信,await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()。

    传统线程的通信方式,Condition都可以实现。

    注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。


    Condition的强大之处在于它可以为多个线程间建立不同的Condition

    看JDK文档中的一个例子:假定有一个绑定的缓冲区,它支持 puttake 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存put 线程和take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个Condition 实例来做到这一点。

    ——其实就是java.util.concurrent.ArrayBlockingQueue的功能

     class BoundedBuffer {
       final Lock lock = new ReentrantLock();          //锁对象
       final Condition notFull  = lock.newCondition(); //写线程锁
       final Condition notEmpty = lock.newCondition(); //读线程锁
    
       final Object[] items = new Object[100];//缓存队列
       int putptr;  //写索引
       int takeptr; //读索引
       int count;   //队列中数据数目
    
       //写
       public void put(Object x) throws InterruptedException {
         lock.lock(); //锁定
         try {
           // 如果队列满,则阻塞<写线程>
           while (count == items.length) {
             notFull.await(); 
           }
           // 写入队列,并更新写索引
           items[putptr] = x; 
           if (++putptr == items.length) putptr = 0; 
           ++count;
    
           // 唤醒<读线程>
           notEmpty.signal(); 
         } finally { 
           lock.unlock();//解除锁定 
         } 
       }
    
       //读 
       public Object take() throws InterruptedException { 
         lock.lock(); //锁定 
         try {
           // 如果队列空,则阻塞<读线程>
           while (count == 0) {
              notEmpty.await();
           }
    
           //读取队列,并更新读索引
           Object x = items[takeptr]; 
           if (++takeptr == items.length) takeptr = 0;
           --count;
    
           // 唤醒<写线程>
           notFull.signal(); 
           return x; 
         } finally { 
           lock.unlock();//解除锁定 
         } 
       } 
    }


    优点:

    假设缓存队列中已经存满,那么阻塞的肯定是写线程,唤醒的肯定是读线程,相反,阻塞的肯定是读线程,唤醒的肯定是写线程。

    那么假设只有一个Condition会有什么效果呢?缓存队列中已经存满,这个Lock不知道唤醒的是读线程还是写线程了,如果唤醒的是读线程,皆大欢喜,如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这时又去唤醒,这样就浪费了很多时间。



  • 相关阅读:
    深入理解Node.js垃圾回收与内存管理
    【File System】Node.js中文件操作模块File System
    【事件流】事件冒泡和事件捕获
    undefined 和null的区别?
    localStorage实现登录注册功能
    解刨for循环
    react中嵌入高德地图并Marker标点
    react页面中嵌入地图,标识出某个地点,使用插件react-amap
    react中使用antd的List组件,以及下载文件,List隔行变色
    公众号页面数据处理
  • 原文地址:https://www.cnblogs.com/riskyer/p/3257994.html
Copyright © 2020-2023  润新知