黑马程序员——Java高新_线程并发库

来源:互联网 发布:windows xp系统安装 编辑:程序博客网 时间:2024/05/03 07:34

 ----------------------Android培训、Java培训、期待与您交流! ----------------------

线程并发库:Java5中新增了java.util.concurrent包。其中有线程池(Executors)、Lock(新的锁)、信号灯(Semaphore)、可阻塞队列(BlockingQueue)等。

-----------------------------分割线-----------------------------

1 线程并发库之线程池(Executors)

     TCP服务器编程模型中,每一个客户端连接用一个单独的线程为之服务,当与客户端的会话结束时,线程也就结束了,即每来一个客户端连接,服务器端就要创建一个新线程。

     举例:这好比假设每个报名学员都要通过老师来亲自接待,以便给每个学员一种好的感觉,但每个学员报名手续要花费半个小时,对于50名同学,老师一个个接待和为之办理手续,显然不实际,那么会怎么做呢?老师会先接待每一个学员,打完招呼后,再把他分配给一名工作人员去办理手续,这样,老师就接待了每名学员。

     如果访问服务器的客户端很多,那么服务器就要不断地创建和销毁线程,这将严重影响服务器的性能。如果真的来一名学员,就安排一名新工作人员为之服务,也是不可能的,那公司岂不是要招聘很多工作人员?而是应该一名工作人员服务完一名学员,空闲下来后,一旦有新的学员要服务,就又立即安排该工作人员为新学员服务。

     线程池的概念与此类似,首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。

     在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程。任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。

     线程池有固定线程池和缓存线程池。

代码示例:

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;public class ThreadPoolTest {public static void main(String[] args) {//ExecutorService service = Executors.newFixedThreadPool(3);ExecutorService service = Executors.newCachedThreadPool();for(int i=1;i<=10;i++){final int sequence = i;service.execute(new Runnable(){public void run() {try{Thread.sleep(200);}catch(Exception e){}for(int j=1;j<=5;j++){System.out.println(Thread.currentThread().getName() + "is serving " + sequence + " task:" + "loop of " + j);}}});}System.out.println(“all task have committed!”);//注意与service.shutdownNow()的区别。service.shutdown();ScheduledExecutorService scheduledService = Executors.newScheduledThreadPool(1);scheduledService.scheduleAtFixedRate(new Runnable(){public void run() {System.out.println("bomb!!!");}}, 5, 1,TimeUnit.SECONDS);}}

总结:线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。 

     线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优级运行,并处于多线程单元中

-----------------------------分割线-----------------------------

2 线程并发库之新锁(Lock)

     Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。

     在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。

     一个锁内部可以有多个Condition,即有多路等待和通知,在传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。(如果只用一个Condition,两个放的都在等,一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走。)

代码示例(建立3个Condition对象来唤醒不同的线程):

import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class SignalTest2 {public static void main(String[] args) {new SignalTest2().init();}private void init(){final Business b = new Business();new Thread(){public void run(){for(int i=0;i<50;i++)b.main();}}.start();new Thread(){public void run(){for(int i=0;i<50;i++)b.sub();}}.start();new Thread(){public void run(){for(int i=0;i<50;i++)b.sub2();}}.start();}private class Business{int status = 1;Lock lock = new ReentrantLock();Condition cond1 = lock.newCondition();Condition cond2 = lock.newCondition();Condition cond3 = lock.newCondition();public  void main(){lock.lock();while(status != 1){try{cond1.await();}catch(Exception e){}}for(int i=1;i<=5;i++){try{Thread.sleep(200);}catch(Exception e){}System.out.println(Thread.currentThread().getName() + ":" + i);}status = 2;cond2.signal();lock.unlock();}public  void sub(){lock.lock();while(status != 2){try{cond2.await();}catch(Exception e){}}for(int i=1;i<=10;i++){try{Thread.sleep(200);}catch(Exception e){}System.out.println(Thread.currentThread().getName() + ":" + i);}status = 3;cond3.signal();lock.unlock();}public  void sub2(){lock.lock();while(status != 3){try{cond3.await();}catch(Exception e){}}for(int i=1;i<=10;i++){try{Thread.sleep(200);}catch(Exception e){}System.out.println(Thread.currentThread().getName() + ":" + i);}status = 1;cond1.signal();lock.unlock();}}}

-----------------------------分割线-----------------------------

3 线程并发库之信号灯(Semaphore

     Semaphore可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。

     举例:厕所有5个坑,假如有十个人要上厕所,那么同时能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中的任何一个人让开后,其中在等待的另外5个人中又有一个可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。

     单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。

代码示例:

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;public class SemaphoreTest {public static void main(String[] args) {ExecutorService service = Executors.newCachedThreadPool();final  Semaphore sp = new Semaphore(3);for(int i=0;i<10;i++){Runnable runnable = new Runnable(){public void run(){try {sp.acquire();} catch (InterruptedException e1) {e1.printStackTrace();}System.out.println("线程" + Thread.currentThread().getName() + "进入,当前已有" + (3-sp.availablePermits()) + "个并发");try {Thread.sleep((long)(Math.random()*10000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程" + Thread.currentThread().getName() + "即将离开");sp.release();//下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元System.out.println("线程" + Thread.currentThread().getName() + "已离开,当前已有" + (3-sp.availablePermits()) + "个并发");}};service.execute(runnable);}}}

-----------------------------分割线-----------------------------

4 线程并发库之可阻塞队列(BlockingQueue

     阻塞队列与Semaphore有些相似,但也不同,阻塞队列是一方存放数据,另一方释放数据,Semaphore通常则是由同一方设置和释放信号量。

     可阻塞队列之一:ArrayBlockingQueue

     只有put方法和take方法才具有阻塞功能

示例代码:

import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class BlockingQueueCondition {public static void main(String[] args) {ExecutorService service = Executors.newSingleThreadExecutor();final Business3 business = new Business3();service.execute(new Runnable(){public void run() {for(int i=0;i<50;i++){business.sub();}}});for(int i=0;i<50;i++){business.main();}}}class Business3{BlockingQueue subQueue = new ArrayBlockingQueue(1);BlockingQueue mainQueue = new ArrayBlockingQueue(1);{try {mainQueue.put(1);} catch (InterruptedException e) {e.printStackTrace();}}public void sub(){try{mainQueue.take();for(int i=0;i<10;i++){System.out.println(Thread.currentThread().getName() + " : " + i);}subQueue.put(1);}catch(Exception e){}}public void main(){try{subQueue.take();for(int i=0;i<5;i++){System.out.println(Thread.currentThread().getName() + " : " + i);}mainQueue.put(1);}catch(Exception e){}}}

-----------------------------分割线-----------------------------

5 线程并发库之其它同步工具类

CyclicBarrier

     表示大家彼此等待,大家集合好后才开始出发,分散活动后又在指定地点集合碰面,这就好比整个公司的人员利用周末时间集体郊游一样,先各自从家出发到公司集合后,再同时出发到公园游玩,在指定地点集合后再同时开始就餐。

CountDownLatch

     犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。这直接通过代码来说明CountDownLatch的作用,这样学员的理解效果更直接。

     可以实现一个人(也可以是多个人)等待其他所有人都来通知他,这犹如一个计划需要多个领导都签字后才能继续向下实施。还可以实现一个人通知多个人的效果,类似裁判一声口令,运动员同时开始奔跑。用这个功能做百米赛跑的游戏程序不错哦!

Exchanger

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

-----------------------------分割线-----------------------------

6 线程并发库之面试题

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

public class Test {public static void main(String[] args) {System.out.println("begin:" + (System.currentTimeMillis() / 1000));/* * 模拟处理16行日志,下面的代码产生了16个日志对象,当前代码需要运行16秒才能打印完这些日志。 * 修改程序代码,开四个线程让这16个对象在4秒钟打完。 */for (int i = 0; i < 16; i++) { // 这行代码不能改动final String log = "" + (i + 1);// 这行代码不能改动{Test.parseLog(log);}}}// parseLog方法内部的代码不能改动public static void parseLog(String log) {System.out.println(log + ":" + (System.currentTimeMillis() / 1000));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}

答案代码示例如下:

 (思路:通过阻塞队列来获得数据)

import java.util.concurrent.*;public class Mission1 {public static void main(String[] args) {System.out.println("begin:" + (System.currentTimeMillis() / 1000));final ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(4);for(int i=0;i<4;i++){new Thread(new Runnable(){public void run() {while(true){try {String log = (String)queue.take();Mission1.parseLog(log);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}).start();}/* * 模拟处理16行日志,下面的代码产生了16个日志对象,当前代码需要运行16秒才能打印完这些日志。 * 修改程序代码,开四个线程让这16个对象在4秒钟打完。 */for (int i = 0; i < 16; i++) { // 这行代码不能改动final String log = "" + (i + 1);// 这行代码不能改动{try {queue.put(log);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}// parseLog方法内部的代码不能改动public static void parseLog(String log) {System.out.println(log + ":" + (System.currentTimeMillis() / 1000));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}

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

package queue;public class Test {public static void main(String[] args) {System.out.println("begin:" + (System.currentTimeMillis() / 1000));for (int i = 0; i < 10; i++) { // 这行不能改动String input = i + ""; // 这行不能改动String output = TestDo.doSome(input);System.out.println(Thread.currentThread().getName() + ":" + output);}}}// 不能改动此TestDo类class TestDo {public static String doSome(String input) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}String output = input + ":" + (System.currentTimeMillis() / 1000);return output;}}

答案代码示例如下:

(思路:通过阻塞队列来获得数据,通过信号灯控制顺序进行有序打印)

import java.util.concurrent.Semaphore;import java.util.concurrent.SynchronousQueue;public class Test {public static void main(String[] args) {final Semaphore semaphore = new Semaphore(1);final SynchronousQueue<String> queue = new SynchronousQueue<String>();for(int i=0;i<10;i++){new Thread(new Runnable(){public void run() {try {semaphore.acquire();String input = queue.take();String output = TestDo.doSome(input);System.out.println(Thread.currentThread().getName()+ ":" + output);semaphore.release();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}).start();}System.out.println("begin:"+(System.currentTimeMillis()/1000));for(int i=0;i<10;i++){  //这行不能改动String input = i+"";  //这行不能改动try {queue.put(input);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}//不能改动此TestDo类class TestDo {public static String doSome(String input){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}String output = input + ":"+ (System.currentTimeMillis() / 1000);return output;}}

     第三题:现有程序同时启动了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不同,则并行执行(相互之间不互斥)。原始代码如下:

package syn;//不能改动此Test类public class Test extends Thread {private TestDo testDo;private String key;private String value;public Test(String key, String key2, String value) {this.testDo = TestDo.getInstance();/* * 常量"1"和"1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象, * 以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果 */this.key = key + key2;this.value = value;}public static void main(String[] args) throws InterruptedException {Test a = new Test("1", "", "1");Test b = new Test("1", "", "2");Test c = new Test("3", "", "3");Test d = new Test("4", "", "4");System.out.println("begin:" + (System.currentTimeMillis() / 1000));a.start();b.start();c.start();d.start();}public void run() {testDo.doSome(key, value);}}class TestDo {private TestDo() {}private static TestDo _instance = new TestDo();public static TestDo getInstance() {return _instance;}public void doSome(Object key, String value) {// 以大括号内的是需要局部同步的代码,不能改动!{try {Thread.sleep(1000);System.out.println(key + ":" + value + ":"+ (System.currentTimeMillis() / 1000));} catch (InterruptedException e) {e.printStackTrace();}}}}
答案代码示例:

 (思路:将传进来的对象,通过去重处理,这样它们的key就相同了,让key做同步锁对象,相同的key的对象自然就可以让其迟一秒打印)

package syn;import java.util.ArrayList;import java.util.Iterator;import java.util.concurrent.CopyOnWriteArrayList;//不能改动此Test类public class Test extends Thread{private TestDo testDo;private String key;private String value;public Test(String key,String key2,String value){this.testDo = TestDo.getInstance();/*常量"1"和"1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象,以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果*/this.key = key+key2; /*a = "1"+"";b = "1"+""*/this.value = value;}public static void main(String[] args) throws InterruptedException{Test a = new Test("1","","1");Test b = new Test("1","","2");Test c = new Test("3","","3");Test d = new Test("4","","4");System.out.println("begin:"+(System.currentTimeMillis()/1000));a.start();b.start();c.start();d.start();}public void run(){testDo.doSome(key, value);}}class TestDo {private TestDo() {}private static TestDo _instance = new TestDo();public static TestDo getInstance() {return _instance;}//private ArrayList keys = new ArrayList();private CopyOnWriteArrayList keys = new CopyOnWriteArrayList();public void doSome(Object key, String value) {Object o = key;if(!keys.contains(o)){keys.add(o);}else{for(Iterator iter=keys.iterator();iter.hasNext();){try {Thread.sleep(20);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}Object oo = iter.next();if(oo.equals(o)){o = oo;break;}}}synchronized(o)// 以大括号内的是需要局部同步的代码,不能改动!{try {Thread.sleep(1000);System.out.println(key+":"+value + ":"+ (System.currentTimeMillis() / 1000));} catch (InterruptedException e) {e.printStackTrace();}}}}


 ----------------------Android培训、Java培训、期待与您交流! ----------------------

0 0