首页 > 编程 > Java > 正文

Java Note: 多线程的同步(互斥锁)的方法对比,信号量锁,读写锁的实现,生产者-消费者模式的实现

2019-11-06 07:24:16
字体:
来源:转载
供稿:网友

本文摘录自:http://blog.csdn.net/ns_code/article/details/17487337

java 5中引入了新的锁机制——java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作。Lock接口有3个实现它的类:ReentrantLock、ReetrantReadWriteLock.ReadLock和ReetrantReadWriteLock.WriteLock,即重入锁、读锁和写锁。

所以,读写锁在Java中的实现是用ReentranLock实现的。

ReentranLock与synchronized比较不同的:在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。synchronize代表一种悲观的并发策略,这种同步又称为阻塞同步,它属于一种悲观的并发策略,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而ReentranLock是基于冲突检测的乐观并发策略,通俗地讲就是先进性操作,如果没有其他线程争用共享数据,那操作就成功了,如果共享数据被争用,产生了冲突,那就再进行其他的补偿措施(最常见的补偿措施就是不断地重拾,直到试成功为止)。

相同的:

基本语法上,ReentrantLock与synchronized很相似,它们都具备一样的线程重入特性。

同时,ReentrantLock有三个高级功能:

1、等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情,它对处理执行时间非常上的同步块很有帮助。而在等待由synchronized产生的互斥锁时,会一直阻塞,是不能被中断的。2、可实现公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待,而非公平锁则不保证这点,在锁释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁时非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过构造方法ReentrantLock(ture)来要求使用公平锁。公平锁确保下一个进入临界区的线程是最先开始等待的线程。3、锁可以绑定多个条件:ReentrantLock对象可以同时绑定多个Condition对象(名曰:条件变量或条件队列),而在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含条件,但如果要和多于一个的条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无需这么做,只需要多次调用newCondition()方法(方法返回一个条件锁,可以调用await()和signal和signalAll等方法)即可。而且我们还可以通过绑定Condition对象来判断当前线程通知的是哪些线程(即与Condition对象绑定在一起的其他线程)。

Java的读写锁是通过如下的方式实现的:

Java 5中提供了读写锁,它将读锁和写锁分离,使得读读操作不互斥,获取读锁和写锁的一般形式如下:

ReadWriteLock rwl = new ReentrantReadWriteLock();      rwl.writeLock().lock()  //获取写锁  rwl.readLock().lock()  //获取读锁  

用读锁来锁定读操作,用写锁来锁定写操作,这样写操作和写操作之间会互斥,读操作和写操作之间会互斥,但读操作和读操作就不会互斥。

Java的信号量锁是通过Semaphore类实现的:

Semaphore当前在多线程环境下被扩放使用,操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,而 release() 释放一个许可。

下面这个例子来自:http://www.cnblogs.com/whgw/archive/2011/09/29/2195555.html

使用线程池描述了Semaphore的使用:

// 线程池ExecutorService exec = Executors.newCachedThreadPool();// 只能5个线程同时访问final Semaphore semp = new Semaphore(5);// 模拟20个客户端访问for (int index = 0; index < 20; index++) {      final int NO = index;              Runnable run = new Runnable() {                 public void run() {                    try {                     // 获取许可                    semp.acquire();                    System.out.PRintln("accessing: " + NO);                    Thread.sleep((long) (Math.random() * 10000));                    // 访问完后,释放                    semp.release();                    System.out.println("-----------------"+semp.availablePermits());            } catch (InterruptedException e) {                    e.printStackTrace();            }                        }              };      exec.execute(run);}// 退出线程池exec.shutdown();生产者-消费者模式的最简单实现(利用Object的notify()和wait()方法):

wait() / nofity()方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。wait()方法:当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等等状态,让其他线程执行。notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。

代码来源:http://blog.csdn.net/monkey_d_meng/article/details/6251879

public class Storage  {      // 仓库最大存储量      private final int MAX_SIZE = 100;        // 仓库存储的载体      private LinkedList<Object> list = new LinkedList<Object>();        // 生产num个产品      public void produce(int num)      {          // 同步代码段          synchronized (list)          {              // 如果仓库剩余容量不足              while (list.size() + num > MAX_SIZE)              {                  System.out.println("【要生产的产品数量】:" + num + "/t【库存量】:"                          + list.size() + "/t暂时不能执行生产任务!");                  try                  {                      // 由于条件不满足,生产阻塞                      list.wait();                  }                  catch (InterruptedException e)                  {                      e.printStackTrace();                  }              }                // 生产条件满足情况下,生产num个产品              for (int i = 1; i <= num; ++i)              {                  list.add(new Object());              }                System.out.println("【已经生产产品数】:" + num + "/t【现仓储量为】:" + list.size());                list.notifyAll();          }      }        // 消费num个产品      public void consume(int num)      {          // 同步代码段          synchronized (list)          {              // 如果仓库存储量不足              while (list.size() < num)              {                  System.out.println("【要消费的产品数量】:" + num + "/t【库存量】:"                          + list.size() + "/t暂时不能执行生产任务!");                  try                  {                      // 由于条件不满足,消费阻塞                      list.wait();                  }                  catch (InterruptedException e)                  {                      e.printStackTrace();                  }              }                // 消费条件满足情况下,消费num个产品              for (int i = 1; i <= num; ++i)              {                  list.remove();              }                System.out.println("【已经消费产品数】:" + num + "/t【现仓储量为】:" + list.size());                list.notifyAll();          }      }  }生产者-消费者较为复杂的实现(条件锁):

Java 5之后,我们可以用Reentrantlock锁配合Condition对象上的await()和signal()或signalAll()方法来实现线程间协作。在ReentrantLock对象上newCondition()可以得到一个Condition对象,可以通过在Condition上调用await()方法来挂起一个任务(线程),通过在Condition上调用signal()来通知任务,从而唤醒一个任务,或者调用signalAll()来唤醒所有在这个Condition上被其自身挂起的任务。另外,如果使用了公平锁,signalAll()的与Condition关联的所有任务将以FIFO队列的形式获取锁,如果没有使用公平锁,则获取锁的任务是随机的,这样我们便可以更好地控制处在await状态的任务获取锁的顺序。与notifyAll()相比,signalAll()是更安全的方式。

理论上来说,条件所的await()和signal()是可以完全替代wait()和notify()的。而且由于可以实现公平锁,比wait()和notify()更优秀。

import java.util.concurrent.*;  import java.util.concurrent.locks.*;    class Info{ // 定义信息类      private String name = "name";//定义name属性,为了与下面set的name属性区别开      private String content = "content" ;// 定义content属性,为了与下面set的content属性区别开      private boolean flag = true ;   // 设置标志位,初始时先生产      private Lock lock = new ReentrantLock();        private Condition condition = lock.newCondition(); //产生一个Condition对象      public  void set(String name,String content){          lock.lock();          try{              while(!flag){                  condition.await() ;              }              this.setName(name) ;    // 设置名称              Thread.sleep(300) ;              this.setContent(content) ;  // 设置内容              flag  = false ; // 改变标志位,表示可以取走              condition.signal();          }catch(InterruptedException e){              e.printStackTrace() ;          }finally{              lock.unlock();          }      }        public void get(){          lock.lock();          try{              while(flag){                  condition.await() ;              }                 Thread.sleep(300) ;              System.out.println(this.getName() +                   " --> " + this.getContent()) ;              flag  = true ;  // 改变标志位,表示可以生产              condition.signal();          }catch(InterruptedException e){              e.printStackTrace() ;          }finally{              lock.unlock();          }      }  }


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表