JAVA多线程并发库高级应用 (三)

11.java5的线程锁技术

java.util.concurrent.locks         为锁和等待条件提供一个框架的接口和类,

接口摘要

Condition

Condition 将 Object 监视器方法(waitnotifynotifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。

Lock

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

ReadWriteLock

ReadWriteLock 维护了一对相关的,一个用于只读操作,另一个用于写入操作。

类摘要

AbstractOwnableSynchronizer

可以由线程以独占方式拥有的同步器。

AbstractQueuedLongSynchronizer

以 long 形式维护同步状态的一个 AbstractQueuedSynchronizer 版本。

AbstractQueuedSynchronizer

为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。

LockSupport

用来创建锁和其他同步类的基本线程阻塞原语。

ReentrantLock

一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

ReentrantReadWriteLock

支持与 ReentrantLock 类似语义的 ReadWriteLock 实现。

ReentrantReadWriteLock.ReadLock

ReentrantReadWriteLock.readLock() 方法返回的锁。

ReentrantReadWriteLock.WriteLock

ReentrantReadWriteLock.writeLock() 方法返回的锁。

   

       Lock比传统线程模型中的synchronized更加面向对象,锁本身也是一个对象,两个线程执行的代码要实现同步互斥效果,就要使用同一个锁对象。锁要上在要操作的资源类的内部方法中,而不是线程代码中。

public interface Lock

所有已知实现类:

ReentrantLock, ReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock

随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:

    Lock l = ...;

    l.lock();

    try {

        // access the resource protected by this lock

    } finally {

        l.unlock();

    }

锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。

方法摘要

 void

lock()           获取锁。

 void

lockInterruptibly()           如果当前线程未被中断,则获取锁。

 Condition

newCondition()           返回绑定到此 Lock 实例的新 Condition 实例。

 boolean

tryLock()           仅在调用时锁为空闲状态才获取该锁。

 boolean

tryLock(long time, TimeUnit unit)           如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。

 void

unlock()           释放锁。

Lock与synchronized对比,打印字符串例子

 

 

12.java5读写锁技术的妙用

       读写锁,分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,由JVM控制。

ReentrantReadWriteLock

构造方法摘要

ReentrantReadWriteLock()           使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock。

ReentrantReadWriteLock(boolean fair)           使用给定的公平策略创建一个新的 ReentrantReadWriteLock。

 

方法摘要

protected  Thread

getOwner()           返回当前拥有写入锁的线程,如果没有这样的线程,则返回 null。

protected  Collection<Thread>

getQueuedReaderThreads()           返回一个 collection,它包含可能正在等待获取读取锁的线程。

protected  Collection<Thread>

getQueuedThreads()           返回一个 collection,它包含可能正在等待获取读取或写入锁的线程。

protected  Collection<Thread>

getQueuedWriterThreads()           返回一个 collection,它包含可能正在等待获取写入锁的线程。

 int

getQueueLength()           返回等待获取读取或写入锁的线程估计数目。

 int

getReadHoldCount()           查询当前线程在此锁上保持的重入读取锁数量。

 int

getReadLockCount()           查询为此锁保持的读取锁数量。

protected  Collection<Thread>

getWaitingThreads(Condition condition)           返回一个 collection,它包含可能正在等待与写入锁相关的给定条件的那些线程。

 int

getWaitQueueLength(Condition condition)           返回正等待与写入锁相关的给定条件的线程估计数目。

 int

getWriteHoldCount()           查询当前线程在此锁上保持的重入写入锁数量。

 boolean

hasQueuedThread(Thread thread)           查询是否给定线程正在等待获取读取或写入锁。

 boolean

hasQueuedThreads()           查询是否所有的线程正在等待获取读取或写入锁。

 boolean

hasWaiters(Condition condition)           查询是否有些线程正在等待与写入锁有关的给定条件。

 boolean

isFair()           如果此锁将公平性设置为 ture,则返回 true。

 boolean

isWriteLocked()           查询是否某个线程保持了写入锁。

 boolean

isWriteLockedByCurrentThread()           查询当前线程是否保持了写入锁。

 ReentrantReadWriteLock.ReadLock

readLock()           返回用于读取操作的锁。

 String

toString()           返回标识此锁及其锁状态的字符串。

 ReentrantReadWriteLock.WriteLock

writeLock()           返回用于写入操作的锁。

     

三个线程读数据,三个线程写数据示例:

可以同时读,读的时候不能写,不能同时写,写的时候不能读

读的时候上读锁,读完解锁;写的时候上写锁,写完解锁。注意finally解锁

package cn.itheima;

 

import java.util.Random;

importjava.util.concurrent.locks.ReadWriteLock;

importjava.util.concurrent.locks.ReentrantReadWriteLock;

 

public class ReadWriteLockDemo

{

       /**读写所使用

        * 三个线程读,三个线程写

        */

       publicstatic void main(String[] args)

       {

              //共享对象

              finalSource source = new Source();

              //创建线程

              for(int i=0; i<3; i++)

              {

                     //读

                     newThread(new Runnable()

                     {

                            publicvoid run()

                            {

                                   while(true)

                                          source.get();

                            }

                     }).start();

                     //写

                     newThread(new Runnable()

                     {

                            publicvoid run()

                            {

                                   while(true)

                                          source.put(newRandom().nextInt(999));

                            }

                     }).start();

              }

       }

 

       staticclass Source

       {

              //共享数据

              privateint data = 0;

              //要操作同一把锁上的读或写锁

              ReadWriteLock rwl =new ReentrantReadWriteLock();

             

              //读方法

              publicvoid get()

              {

                     //上读锁

                     rwl.readLock().lock();

                     try

                     {

                            //获取数据并输出

                            System.out.println("读——"+Thread.currentThread().getName()+"正在获取数据。。。");

                            try

                            {

                                   Thread.sleep(newRandom().nextInt(6)*1000);

                            }catch (InterruptedException e)

                            {

                                   e.printStackTrace();

                            }

                            System.out.println("读——"+Thread.currentThread().getName()+"获取到的数据:"+data);

                     }finally

                     {

                            //解锁

                            rwl.readLock().unlock();

                     }                  

              }

              //写方法

              publicvoid put(int data)

              {

                     //上写锁

                     rwl.writeLock().lock();

                     try

                     {

                            //提示信息

                            System.out.println("写——"+Thread.currentThread().getName()+"正在改写数据。。。");

                            try

                            {

                                   Thread.sleep(newRandom().nextInt(6)*1000);

                            }catch (InterruptedException e)

                            {

                                   e.printStackTrace();

                            }

                            this.data= data;

                            System.out.println("写——"+Thread.currentThread().getName()+"已将数据改写为:"+data);

                     }finally

                     {

                            //解锁

                            rwl.writeLock().unlock();

                     }                  

              }

       }

}

JDK帮助文档中的示例用法。下面的代码展示了如何利用重入来执行升级缓存后的锁降级(为简单起见,省略了异常处理):

 class CachedData {

   Object data;
   volatile boolean cacheValid;    数据有没有标记
   ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
   void processCachedData() {处理数据
     rwl.readLock().lock();先上读锁
     if (!cacheValid) {如果数据不存在
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();准备写数据,需先解除读锁
        rwl.writeLock().lock();上写锁
        // Recheck state because another thread might have acquired
        //   write lock and changed state before we did.
        if (!cacheValid) {再次检查数据是否存在,防止其他线程已经存入数据
          data = ...
          cacheValid = true;写好数据,改变标记
        }
        // Downgrade by acquiring read lock before releasing write lock
        准备释放写锁,数据存在了,释放后就要使用数据,恢复产生数据前的读锁状态
         rwl.readLock().lock();
        rwl.writeLock().unlock(); // Unlock write, still hold read
     }
 
     use(data);存在直接使用数据
     rwl.readLock().unlock();解除读锁
   }
 }

 

面试题:设计一个缓存系统

       缓存系统:你要取数据,需调用我的public ObjectgetData(String key)方法,我要检查我内部有没有这个数据,如果有就直接返回,如果没有,就从数据库中查找这个数,查到后将这个数据存入我内部的存储器中,下次再有人来要这个数据,我就直接返回这个数不用再到数据库中找了。              你要取数据不要找数据库,来找我。

class CachedSystem

{     缓存系统的存储器

       privateMap<String, Object> cache = new HashMap<String, Object>();

       取数据方法    可能有多个线程来取数据,没有数据的话又会去数据库查询,需要互斥

       publicsynchronizedObject get(String key)

       {     先查询内部存储器中有没有要的值

              Objectvalue = cache.get(key);

              if(value==null)如果没有,就去数据库中查询,并将查到的结果存入内部存储器中

              {

                     value= “aaaa”; 实际代码是查询后的结果 queryDB(key)

                     cache.put(key,value);

}

return value;

}

}

上面的代码每次只能有一个线程来查询,但只有写的时候才需要互斥,修改如下

来一个读写锁

ReadWriteLockrwl = new ReentrantReadWriteLock();

public Object get(String key)

{    

       上读锁

       rwl.readLock().lock();

先查询内部存储器中有没有要的值

       Objectvalue = cache.get(key);

       if(value==null)如果没有,就去数据库中查询,并将查到的结果存入内部存储器中

       {

              释放读锁 上写锁

              rwl.readLock().unlock();

              rwl.writeLock().lock();

              if(value==null)再次进行判断,防止多个写线程堵在这个地方重复写

              {

                     value = “aaaa”;

                     cache.put(key, value);

              }

设置完成 释放写锁,恢复读写状态

              rwl.readLock().lock();

              rwl.writeLock().unlock();

}

释放读锁

rwl.readLock().unlock();

return value;                                                                  注意:try finallyunlock

}

 

13.java5条件阻塞Condition的应用

       Condition的功能类似在传统线程技术中的Object.wait()和Object.natify()的功能,传统线程技术实现的互斥只能一个线程单独干,不能说这个线程干完了通知另一个线程来干,Condition就是解决这个问题的,实现线程间的通信。比如CPU让小弟做事,小弟说我先歇着并通知大哥,大哥就开始做事。

public interface Condition

Condition 将 Object 监视器方法(waitnotifynotifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition()方法。

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

 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, takeptr, 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();

    }

   }

 }

使用方法:

Lock lock = new ReentrantLock();

Condition condition = lock.newCondition();

this.wait()àcondition.await()

this.notify()àcondition.signal()

注意:判断条件时用while防止虚假唤醒,等待在那里,唤醒后再进行判断,确认符合要求后再执行任务。

 

14.java5Semaphore同步工具

       Semaphore可以维护当前访问自身的线程个数,并且提供了同步机制。

       semaphore实现的功能类似于厕所里有5个坑,有10个人要上厕所,同时就只能有5个人占用,当5个人中 的任何一个让开后,其中在等待的另外5个人中又有一个可以占用了。

java.util.concurrent.Semaphore

一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个acquire(),然后再获取该许可。每个 release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。

Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问:

 class Pool {

  private static final int MAX_AVAILABLE = 100;

  private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);

  public Object getItem() throws InterruptedException {

    available.acquire();

    return getNextAvailableItem();

   }

  public void putItem(Object x) {

    if (markAsUnused(x))

      available.release();

   }

   //Not a particularly efficient data structure; just for demo

  protected Object[] items = ... whatever kinds of items being managed

  protected boolean[] used = new boolean[MAX_AVAILABLE];

 

  protected synchronized Object getNextAvailableItem() {

    for (int i = 0; i < MAX_AVAILABLE; ++i) {

      if (!used[i]) {

         used[i] = true;

         return items[i];

      }

    }

    return null; // not reached

   }

  protected synchronized boolean markAsUnused(Object item) {

    for (int i = 0; i < MAX_AVAILABLE; ++i) {

      if (item == items[i]) {

         if (used[i]) {

           used[i] = false;

           return true;

         } else

           return false;

      }

    }

    return false;

   }

 }

获得一项前,每个线程必须从信号量获取许可,从而保证可以使用该项。该线程结束后,将项返回到池中并将许可返回到该信号量,从而允许其他线程获取该项。注意,调用acquire()时无法保持同步锁,因为这会阻止将项返回到池中。信号量封装所需的同步,以限制对池的访问,这同维持该池本身一致性所需的同步是分开的。

构造方法摘要

Semaphore(int permits)           创建具有给定的许可数和非公平的公平设置的 Semaphore。

Semaphore(int permits, boolean fair)           创建具有给定的许可数和给定的公平设置的 Semaphore。

方法摘要

 void

acquire()           从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断

 void

acquire(int permits)           从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断

 void

acquireUninterruptibly()           从此信号量中获取许可,在有可用的许可前将其阻塞。

 void

acquireUninterruptibly(int permits)           从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。

 int

availablePermits()           返回此信号量中当前可用的许可数。

 int

drainPermits()           获取并返回立即可用的所有许可。

protected  Collection<Thread>

getQueuedThreads()           返回一个 collection,包含可能等待获取的线程。

 int

getQueueLength()           返回正在等待获取的线程的估计数目。

 boolean

hasQueuedThreads()           查询是否有线程正在等待获取。

 boolean

isFair()           如果此信号量的公平设置为 true,则返回 true。

protected  void

reducePermits(int reduction)           根据指定的缩减量减小可用许可的数目。

 void

release()           释放一个许可,将其返回给信号量。

 void

release(int permits)           释放给定数目的许可,将其返回到信号量。

 String

toString()           返回标识此信号量的字符串,以及信号量的状态。

 boolean

tryAcquire()          仅在调用时此信号量存在一个可用许可,才从信号量获取许可。

 boolean

tryAcquire(int permits)           仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。

 boolean

tryAcquire(int permits, long timeout, TimeUnit unit)           如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。

 boolean

tryAcquire(long timeout, TimeUnit unit)           如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断,则从此信号量获取一个许可。

     

示例:3个坑 10个人

厕所,有多少人都能装,线程数动态变化,来一个人产生一个线程

ExecutorService service =Exccutors.newCachedThreadPool();

final Semaphore sp = new Semaphore(3);厕所中坑的个数  指定只有3个

       3个坑,来了5个人,有2个人要等,其中有一个办完事走了,等待的2个哪个先上呢?默认的构造方法不管,谁抢到了谁上。newSemaphore(3, true)就可以保证先来的先上。

将坑的个数设置为1就可以达到互斥效果,每次只能有一个线程运行

for (int i=0; i<10; i++)来了10个人

{人的任务  抢坑

       Runnablerunnable = new Runnable()

       {

       public void run()

       {

       sp.acquire();抢坑了 会抛中断异常

}有人占住坑了,给出提示

SOP(currentThreadName+进入,当前已有(3-sp.availablePermits())个人了)

Thread.sleep(5000)蹲坑办事

办完事打声招呼

SOP(ThreadName即将离开)

释放坑的占有权

sp.release();

SOP(ThreadName已经走了,还有sp.availablePermits()个坑可用)

}

       开始任务吧

       service.execute(runnable)

}

传统互斥只能内部释放锁this.unlock(),进去this.lock()晕倒了别人就没法进去了;用信号灯可以外部释放,其他线程可以释放再获取sp.release()  sp.acquire()

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值