java 利用同步工具类控制线程

 


 

 

前言

   参考来源:《java并发编程实战》html

   同步工具类:根据工具类的自身状态来协调线程的控制流。经过同步工具类,来协调线程之间的行为。java

   可见性:在多线程环境下,当某个属性被其余线程修改后,其余线程可以马上看到修改后的信息。典型的是用java内置关键字volatile,volatile变量可让jvm知道这个变量是对全部线程共享的,jvm会作必定的同步工做,但不保证原子性。编程

       这一部份内容能够看一下http://www.cnblogs.com/dolphin0520/p/3920373.html安全

                  http://www.cnblogs.com/Mainz/p/3556430.html#多线程

   发布和逸出:并发

        发布:发布一个对象指,使对象可以在当前做用域以外的代码使用。举个栗子就是,对象A持有对象C,把C的引用传递到其余地方,或者外界访问A有非私有方法返回C。发布一个对象时要考虑是否有足够理由发布出去,不然可能会引发同步问题。jvm

           很明显,同步工具类确定须要发布给通讯双方。ide

        逸出:当某个不该该发布的对象被发布时,叫作逸出。函数

  线程之间通讯,就须要把一个通讯协议(某个对象)发布给双方,而且对双方来讲都是可见的,这样来协调线程之间的行为。固然若是有特别须要,彻底能够本身去写同步工具类。工具

 


 

闭锁

  闭锁,可使线程等待某个事件(信号)发生后才执行后续操做。闭锁有一个初始值,当初始值为0的时候闭锁终止,全部被该闭锁阻塞的线程均可以继续执行。假设有个线程A,咱们想A启动以后须要等待线程B的信号才能继续执行,这时候咱们能够把一个闭锁发布给A,B,A等待闭锁的结束,B来结束闭锁,这样A就能等B的命令才能继续执行。事实上,利用闭锁还可让主线程等待全部子线程结束。有一点要注意的是,闭锁是一次性的,一旦进入终止状态(count==0),就不能被重置。

 

  CountDownLatch简单用法介绍:

    public CountDownLatch(int count);  构造函数,当count为0的时候,await方法再也不阻塞。

    public void countDown();         使count减1。

    public void await();          使当前线程阻塞,直到CountDownLatch的count为0。

 

  下面这个例子显示了利用闭锁控制子线程执行和等待全部子线程结束。

 1 public class TestDownLatch {
 2     int count = 5;
 3     //控制子线程执行的闭锁
 4     final CountDownLatch startGate = new CountDownLatch(1);
 5     //让主线程等待全部子线程结束
 6     final CountDownLatch endGate = new CountDownLatch(count);
 7     
 8     //测试线程
 9     class Task extends Thread{
10         
11         int i;
12         
13         public Task(int i) {
14             this.i = i;
15         }
16 
17 
18         @Override
19         public void run() {
20             //等待闭锁的结束
21             try {
22                 //等待主线程的命令
23                 startGate.await();
24                 System.out.println(i);
25                 //告诉主线程我已经作完了
26                 endGate.countDown();
27             } catch (InterruptedException igored) {
28             }
29         }
30         
31     }
32     
33     public void test(){
34         for (int i = 0; i < count; i++) {
35             Task task = new Task(i);
36             task.start();
37         }
38         System.out.println("全部线程已开启");
39         try {
40             Thread.sleep(3000);
41             startGate.countDown();
42             System.out.println("全部子线程开始输出");
43             
44             Thread.sleep(3000);
45             endGate.await();
46             System.out.println("主线程结束");
47         } catch (InterruptedException ignored) {
48         }
49         
50         
51     }
52     
53     public static void main(String[] args) {
54         TestDownLatch testDownLatch = new TestDownLatch();
55         testDownLatch.test();
56     }
57 
58 }
闭锁

 运行结果:

 

 


 

FutureTask

  《java并发编程实战》的翻译者没有翻译这个词,我也只能这么叫它先。FutureTask自己实现Runnable接口和Future接口。

   FutureTask至关于一个助手,你把一个任务(有返回结果而且每每比较耗时)交给FutureTask,让它先计算结果,而后你去干别的事,这个助手(FutureTask)会帮你保存结果,等你须要结果时(调用Future#get),若是计算结束了,FutureTask直接把结果给你,若是还没计算完,就让你等待(阻塞),直到计算完了。

 

  FutureTask简单用法介绍:

    public FutureTask(Callable<V> callable);  构造方法。参数Callable,至关于一种可生成结果的Runnable,至关于上面的任务(带返回结果),V为结果的类型,实现Callable须要实现call()。

    public V get();                获取Callable返回的结果,若是Callable没执行完,该方法会阻塞当前线程。

    public boolean cancel(boolean mayInterruptIfRunning);    取消执行

    另外还有isDone(),isCancelled()方法知道FutureTask状态;

 

如下代码测试FutureTask,先实例化一个FutureTask并自动获取当前时间(一秒的消耗时间),输出当时时间,而后三秒后再去FutureTask拿结果,能够看到两个时间没有三秒间隔,说明FutureTask花费一秒后算好结果就保存起来,等主线程获取结果。

 1 public class TestFutureTask {
 2     
 3     //任务,返回计算时的时间
 4     private final FutureTask<Date> future = new FutureTask<Date>(new Callable<Date>() {
 5         @Override
 6         public Date call() throws Exception {
 7             //1秒后再返回结果
 8             Thread.sleep(1000);
 9             return new Date();
10         }
11         
12     });
13     
14     public TestFutureTask(){
15         new Thread(future).start();
16     }
17     
18     public Date get() throws InterruptedException, ExecutionException{
19         return future.get();
20     }
21     
22     public static void main(String[] args) {
23         try {
24             System.out.println(new Date());
25             TestFutureTask task = new TestFutureTask();
26             System.out.println("等待3秒.............");
27             System.out.println(task.get());
28         } catch (InterruptedException e) {
29             e.printStackTrace();
30         } catch (ExecutionException e) {
31             e.printStackTrace();
32         }
33     }
34     
35 }
FutureTask

 

运行结果:

 

 


 

信号量

  计数信号量用来控制同时访问某个特定资源的操做数量。信号量(Semaphore)维护的是一组许可,准确来讲,信号量只是维护这组许可的数量。它有一个初始值,根据这个值的大小来决定每次请求许可时的行为,若是用过链接池就很容易明白,链接池里一开始放着一堆链接(Connection),每次从链接池拿出一个Connection,链接池就少一个Connection,每次Connection用完就还给链接池,方便之后的用户使用。这些资源池都拥有一个信号量,来控制资源的出入。一样,使用Semaphore能够将任何一种容器变成有界阻塞容器。

  

  Semaphore简单用法介绍:

    public Semaphore(int permits);  构造方法。参数premits为初始化信号量大小,当permits为0,获取(调用acquire方法)资源会阻塞,直到permits>0。

    public void acquire() throws InterruptedException;   当permits>0时获取许可,permits会减一,不然阻塞直到permits>0。

    public void release();                  释放许可,permits加一。


如下代码使用信号量简单模拟链接池,测试让链接池大小为5,而后开六个线程去获取链接,每一个线程持有链接3秒,能够看到第六个线程阻塞住,要等有一个线程释放链接才能够获取到链接

 1 public class Pool {
 2     private final List<Connection> list;
 3     private final Semaphore sem;
 4     
 5     public Pool(int initSize) {
 6         //注意这里
 7         list = Collections.synchronizedList(new LinkedList<Connection>());
 8         for (int i = 0; i < initSize; i++) {
 9             list.add(new Connection());
10         }
11         sem = new Semaphore(initSize);
12     }
13     
14     //获取链接
15     public Connection get() throws InterruptedException{
16         //若是sem当前许可数量为0,阻塞当前线程
17         sem.acquire();
18         return list.remove(0);
19         
20     }
21     
22     //释放链接
23     public void release(Connection resource) throws InterruptedException{
24         sem.release();
25         list.add(resource);
26         
27     }
28     
29     public static void main(String[] args) {
30         final Pool pool = new Pool(5);
31         
32         for (int i = 0; i < 6; i++) {
33             pool.new TestSemaphore(i, pool).start();
34         }
35     }
36     
37     public class TestSemaphore extends Thread{
38         
39         final int i;
40         final Pool pool;
41         
42         public TestSemaphore(int i,Pool pool) {
43             this.i = i;
44             this.pool = pool;
45         }
46 
47         @Override
48         public void run() {
49             try {
50                 Connection conn = pool.get();
51                 System.out.println(i+"成功获取资源并占用三秒!");
52                 Thread.sleep(3000);
53                 pool.release(conn);
54                 System.out.println(i+"已释放资源!!!");
55             } catch (InterruptedException e) {
56                 System.out.println(i+"获取资源失败----");
57             }
58         }
59     }
60 
61 }
信号量

 

运行结果:

 

  特别注意:Pool的构造方法用Collections.synchronizedList方法使保存链接的list变成线程安全,在调用list的方法时会自动加锁,可是若是用迭代器访问list的时候须要手动加锁,这一点在Collections的API文档有特别说明。

 


 

栅栏

  栅栏跟闭锁很像,先说栅栏的做用——阻塞一组线程直到某个事件发生。闭锁和栅栏的最大区别就是,栅栏可让全部线程必须同时到达栅栏位置,才能继续执行,闭锁最多作到线程执行到阻塞的位置必须等待闭锁的终止,而没法保证线程等待其余线程的执行。栅栏就像赛马的那个栏(我也不知道叫什么),全部马到了起点,栅栏一开,全部马都开始跑,闭锁是没办法知道全部马都到达栅栏处的。

 

  CyclicBarrier简单用法介绍:

    public CyclicBarrier(int parties, Runnable barrierAction);  构造方法。parties指一共有多少个线程,barrierAction就是全部线程经过栅栏的时回调函数。

    public int await() throws InterruptedException, BrokenBarrierException;  当前线程等待其余全部线程到达(也是等待栅栏打开)。

    CyclicBarrier是指能够循环使用的栅栏,每次栅栏打开后都能重置以便下次使用。可是若是对await的调用超时,或者await阻塞的线程被中断,栅栏被认为是打破,全部阻塞的await调用都抛出BrokenBarrierException。

 

 如下代码用栅栏使全部五个召唤师都到达战场后游戏开始,从开始链接到全军出击的时间差应该是五个召唤师最慢那个,就是LOL时半天进度条也没100的那个。虽然我戒撸好久了= =

 1 public class TestBarrier {
 2     private final CyclicBarrier barrier;
 3     
 4     public TestBarrier() {
 5         this.barrier = new CyclicBarrier(5, new Runnable() {
 6             @Override
 7             public void run() {
 8                 System.out.println(new Date()+"...全军出击!");
 9             }
10         });
11     }
12     
13     public void start(){
14         System.out.println(new Date()+"开始链接-----");
15         new Summoner(1,3).start();
16         new Summoner(2,1).start();
17         new Summoner(3,5).start();
18         new Summoner(4,6).start();
19         new Summoner(5,1).start();
20     }
21 
22     public class Summoner extends Thread{
23 
24         int i;
25         int preSecond;
26         
27         public Summoner(int i,int preSecond) {
28             this.i = i;
29             this.preSecond = preSecond;
30         }
31 
32         @Override
33         public void run() {
34             try {
35                 System.out.println("召唤师"+i+"须要"+preSecond+"秒到达战场");
36                 Thread.sleep(preSecond*1000);
37                 barrier.await();
38                 System.out.println("------------ 召唤师"+i+":"+new Date());
39             } catch (InterruptedException | BrokenBarrierException e) {
40                 e.printStackTrace();
41             }
42         }
43         
44     }
45     
46     public static void main(String[] args) {
47         new TestBarrier().start();
48     }
49 
50 }
栅栏

 

 

运行结果:






 

小结

  闭锁:当你的线程须要等待一个命令的时候能够用它。

  FutureTask:当你有个耗时的过程想利用并行来提升效率,能够用FutureTask。

  信号量:当你在多线程环境下想控制有限资源的访问,使一个容器变成有界阻塞容器,能够考虑信号量。

  栅栏:当你的多个线程须要共同完成某些步骤才能够继续执行,例如子问题结果合并,可使用栅栏。

相关文章
相关标签/搜索