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

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()

 

15.java5CyclicBarrier同步工具

       例如:组织人员(线程)郊游,约定一个时间地点(路障),人员陆续到达地点,等所有人员全部到达,开始到公园各玩各的,再到约定时间去食堂吃饭,等所有人到齐开饭……

java.util.concurrent.CyclicBarrier

一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环的 barrier。

CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作很有用。

构造方法摘要

CyclicBarrier(int parties)           创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。

CyclicBarrier(int parties, Runnable barrierAction)           创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。

方法摘要

 int

await()     在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。

 int

await(long timeout, TimeUnit unit)           在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。

 int

getNumberWaiting()           返回当前在屏障处等待的参与者数目。

 int

getParties()           返回要求启动此 barrier 的参与者数目。

 boolean

isBroken()           查询此屏障是否处于损坏状态。

 void

reset()           将屏障重置为其初始状态。

例:

ExecutorService service =Executors.newCachedThreadPool();

final CyclicBarrier cb = newCyclicBarrier(3);  约定3个人

for (int i=0; i<3; i++)产生3个人

{     每个人的任务

       Runnablerunnable = newRunnable()

       {

              publicvoid run()

              {     开始出发到目的地

       Thread.sleep((long)Math.random()*1000);

       SOP(ThreadName即将到达集合点1,

当前已有cb.getNumberWaiting()+1个

+ (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"))

       cb.await();到了其他人没来就等

       人到齐了再继续进行

       Thread.sleep((long)Math.random()*1000);

       SOP(ThreadName即将到达集合点2)

       cb.await();到了其他人没来就等

 

}

}

service.execute(runnable);

}

 

16.java5CountDownLatch同步工具

       好像倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当到达0时,所有等待者就开始执行。

       举例:多个运动员等待裁判命令:    裁判等所有运动员到齐后发布结果

代码示例:

ExecutorService service =Executors.newCachedThreadPool();

裁判发布命令的计数器,计数器为0,运动员就跑

final CountDownLatch cdOrder = new CountDownLatch(1);     

运动员跑到终点的计数器,为0裁判宣布结果

final CountDownLatch cdAnswer = newCountDownLatch(3);

产生3个运动员

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

{     运动员的任务

       Runnablerunnable = new Runnable(){

public void run()

{

       SOP(ThreadName准备接受命令)

       等待发布命令

       cdOrder.await();     计数器为0继续向下执行

       SOP(ThreadName已接受命令)    order计数器为0了

       Thread.sleep(Random);开始跑步

       cdAnswer.countDown();跑到终点了,计数器减1

}

};

       service.execute(runnable);运动员开始任务

}

Thread.sleep(1000)裁判休息一会 再发布命令

SOP(即将发布命令)

cdOrder.countDown();命令计数器置为0,发布命令

SOP(命令已经发布,等待结果)

cdAnswer.await(); 等待所有运动员,计数器为0 所有运动员到位

SOP(宣布结果)

 

java.util.concurrent.CountDownLatch

一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数 初始化 CountDownLatch。由于调用了 countDown()方法,所以在当前计数到达零之前,await方法会一直受阻塞。之后,会释放所有等待的线程,await的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier

CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown()的线程打开入口前,所有调用 await的线程都一直在入口处等待。用 N 初始化的CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。

CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await

构造方法摘要

CountDownLatch(int count)           构造一个用给定计数初始化的 CountDownLatch。

方法摘要

 void

await()           使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断

 boolean

await(long timeout, TimeUnit unit)           使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。

 void

countDown()           递减锁存器的计数,如果计数到达零,则释放所有等待的线程。

 long

getCount()           返回当前计数。

 String

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

 

17.java5Exchanger同步工具

       用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人会一直等待第二个人,直到第二个人拿着数据到来时,才能彼此交换数据。

举例:毒品交易  双方并不是同时到达,有先有后,只有都到达了,瞬间交换数据,各自飞

代码演示:

ExecutorService service =Executors.newCachedThreadPool();

final Exchanger exchanger = newExchanger();

       毒贩:

service.execute(new Runnable()

{     毒贩做的事

       public void run()

       {

       String(毒品) data1 = 毒品

       SOP(毒贩正在将data1换出去)

       Thread.sleep(Random)换的过程

       毒贩到位了,拿着毒品等待毒人接头,接头后就能换到钱了

       String data2 = (String)exchanger.exchange(data1);

       SOP(毒贩换到了钱:data2)

}

});

       毒人:

service.execute(new Runnable()

{     吸毒人做的事

       public void run()

       {

       String(钱) data1 = 钱

       SOP(毒人正在将data1换出去)

       Thread.sleep(Random)换的过程

       吸毒人到位了,拿着钱等待毒贩接头,接头后就能换到毒品了

       String data2 =(String)exchanger.exchange(data1);

       SOP(毒人换到了毒品:data2)

}

});

java.util.concurrent.Exchanger<V> V -可以交换的对象类型

可以在对中对元素进行配对和交换的线程的同步点。每个线程将条目上的某个方法呈现给exchange方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象。Exchanger 可能被视为 SynchronousQueue 的双向形式。Exchanger 可能在应用程序(比如遗传算法和管道设计)中很有用。

用法示例:以下是重点介绍的一个类,该类使用Exchanger 在线程间交换缓冲区,因此,在需要时,填充缓冲区的线程获取一个新腾空的缓冲区,并将填满的缓冲区传递给腾空缓冲区的线程。

class FillAndEmpty {

  Exchanger<DataBuffer> exchanger = newExchanger<DataBuffer>();

  DataBuffer initialEmptyBuffer = ... a made-up type

  DataBuffer initialFullBuffer = ...

  class FillingLoop implements Runnable {

    public void run() {

      DataBuffer currentBuffer = initialEmptyBuffer;

      try {

        while (currentBuffer != null) {

          addToBuffer(currentBuffer);

          if (currentBuffer.isFull())

            currentBuffer = exchanger.exchange(currentBuffer);

        }

      } catch (InterruptedException ex) { ... handle ... }

    }

   }

  class EmptyingLoop implements Runnable {

    public void run() {

      DataBuffer currentBuffer = initialFullBuffer;

      try {

         while (currentBuffer != null) {

          takeFromBuffer(currentBuffer);

          if (currentBuffer.isEmpty())

            currentBuffer = exchanger.exchange(currentBuffer);

        }

      } catch (InterruptedException ex) { ... handle ...}

     }

   }

  void start() {

    new Thread(new FillingLoop()).start();

    new Thread(new EmptyingLoop()).start();

   }

  }

构造方法摘要

Exchanger()           创建一个新的 Exchanger。

方法摘要

 V

exchange(V x)           等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。

 V

exchange(V x, long timeout, TimeUnit unit)           等待另一个线程到达此交换点(除非当前线程被中断,或者超出了指定的等待时间),然后将给定的对象传送给该线程,同时接收该线程的对象。

 

18.java5阻塞队列的应用

       队列包含固定长度的队列和不固定长度的队列,先进先出

固定长度的队列往里放数据,如果放满了还要放,阻塞式队列就会等待,直到有数据取出,空出位置后才继续放;非阻塞式队列不能等待就只能报错了。

       讲Condition时提到了阻塞队列的原理,Java中已经实现了阻塞队列ArrayBlockingQueue

BlockingQueue<E>       public interfaceBlockingQueue<E>extends Queue<E>

支持两个附加操作的Queue,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。

BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(null 或 false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:

 

抛出异常

特殊值

阻塞

超时

插入

add(e)

offer(e)

put(e)

offer(e, time, unit)

移除

remove()

poll()

take()

poll(time, unit)

检查

element()

peek()

不可用

不可用

BlockingQueue 不接受 null 元素。试图 add、put 或offer 一个 null 元素时,某些实现会抛出 NullPointerException。null 被用作指示poll 操作失败的警戒值。

BlockingQueue 可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地 put 附加元素。没有任何内部容量约束的 BlockingQueue 总是报告 Integer.MAX_VALUE 的剩余容量。

BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持 Collection 接口。因此,举例来说,使用remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。

BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)没有 必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。

 

java.util.concurrent.ArrayBlockingQueue<E>     E - 在此 collection 中保持的元素类型

extends AbstractQueue<E>implements BlockingQueue<E>, Serializable

一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。队列的头部 是在队列中存在时间最长的元素。队列的尾部是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。

这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。

此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness) 设置为 true 而构造的队列允许按照FIFO 顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”。

此类及其迭代器实现了CollectionIterator 接口的所有可选方法。

此类是JavaCollections Framework 的成员。

构造方法摘要

ArrayBlockingQueue(int capacity)           创建一个带有给定的(固定)容量和默认访问策略的 ArrayBlockingQueue。

ArrayBlockingQueue(int capacity, boolean fair)           创建一个具有给定的(固定)容量和指定访问策略的 ArrayBlockingQueue。

ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)           创建一个具有给定的(固定)容量和指定访问策略的 ArrayBlockingQueue,它最初包含给定 collection 的元素,并以 collection 迭代器的遍历顺序添加元素。

方法摘要

 boolean

add(E e)           将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException。

 void

clear()           自动移除此队列中的所有元素。

 boolean

contains(Object o)           如果此队列包含指定的元素,则返回 true。

 int

drainTo(Collection<? super E> c)           移除此队列中所有可用的元素,并将它们添加到给定 collection 中。

 int

drainTo(Collection<? super E> c, int maxElements)           最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 collection 中。

 Iterator<E>

iterator()           返回在此队列中的元素上按适当顺序进行迭代的迭代器。

 boolean

offer(E e)           将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false。

 boolean

offer(E e, long timeout, TimeUnit unit)           将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。

 E

peek()           获取但不移除此队列的头;如果此队列为空,则返回 null。

 E

poll()           获取并移除此队列的头,如果此队列为空,则返回 null。

 E

poll(long timeout, TimeUnit unit)           获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。

 void

put(E e)           将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。

 int

remainingCapacity()           返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的其他元素数量。

 boolean

remove(Object o)           从此队列中移除指定元素的单个实例(如果存在)。

 int

size()           返回此队列中元素的数量。

 E

take()  获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。

 Object[]

toArray()           返回一个按适当顺序包含此队列中所有元素的数组。

<T> T[]

toArray(T[] a)           返回一个按适当顺序包含此队列中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。

 String

toString()           返回此 collection 的字符串表示形式。

阻塞队列的实现原理(Condition锁中有提到await signal)

 

19.java5同步集合类的应用

       传统集合实现同步的问题

       举了一个例子:Map集合线程不同步导致的问题。

       解决办法:使用同步的Map集合      使用集合工具类中的方法将不同步的集合转为同步的Collections.synchronizedMap(newMap())这个方法返回一个同步的集合

       publicstatic <K, V> Map<K, V> synchronizedMap(Map<K, V> m)

{return newSynchronizedMap<K, V>(m);}

SynchronizedMap类相当于一个代理类,通过查看源代码发现:该类中的所有方法都是直接返回:原Map集合方法调用后的结果,只是将返回结果的代码放在了同步代码块中以实现同步,构造是将同步锁默认置为当前对象。

HashSet与HashMap的关系与区别:

       HashSet是单列的,HashMap是双列的(键值对)

       关系:HashSet内部使用的是HashMap中的键,不考虑值。

查看HashSet的源代码发现其内部就是用HashMap实现的,只是没有使用HashMap的V,只使用了它的K。

 

JDK1.5中提供了并发 Collection:提供了设计用于多线程上下文中的 Collection 实现:

ConcurrentHashMapConcurrentSkipListMapConcurrentSkipListSetCopyOnWriteArrayListCopyOnWriteArraySet。当期望许多线程访问一个给定 collection 时,ConcurrentHashMap 通常优于同步的 HashMap,ConcurrentSkipListMap 通常优于同步的 TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList优于同步的ArrayList。

ConcurrentSkipListMap<K,V>映射可以根据键的自然顺序进行排序,也可以根据创建映射时所提供的 Comparator 进行排序,具体取决于使用的构造方法。

ConcurrentSkipListSet<E>一个基于 ConcurrentSkipListMap 的可缩放并发 NavigableSet 实现。set 的元素可以根据它们的自然顺序进行排序,也可以根据创建 set 时所提供的 Comparator 进行排序,具体取决于使用的构造方法

CopyOnWriteArrayList<E>ArrayList 的一个线程安全的变体,其中所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。

这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。“

CopyOnWriteArraySet<E>对其所有操作使用内部 CopyOnWriteArrayList Set。因此,它共享以下相同的基本属性:

它最适合于具有以下特征的应用程序:set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。它是线程安全的。 因为通常需要复制整个基础数组,所以可变操作(add、set 和 remove 等等)的开销很大。 迭代器不支持可变 remove 操作。 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。

 

       传统集合中存在的其它问题:对集合迭代时,不能对集合中的元素进行修改(添加、删除……),Java5中提供的并发集合就解决了这个问题。

 

20. 空中网挑选实习生的面试题1

       现有的程序代码模拟产生了16个日志对象,并且需要运行16秒才能打印完这些日志,请在程序中增加4个线程去调用parseLog()方法来分头打印这16个日志对象,程序只需要运行4秒即可打印完这些日志对象。原始代码如下:

public class Test1

{

       publicstatic void main(String[] args)

       {

       SOP(begin:+sys.currentTimeMillis()/1000);

       //模拟处理16行日志,下面的代码产生16个日志对象,需运行16秒才能打印完

       //修改程序代码,开4个线程让这16个日志在4秒钟打完

       for (iint i=0; i<16; i++) //这行代码不能改动

       {

       final String log = “”+(i+1);   //这行代码不能改动

       {

       Test1.parseLog(log);

}

}

}

//parseLog方法内部代码不能改动

public staticvoid parseLog(String log)

{

       SOP(log+”:”+(sys.currentTimeMillis()/1000));

       try

       {

       Thread.sleep(1000);

}

       catch(InterruptedException e)

       {

       e.printStackTrace();

}

}

}

刚看到题目还想着很简单;直接在Test.parseLog(log)的地方new4个线程,都执行打印任务即可,仔细一看不行,在这里new4个线程的话就是16*4个线程了,所以要将线程在for循环外边创建出来,for内部将产生的日志对象装在一个共享变量里,在线程内部从共享变量中取数据打印。要考虑线程同步互斥问题,这个共享变量要具备同步功能,可以使用ArrayBlockingQueue这种阻塞式队列来存储日志对象。也可以使用普通集合,但拿数据要考虑同步问题,可能会浪费时间。

在for循环外部创建线程,定义共享变量

finalBlockingQueue<String> queue = new ArrayBlockingQueue<String>(16);

       for(int i=0; i<4; i++)    创建4个线程

       {

       new Thread(new Runnable()

{

       public void run()

       {

       while (true)

       {     先从集合中取到日志对象,再打印

       String log =queue.take();     要处理异常

       parseLog(log);

}

}

}).start();

}

在16次for循环内部将产生的日志对象装入共享变量中

queue.put(log);      要处理异常

这道题只要用到同步集合来共享数据就可以了  List集合的Vector也可以

 

21. 空中网挑选实习生的面试题2

       现成程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法去处理。就好像生产者不断地产生数据,消费者不断地消费数据。请将程序改造成有10个线程来消费生产者产生的数据,这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要一秒才能处理完,程序应保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完后,下一个消费者才能消费数据,下一个消费者是谁都可以,但要保证这些消费者线程拿到的数据是有序的。原始代码如下:

public class Test2

{

       publicstatic void main(String[] args)

       {

       SOP(begin+sys.currentTimeMillis()/1000);

       for (int i=0; i<10; i++)  //这行不能改动

       {

       String input = i+””;       //这行不能改动

       String output =TeatDo.doSome(input);

       SOP(ThreadName+output);

}

}

}

//不能改动此TestDo类

class TestDo

{

       publicstatic String doSome(String input)

       {

       try

       {

       Thread.sleep(1000);

}

       catch (InterruptedException e)

       {

       e.printStackTrace();

}

String output = input + “:” + (Sys.currentTimeMillis());

return output;

}

}

       看这题和上一题差不多,也是数据共享问题了,弄一个同步集合存起来。

       用同样的方法一样解决 new ArrayBlockingQueue()

张老师又讲了另一个同步队列:SynchronousQueue一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。不能在同步队列上进行 peek,因为仅在试图要移除元素时,该元素才存在;除非另一个线程试图移除某个元素,否则也不能(使用任何方法)插入元素;也不能迭代队列,因为其中没有元素可用于迭代。

SynchronousQueue是一个特殊队列,即便是空的也不能插入元素,也读不到元素,要往里边插入的时候如果没有读取操作,插入操作就会阻塞,等到有读取操作出现时,插入操作检测到了读取操作,才能把数据插入进去,而读取操作正好可以拿到刚刚插入进去的数据。就好比毒品买卖,我拿着毒品给谁呢,只有买毒品的人来了,才能立马给他,他也拿到了。与Exchanger类似,不过Exchanger是单对单的交换,SynchronousQueue可以多个抢数据,我拿着毒品等人来买,一下来了3个人买,谁抢到了就是谁的;或者我拿3包毒品,3个人同时每人一份。

这道题用synchronousQueue的话会一下子将10个数据全打印出来,因为10次循环一次放一个并没有人来取,所以没有放进去,后来一下10个线程来取数据,就一下放进去拿走了。我测试的时候没有这种情况,都是间隔一秒一秒的。测试后发现,将doSome处理后的结果存进去,就会有间隔,而直接存进去,取数据后再处理的话就是一下一片了。分析后知道:put时没有take,10个数据都在等待存入,如果存入的数据是doSome(input)的话,开始取数据时才开始执行doSome所以就会有间隔了。直接存数据,取出后在doSome就是一下拿到10个数据了。

要解决这个问题,可以使用厕所抢坑的方式解决,使用Semaphore来获取许可,每取一次数据释放一次即可。

final Semaphorex = new Semaphore(1);     一次一个

finalSynchronousQueue queue = new SynchronousQueue();

每次取数据前都要先获取许可

x.acquire();

取完后释放许可

x.release();

这种方式与使用Lock方式一样

 

22. 空中网挑选实习生的面试题3

       现有程序同时启动了4个线程去调用TestDo.doSome(key, value)方法,由于TestDo.doSome(key, value)方法内的代码是先暂停1秒,然后再输出以秒为单位的当前时间值,所以,会打印出4个相同的时间值,如下所示:

              4:4:1258199615

              1:1:1258199615

              3:3:1258199615

              1:2:1258199615

       请修改代码,如果有几个线程调用TestDo.doSome(key,value)方法时,传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果,即当有两个线程的key都是“1”时,她们中的一个要比另外其他线程晚1秒输出结果,如下所示:

              4:4:1258199615

              1:1:1258199615

              3:3:1258199615

              1:2:1258199616

       总之,当每个线程中指定的key相等时,这些相等key的线程应每隔一秒依次输出时间值(要用互斥),如果key不同,则并行执行(相互之间不互斥)。原始代码如下:

//不能改动此Test类

public class Test3 extends Thread

{

       privateTestDo testDo;

       privateString key;

       privateString value;

       publicTest3(String key, String key2, String value)

       {

       this.testDo = TestDo.getInstance();

       /*常量“1”和 “1”是同一个对象,下面这行代码就是要用“1”+“”的

方式产生新的对象,以实现内容没有改变,仍然相等(都还为“1”),

但对象却不再是同一个的效果

*/

this.key = key + key2;

this.value = value;

}

       publicstatic void main(String[] args) throws InterruptedException

       {

       Test3 a = new Test3(“1”, “”, “1”);

       Test3 b = new Test3(“1”, “”, “2”);

       Test3 c = new Test3(“3”, “”, “3”);

       Test3 d = new Test3(“4”, “”, “4”);

       SOP(begin+:+sys.currentTimeMillis()/1000);

       a.start();

       b.start();

       c.start();

       d.start();

}

       publicvoid run()

       {

       testDo.doSome(key, value);

}

}

 

class TestDo

{

       privateTestDo(){}

       privatestatic TestDo _instance = new TestDo();

public staticTestDo getInstance()

       {

       return _instance;

}

public voiddoSome(Object key, String value)

{

       //此大括号内的代码是需要局部同步的代码,不能改动!

       {

       try

       {

       Thread.sleep(1000);

       SOP(key+”:”+value+”:”+sys.currentTimeMillis()/1000);

}

       catch (InterruptedExceptione)

       {

              e.printStackTrace();

}

}

}

}

       看完这道题第一个想法是在标记位置加上同步代码块,但是锁不好弄了,因为每次都新建了一个key对象来接受实际key,没法获取到实际key对象。     

       想到了Lock对象,所以建一个Lock对象,判断key的值是否和指定值“1“相同,如果相同就锁上,不同不管,finally里在解锁前进行判断,避免没上锁还要解锁发生问题。

Lock lock = newReentrantLock();

       publicvoid doSome(Object key, String value)

       {

              if(key.equals("1"))

                     lock.lock();

                     //System.out.println("OKOKOOK");

              //synchronized("1")

              try

              //此大括号内的代码是需要局部同步的代码,不能改动!

              {

                     try

                     {

                            Thread.sleep(1000);

                            System.out.println(key+":"+value+":"+System.currentTimeMillis()/1000);

                     }

                     catch(InterruptedException e)

                     {

                            e.printStackTrace();

                     }

              }finally

              {

                     if(key.equals("1"))

                            lock.unlock();

              }

       }

但上面的方式写死了,如果换2呢 还要改代码,现在要不管是什么只要相同都互斥,将这些加进来的key放到一个集合ArrayList中,每次传进来一个key,先把传进来的key作为锁对象,再判断这个对象有没有存在锁集合中,如果没有,就把它存进去,同时就用这个key做锁;如果已经存在了,就是说这个key已经做过锁对象了,就需要将以前做锁的那个对象拿出来,再让它来当锁,与传进来的key对象一样,这样就产生互斥效果了。

       需要注意:拿原来的锁对象时要迭代锁集合,因为有多个线程在运行,所以迭代时有可能出现其他线程的key没有做过锁,需要将它加到锁集合中,可是这时候这个线程还在迭代过程中,迭代时不能操作集合中的数据,就会发生异常。要解决这个问题,就需要用到同步集合了。CopyOnWriteArrayList

 

       使用ArrayList时就经常出异常,换CopyOnWriteArrayList后没有异常了

//将所有传过来的key都存起来

    //privateList<Object> keys = new ArrayList<Object>();

    privateCopyOnWriteArrayList<Object> keys = newCopyOnWriteArrayList<Object>();

    publicvoiddoSome(Object key, String value)

    {

        //先用这个key当锁,用过一次就存到集合中

        Objecto = key;

        //判断这个锁用过没有

        if (!keys.contains(o))

        {

            //如果这个key没有用过,就用它当锁,把它存到锁集合中

            keys.add(o);

        }

        else   //锁集合中已经有了这个key

        {

            //这个key已经当过锁了,就把它拿出来,还用它做锁,就和现在的key互斥了

            //因为不知道原来key的位置,所有需要进行遍历

            for(Iterator<Object> it = keys.iterator();it.hasNext();)

            {

                //当前遍历到的对象

                Objectoo = it.next();

                //如果找到了,就让它做锁

                if(oo.equals(o))

                {

                    o= oo;

                    break; //找到了,不用再循环了

                }                  

            }

            //o= keys.get(keys.indexOf(o));    //key和o不是同一个对象,拿不到

        }

       

        synchronized(o)

        //此大括号内的代码是需要局部同步的代码,不能改动!

        {

            try

            {

                Thread.sleep(1000);

                System.out.println(key+":"+value+":"+System.currentTimeMillis()/1000);

            }

            catch(InterruptedException e)

            {

                e.printStackTrace();

            }

        }

    }

 

a = “1”+””;

b = “1”+””;

a和b是同一个对象,常量相加equals为真 ==为假

 

Object o1 = new String("1");

       Object o2 = new String("1");

       System.out.println(o1==o2); //false

       System.out.println(o1.equals(o2)); //true

       System.out.println(o1);  //1

       System.out.println(o2);  //1

       Object o3 = "1"+"";

       Object o4 = "1"+"";

       System.out.println(o3==o4); //true

       System.out.println(o3.equals(o4)); //true

       Object o5 = "2"+"";

       Object o6 = get("2","");

       System.out.println(o5==o6); //false

       System.out.println(o5.equals(o6)); //true

       System.out.println(o5+"__"+o6); //2__2

      

    publicstatic Object get(String a,String b)

    {

       return a+b;

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值