黑马程序员——多线程回顾

------------android培训java培训、期待与您交流! -------------

一、传统线程机制的回顾

1创建线程的两种传统方式

1) Thread子类覆盖的run方法中编写运行代码

   问:能否在run方法声明上抛出InterruptedException异常,以便省略run方法内部   对Thread.sleep()语句的trycatch处理?

   :不能,因为run是覆盖父类的run方法,子类不能抛出比父类更多的异常,更别说父类没抛

2在传递给Thread对象的Runnable对象的run方法中编写代码

    总结:查看Thread类的run()方法的源代码,可以看到其实这两种方式都是在调用Thread对象的run方法,如果Thread类的run方法没有被覆盖,并且为该Thread对象设置了一个Runnable对象,该run方法会调用Runnable对象的run方法。

      问:如果在Thread子类覆盖的run方法中编写了运行代码,也为Thread子类对象传递了一个Runnable对象,那么,线程运行时的执行代码是子类的run方法的代码?还是Runnable对象的run方法的代码?

      答:运行的是Thread中的run

涉及到的一个以往知识点:匿名内部类对象的构造方法如何调用父类的非默认构造方法

定义一个接口A
interface A{
 public int add(int b,int c);
}

在main方法中加上
new A(){
/*这里的new A() 就是调用构造方法 如有有参数的就直接写参数就行了 new A("aa") 着样就行
匿名内部类不能重新定义新的构造方法*/
    public int add(int b, int c) {
      return b+c;
 }
};

定时器的应用

      Timer

      TimerTask

线程的同步互斥与通信



使用synchronized代码块及其原理

使用synchronized方法

分析静态方法所使用的同步监视器对象是什么?目标类的字节码文件对象

waitnotify实现线程间的通信

多个线程访问共享对象和数据的方式

1如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做。

2如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享:

        1将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

        2将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。

        3)上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。

        4总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。

4极端且简单的方式,即在任意一个类中定义一个static的变量,这将被所有线程共享。

ThreadLocal实现线程范围的共享变量

见下页的示意图和辅助代码解释ThreadLocal的作用和目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。

每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。

ThreadLocal的应用场景:

      1订单处理包含一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理,如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。

    2) 银行转账包含一系列操作: 把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法。

    3) 例如Strut2ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext方法拿到的对象都不相同,对同一个线程来说,不管调用getContext方法多少次和在哪个模块中getContext方法,拿到的都是同一个。

      实验案例:定义一个全局共享的ThreadLocal变量,然后启动多个线程向该ThreadLocal变量中存储一个随机值,接着各个线程调用另外其他多个类的方法,这多个类的方法中读取这个ThreadLocal变量的值,就可以看到多个类在同一个线程中共享同一份数据。

实现对ThreadLocal变量的封装,让外界不要直接操作ThreadLocal变量。

     对基本类型的数据的封装,这种应用相对很少见。

      对对象类型的数据的封装,比较常见,即让某个类针对不同线程分别创建一个独立的实例对象。

总结:一个ThreadLocal代表一个变量,故其中里只能放一个数据,你有两个变量都要线程范围内共享,则要定义两个ThreadLocal对象。如果有一个百个变量要线程共享呢?那请先定义一个对象来装这一百个变量,然后在ThreadLocal中存储这一个对象。

关于线程范围内的变量共享的举例:监狱里罪犯的排队打饭,针对A罪犯,那几个打饭和打菜和打汤的模块操作的饭盆是A罪犯相关的饭盆;针对B罪犯,那几个打饭和打菜和打汤的模块操作的饭盆是B罪犯相关的饭盆。

首先用如下代码来说明如何实现全局共享:

Class GlobalData

 {

     public static ThreadLocal var = new ThreadLocal();

 } 

全局静态变量可以被多个模块类共享,并且不管是哪个线程来调用,数据都是同一份。

接着用如下代码来说全局共享的变量被不同线程调用时,希望有不同的返回值的情况。

Class A{

public void say(){

GlobalData.var.get()

}

}

线程1、线程、线程3访问的GlobalData.var得到的对象是否是同一个?要反复强调这是同一个对象。但是,使用的GlobalData.var.get()得到数据是肯定同一个吗?那就不一定了!例如这里要讲的Threadlocal就可以为三个线程分别返回三个不同的值。

三个线程用如下代码来set这个GlobalData.var对象的值时

 GlobalData.var.set(new Random().nextInt(10000));

最终存进去了几个值?这时候要为每个线程各自分别存储进去一个值,即总共存储进了三个值。

------------------------------

[html]  view plain copy print ?
  1. 通过ThreadLocal类的示意代码进行原理分析:  
  2. ThreadLocal  
  3. {  
  4. HashMap hashMap = new HashMap();  
  5.         void set(Object obj)  
  6. {  
  7. hashMap.put(Thread.currentThread(),obj);  
  8. }  
  9. object get()  
  10. {  
  11. return hashMap.get(Thread.currentThread());  
  12. }  
  13. }  


Java5中的线程并发库

java.util.concurrent包及子包的API帮助文档

了解java.util.concurrent.atomic

查看atomic包文档页下面的介绍

通过如下两个方法快速理解atomic包的意义:

AtomicInteger类的boolean compareAndSet(expectedValue, updateValue); 

AtomicIntegerArray类的int addAndGet(int i, int delta);

顺带解释volatile类型的作用,需要查看java语言规范。

了解java.util.concurrent.lock

在下页通过案例详细讲解

如何看包的API帮助文档:可以先找到该包下的某个类的帮助页面,然后在该页面的顶部单击package超链接。

要简要介绍下atomiclock这两个子包,

Volatile的意思是说:在jvm中,一个线程更新了共享变量i,另外一个线程立即去读取共享区中的i时,读到的可能不是刚才另外那个线程更新过的结果,这就类似数据库中的事务隔离级别中的read uncommitedvolatile就是解决这个问题的。

看到了下面这个静态方法,再结合AtomicIntegerFieldUpdater类的文档,就可以理解AtomicIntegerFieldUpdater类的作用了

AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName) 

线程池

线程池的概念与Executors类的应用

         创建固定大小的线程池

        创建缓存线程池

        创建单一线程池(如何实现线程死了以后重新启动?单一线程就能实现,一个死了,就会开启另一个,保证始终一个线程运行)

关闭线程池

shutdownshutdownNow的比较

      shutdown()当线程池调用该方法时,线程池的状态则立刻变成shudown状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。

     shutdownNow()  根据JDK文档描述,大致意思是:执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。 
     它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep waitCondition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出

用线程池启动定时器

   调用ScheduledExecutorServiceschedule方法,返回的ScheduleFuture对象可以取消任务。

   支持间隔重复任务的定时方式,不直接支持绝对定时方式,需要转换成相对时间方式。

关于线程池的讲解:

       首先介绍在Tcp服务器编程模型的原理,每一个客户端连接用一个单独的线程为之服务,当与客户端的会话结束时,线程也就结束了,即每来一个客户端连接,服务器端就要创建一个新线程。这好比假设每个报名学员都要通过我来亲自接待,以便给每个学员一种好的感觉,但每个学员报名手续要花费半个小时,对于50名同学,我一个个接待和为之办理手续,显然不实际,我会怎么做呢?我会先接待每一个学员,打完招呼后,再把他分配给一名工作人员去办理手续,这样,我就接待了每名学员。

         如果访问服务器的客户端很多,那么服务器要不断地创建和销毁线程,这将严重影响服务器的性能。如果真的来一名学员,我们都安排一名新工作人员为之服务,也是不可能的,那公司岂不是要招聘很多工作人员?而是应该一名工作人员服务完一名学员,空闲下来后,一旦有新的学员要服务,我又立即安排该工作人员为新学员服务。线程池的概念与此类似,首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。

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

固定大小的线程池&缓存线程池-----------------

步骤1:用3个大小的固定线程池去执行10个内部循环10次就结束的任务,为了观察固定线程池下的其他任务一直再等待,希望打印出正在执行的线程名、任务序号和任务内部的循环次数,刚开始看到只有3个线程在执行,并看到任务前仆后继的效果。

   注意:这10个任务要用各自独立的runnable对象,才能看到任务的序号。

  步骤2:改为缓存线程池,可以看到当前有多少个任务,就会分配多少个线程为之服务。


[html]  view plain copy print ?
  1. package cn.itcast.foundationsummary;  
  2. import java.util.concurrent.ExecutorService;  
  3. import java.util.concurrent.Executors;  
  4. import java.util.concurrent.ScheduledExecutorService;  
  5. import java.util.concurrent.TimeUnit;  
  6. public class ThreadPoolTest {  
  7. public static void main(String[] args) {  
  8. //ExecutorService service  =  Executors.newFixedThreadPool(3);固定线程池  
  9.           
  10.  ExecutorService   service  =  Executors.newCachedThreadPool();  
  11. for(int i=1;i<=10;i++){  
  12. final int sequence = i;  
  13. //仔细品味runnable对象放到循环里面和外面的区别,为了让每个对象有自己独立的编号  
  14. service.execute(new Runnable(){  
  15. public void run() {  
  16. try{Thread.sleep(200);}catch(Exception e){}  
  17. for(int j=1;j<=5;j++){  
  18. System.out.println(Thread.currentThread().getName() + "is serving "   
  19. + sequence + " task:" + "loop of " + j);  
  20. }  
  21. }  
  22. });  
  23. }  
  24. /*  
  25. 用下面这句代码来说明上面的代码是在提交任务,并且所有的任务都已经提交了,但任务是什么时候执行的,则是由线程池调度的!  
  26. */  
  27. System.out.println(“all task have committed!”);  
  28. //注意与service.shutdownNow()的区别。  
  29.         service.shutdown();  
  30. /*  
  31. */  
  32. ScheduledExecutorService scheduledService = Executors.newScheduledThreadPool(1);  
  33. //scheduleAtFixedRate  调度,用固定的频率,下面是5秒之后开始炸,然后每隔一秒再炸  
  34. scheduledService.scheduleAtFixedRate(  
  35. new Runnable(){  
  36. public void run() {  
  37. System.out.println("bomb!!!");  
  38. }},   
  39. 5,   
  40. 1,  
  41. TimeUnit.SECONDS);  
  42. }  
  43. }  


Callable&Future

--->Future取得的结果类型和Callable返回的结果类型必须一致,这是通过泛型来实现的。

--->Callable要采用ExecutorSevicesubmit方法提交,返回的future对象可以取消任务。

--->CompletionService用于提交一组Callable任务,其take方法返回已完成的一个Callable任务对应的Future对象。

     好比我同时种了几块地的麦子,然后就等待收割。收割时,则是那块先成熟了,则先去收割哪块麦子。

[java]  view plain copy print ?
  1. package cn.itcast.day3.thread;  
  2. 举例的程序代码如下:  
  3. import java.util.concurrent.Callable;  
  4. import java.util.concurrent.CompletionService;  
  5. import java.util.concurrent.ExecutionException;  
  6. import java.util.concurrent.ExecutorCompletionService;  
  7. import java.util.concurrent.ExecutorService;  
  8. import java.util.concurrent.Executors;  
  9. import java.util.concurrent.Future;  
  10. public class FutureTest {  
  11. public static void main(String[] args) {  
  12. ExecutorService service = Executors.newSingleThreadExecutor();  
  13. class MyCallable implements Callable<String>{  
  14. public String call() throws Exception {  
  15. for(int i=0;i<10;i++)  
  16. {  
  17. Thread.sleep(1000);  
  18. System.out.println("calling " + i);  
  19. }  
  20. return "hello";  
  21. }  
  22. }  
  23. //Future<String> future = service.submit(new MyCallable());  
  24. class MyCallable2<T> implements Callable<T>{  
  25. public T call() throws Exception {  
  26. for(int i=0;i<10;i++)  
  27. {  
  28. Thread.sleep(1000);  
  29. System.out.println("calling " + i);  
  30. }  
  31. return null;  
  32. }  
  33. }  
  34. Future<String> future = service.submit(new Callable<String>(){  
  35. public String call() throws Exception {  
  36. for(int i=0;i<10;i++)  
  37. {  
  38. Thread.sleep(1000);  
  39. System.out.println("calling " + i);  
  40. }  
  41. return "hello";  
  42. }  
  43. });  
  44. try {  
  45. //加上下面这句则暂停失败,还能看到上面打印的calling。  
  46. //Thread.sleep(2000);  
  47. future.cancel(false);  
  48. System.out.println(future.isCancelled());  
  49. if(false)  
  50. System.out.println(future.get());  
  51. catch (Exception e) {  
  52. e.printStackTrace();  
  53. }  
  54. System.out.println("end!");  
  55. service.shutdown();  
  56. }  
  57. }  


-----------------用了随机值的CompletionService------------------

[java]  view plain copy print ?
  1. CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(service);  
  2. for(int i=0;i<10;i++){  
  3. final Integer seq = i+1;  
  4. completionService.submit(new Callable<Integer>(){  
  5. public Integer call() throws Exception {  
  6. try{  
  7. Thread.sleep((long)(Math.random() * 1000));}catch(Exception e){}  
  8. return seq;  
  9. }  
  10. });  
  11. }  
  12. for(int i=0;i<10;i++){  
  13. Future<Integer> f = completionService.take();  
  14. System.out.println(f.get());  
  15. }  


--------------------------------------------------------------------------------------------------

Lock&Condition实现线程同步通信

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

Ø readWriteLock读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!

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

一个锁内部可以有多个Condition,即有多路等待和通知,可以参看jdk1.5提供的LockCondition实现的可阻塞队列的应用案例,从中除了要体味算法,还要体味面向对象的封装。在传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。(如果只用一个Condition,两个放的都在等,一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走。)

-----------------锁的例子----------------------------------

[java]  view plain copy print ?
  1. package thread;  
  2. import java.util.concurrent.ExecutorService;  
  3. import java.util.concurrent.Executors;  
  4. import java.util.concurrent.locks.Lock;  
  5. import java.util.concurrent.locks.ReentrantLock;  
  6. public class LockTest {  
  7. public static void main(String[] args) {  
  8. final Business business = new Business();  
  9. ExecutorService executor =  Executors.newFixedThreadPool(3);  
  10. for(int i=0;i<3;i++)  
  11. {  
  12. executor.execute(  
  13. new Runnable()  
  14. {  
  15. public void run()  
  16. {  
  17. business.service();  
  18. }  
  19. }  
  20. );  
  21. }  
  22. executor.shutdown();  
  23. }  
  24. private static class Business  
  25. {  
  26. private int count = 0;  
  27. Lock lock = new ReentrantLock();  
  28. public void service()  
  29. {  
  30. lock.lock();  
  31. try {  
  32. count++;  
  33. try {  
  34. Thread.sleep(1);  
  35. catch (InterruptedException e) {  
  36. e.printStackTrace();  
  37. }  
  38. System.out.println(count);  
  39. catch (RuntimeException e) {  
  40. e.printStackTrace();  
  41. }  
  42. finally  
  43. {  
  44. lock.unlock();  
  45. }  
  46. }  
  47. }  
  48. }  


-----------------------读写锁的例子---------------------------

注意:刚开始用eclipse for jee自己的jdk,没有看到读锁可以并发的效果,后来换成sunjdk,就看到了效果!

[java]  view plain copy print ?
  1. import java.util.concurrent.locks.ReentrantReadWriteLock;  
  2. public class ReadWriteLockTest {  
  3. public static void main(String[] args) {  
  4. final Queue3 q3 = new Queue3();  
  5. for(int i=0;i<3;i++)  
  6. {  
  7. new Thread(){  
  8. public void run(){  
  9. while(true){  
  10. q3.get();  
  11. }  
  12. }  
  13. }.start();  
  14. }  
  15. for(int i=0;i<3;i++)  
  16. {  
  17. new Thread(){  
  18. public void run(){  
  19. while(true){  
  20. q3.put(new Random().nextInt(10000));  
  21. }  
  22. }  
  23. }.start();  
  24. }  
  25. }  
  26. }  
  27. class Queue3{  
  28. private Object data = null;//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。  
  29. private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();  
  30. public void get(){  
  31. rwl.readLock().lock();  
  32. System.out.println(Thread.currentThread().getName() + " be ready to read data!");  
  33. try {  
  34. Thread.sleep((long)(Math.random()*1000));  
  35. catch (InterruptedException e) {  
  36. e.printStackTrace();  
  37. }  
  38. System.out.println(Thread.currentThread().getName() + “have read data :“ + data);  
  39. rwl.readLock().unlock();  
  40. }  
  41. public void put(Object data){  
  42. rwl.writeLock().lock();  
  43. System.out.println(Thread.currentThread().getName() + " be ready to write data!");  
  44. try {  
  45. Thread.sleep((long)(Math.random()*1000));  
  46. catch (InterruptedException e) {  
  47. e.printStackTrace();  
  48. }  
  49. this.data = data;  
  50. System.out.println(Thread.currentThread().getName() + " have write data: “ + data);  
  51. rwl.writeLock().unlock();  
  52. }  
  53. }  


------------------ Condition的例子1:实现两个线程交替执行-----------------------------


[java]  view plain copy print ?
  1. public class ConditionTest {  
  2. public static void main(String[] args) {  
  3. ExecutorService service = Executors.newSingleThreadExecutor();  
  4. final Business2 business = new Business2();  
  5. service.execute(new Runnable(){  
  6. public void run() {  
  7. for(int i=0;i<50;i++){  
  8. business.sub();  
  9. }  
  10. }  
  11. });  
  12. for(int i=0;i<50;i++){  
  13. business.main();  
  14. }  
  15. }  
  16. }  
  17. class Business2{  
  18. Lock lock = new ReentrantLock();  
  19. Condition condition = lock.newCondition();  
  20. boolean bShouldSub = true;  
  21. public void sub(){  
  22. lock.lock();  
  23. if(!bShouldSub)  
  24. try {  
  25. condition.await();  
  26. catch (InterruptedException e) {  
  27. e.printStackTrace();  
  28. }  
  29. try  
  30. {  
  31. for(int i=0;i<10;i++){  
  32. System.out.println(Thread.currentThread().getName() + " : " + i);  
  33. }  
  34. bShouldSub = false;  
  35. condition.signal();  
  36. }finally{  
  37. lock.unlock();  
  38. }  
  39. }  
  40. public void main(){  
  41. lock.lock();  
  42. if(bShouldSub)  
  43. try {  
  44. condition.await();  
  45. catch (InterruptedException e) {  
  46. // TODO Auto-generated catch block  
  47. e.printStackTrace();  
  48. }  
  49. try  
  50. {  
  51. for(int i=0;i<5;i++){  
  52. System.out.println(Thread.currentThread().getName() + " : " + i);  
  53. }  
  54. bShouldSub = true;  
  55. condition.signal();  
  56. }finally{  
  57. lock.unlock();  
  58. }  
  59. }  
  60. }  


--------- Condition的例子2:实现三个线程交替运行的效果--------------------------

[java]  view plain copy print ?
  1. import java.util.concurrent.locks.Condition;  
  2. import java.util.concurrent.locks.Lock;  
  3. import java.util.concurrent.locks.ReentrantLock;  
  4. public class SignalTest2 {  
  5. public static void main(String[] args) {  
  6. new SignalTest2().init();  
  7. }  
  8. private void init(){  
  9. final Business b = new Business();  
  10. new Thread(){  
  11. public void run(){  
  12. for(int i=0;i<50;i++)  
  13. b.main();  
  14. }  
  15. }.start();  
  16. new Thread(){  
  17. public void run(){  
  18. for(int i=0;i<50;i++)  
  19. b.sub();  
  20. }  
  21. }.start();  
  22. new Thread(){  
  23. public void run(){  
  24. for(int i=0;i<50;i++)  
  25. b.sub2();  
  26. }  
  27. }.start();  
  28. }  
  29. private class Business{  
  30. int status = 1;  
  31. Lock lock = new ReentrantLock();  
  32. Condition cond1 = lock.newCondition();  
  33. Condition cond2 = lock.newCondition();  
  34. Condition cond3 = lock.newCondition();  
  35. public  void main(){  
  36. lock.lock();  
  37. while(status != 1){  
  38. try{cond1.await();}catch(Exception e){}  
  39. }  
  40. for(int i=1;i<=5;i++){  
  41. try{Thread.sleep(200);}catch(Exception e){}  
  42. System.out.println(Thread.currentThread().getName() + ":" + i);  
  43. }  
  44. status = 2;  
  45. cond2.signal();  
  46. lock.unlock();  
  47. }  
  48. public  void sub(){  
  49. lock.lock();  
  50. while(status != 2){  
  51. try{cond2.await();}catch(Exception e){}  
  52. }  
  53. for(int i=1;i<=10;i++){  
  54. try{Thread.sleep(200);}catch(Exception e){}  
  55. System.out.println(Thread.currentThread().getName() + ":" + i);  
  56. }  
  57. status = 3;  
  58. cond3.signal();  
  59. lock.unlock();  
  60. }  
  61. public  void sub2(){  
  62. lock.lock();  
  63. while(status != 3){  
  64. try{cond3.await();}catch(Exception e){}  
  65. }  
  66. for(int i=1;i<=10;i++){  
  67. try{Thread.sleep(200);}catch(Exception e){}  
  68. System.out.println(Thread.currentThread().getName() + ":" + i);  
  69. }  
  70. status = 1;  
  71. cond1.signal();  
  72. lock.unlock();  
  73. }  
  74. }  
  75. }  


------------------------------------------------------------------------------------

Semaphore实现信号灯

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

    Semaphore实现的功能就类似厕所有5个坑,假如有十个人要上厕所,那么同时能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中的任何一个人让开后,其中在等待的另外5个人中又有一个可以占用了。

    另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。

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

[java]  view plain copy print ?
  1. public class SemaphoreTest {  
  2. public static void main(String[] args) {  
  3. ExecutorService service = Executors.newCachedThreadPool();  
  4. final  Semaphore sp = new Semaphore(3);  
  5. for(int i=0;i<10;i++){  
  6. Runnable runnable = new Runnable(){  
  7. public void run(){  
  8. try {  
  9. sp.acquire();  
  10. catch (InterruptedException e1) {  
  11. e1.printStackTrace();  
  12. }  
  13. System.out.println("线程" + Thread.currentThread().getName() +   
  14. "进入,当前已有" + (3-sp.availablePermits()) + "个并发");  
  15. try {  
  16. Thread.sleep((long)(Math.random()*10000));  
  17. catch (InterruptedException e) {  
  18. e.printStackTrace();  
  19. }  
  20. System.out.println("线程" + Thread.currentThread().getName() +   
  21. "即将离开");  
  22. sp.release();  
  23. //下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元  
  24. System.out.println("线程" + Thread.currentThread().getName() +   
  25. "已离开,当前已有" + (3-sp.availablePermits()) + "个并发");  
  26. }  
  27. };  
  28. service.execute(runnable);  
  29. }  
  30. }  
  31. }  

其他同步工具类

CyclicBarrier

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

CountDownLatch

      犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。这直接通过代码来说明CountDownLatch的作用,这样学员的理解效果更直接。可以实现一个人(也可以是多个人)等待其他所有人都来通知他,这犹如一个计划需要多个领导都签字后才能继续向下实施。还可以实现一个人通知多个人的效果,类似裁判一声口令,运动员同时开始奔跑。用这个功能做百米赛跑的游戏程序不错哦!

Exchanger

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

          Cyclic:循环的,有周期性的

         Barrier:障碍物,屏障 

         Latch:门闩,闩锁

讲解CyclicBarrier的功能时,通过辅助画图的方式说明,效果会更好。

\              /

  \     |    /

------------------------三个线程干完各自的任务,在不同的时刻到达集合点后,就可以接着忙各自的工作去了,再到达新的集合点,再去忙各自的工作,

到达集合点了用CyclicBarrier对象的await方法表示。

  /     |   \

/       |     \

-------------------

为什么几个人能碰到一起,说白了,就是大家都把手头这一阶段的工作做完了,就可以碰到一起了。譬如,我下楼等方老师,就是等他手头工作做完了,到达了它要集合的状态,就集合了。

-----------------CyclicBarrier的代码:---------------------------------

[java]  view plain copy print ?
  1. package cn.itcast.day3.thread;  
  2. import java.util.concurrent.CyclicBarrier;  
  3. import java.util.concurrent.ExecutorService;  
  4. import java.util.concurrent.Executors;  
  5. import java.util.concurrent.Semaphore;  
  6. public class CyclicBarrierTest {  
  7. public static void main(String[] args) {  
  8. ExecutorService service = Executors.newCachedThreadPool();  
  9. final  CyclicBarrier cb = new CyclicBarrier(3);  
  10. for(int i=0;i<3;i++){  
  11. Runnable runnable = new Runnable(){  
  12. public void run(){  
  13. try {  
  14. Thread.sleep((long)(Math.random()*10000));  
  15. System.out.println("线程" + Thread.currentThread().getName() +   
  16. "即将到达集合地点1,当前已有" + cb.getNumberWaiting() + "个已经到达,正在等候");  
  17. cb.await();  
  18. Thread.sleep((long)(Math.random()*10000));  
  19. System.out.println("线程" + Thread.currentThread().getName() +   
  20. "即将到达集合地点2,当前已有" + cb.getNumberWaiting() + "个已经到达,正在等候");  
  21. cb.await();  
  22. Thread.sleep((long)(Math.random()*10000));  
  23. System.out.println("线程" + Thread.currentThread().getName() +   
  24. "即将到达集合地点3,当前已有" + cb.getNumberWaiting() + "个已经到达,正在等候");  
  25. cb.await();  
  26. catch (Exception e) {  
  27. e.printStackTrace();  
  28. }  
  29. }  
  30. };  
  31. service.execute(runnable);  
  32. }  
  33. service.shutdown();  
  34. }  
  35. }  


-----------------CountdownLatch的代码:---------------------------------

[java]  view plain copy print ?
  1. package cn.itcast.day3.thread;  
  2. import java.util.concurrent.CountDownLatch;  
  3. import java.util.concurrent.CyclicBarrier;  
  4. import java.util.concurrent.ExecutorService;  
  5. import java.util.concurrent.Executors;  
  6. public class CountdownLatchTest {  
  7. public static void main(String[] args) {  
  8. ExecutorService service = Executors.newCachedThreadPool();  
  9. final CountDownLatch cdOrder = new CountDownLatch(1);  
  10. final CountDownLatch cdAnswer = new CountDownLatch(3);  
  11. for(int i=0;i<3;i++){  
  12. Runnable runnable = new Runnable(){  
  13. public void run(){  
  14. try {  
  15. System.out.println("线程" + Thread.currentThread().getName() +   
  16. "正准备接受命令");  
  17. cdOrder.await();  
  18. System.out.println("线程" + Thread.currentThread().getName() +   
  19. "已接受命令");  
  20. Thread.sleep((long)(Math.random()*10000));  
  21. System.out.println("线程" + Thread.currentThread().getName() +   
  22. "回应命令处理结果");  
  23. cdAnswer.countDown();  
  24. catch (Exception e) {  
  25. e.printStackTrace();  
  26. }  
  27. }  
  28. };  
  29. service.execute(runnable);  
  30. }  
  31. try {  
  32. Thread.sleep((long)(Math.random()*10000));  
  33. System.out.println("线程" + Thread.currentThread().getName() +   
  34. "即将发布命令");  
  35. cdOrder.countDown();  
  36. System.out.println("线程" + Thread.currentThread().getName() +   
  37. "已发送命令,正在等待结果");  
  38. cdAnswer.await();  
  39. System.out.println("线程" + Thread.currentThread().getName() +   
  40. "已收到所有响应结果");  
  41. catch (Exception e) {  
  42. e.printStackTrace();  
  43. }  
  44. service.shutdown();  
  45. }  
  46. }  


---------------------------ExchangerTest-------------------------

讲解Exchanger的比喻:好比两个毒贩要进行交易,一手交钱、一手交货,不管谁先来到接头地点后,就处于等待状态了,当另外一方也到达了接头地点(所谓到达接头地点,也就是到到达了准备接头的状态)时,两者的数据就立即交换了,然后就又可以各忙各的了。

exchange方法就相当于两手高高举着待交换物,等待人家前来交换,一旦人家到来(即人家也执行到exchange方法),则两者立马完成数据的交换。

[java]  view plain copy print ?
  1. package cn.itcast.day3.thread;  
  2. import java.util.concurrent.Exchanger;  
  3. import java.util.concurrent.ExecutorService;  
  4. import java.util.concurrent.Executors;  
  5. public class ExchangerTest {  
  6. public static void main(String[] args) {  
  7. ExecutorService service = Executors.newCachedThreadPool();  
  8. final Exchanger exchanger = new Exchanger();  
  9. service.execute(new Runnable(){  
  10. public void run() {  
  11. try {  
  12. Thread.sleep((long)(Math.random()*10000));  
  13. String data1 = "zxx";  
  14. System.out.println("线程" + Thread.currentThread().getName() +   
  15. "正在把数据" + data1 +"换出去");  
  16. String data2 = (String)exchanger.exchange(data1);  
  17. System.out.println("线程" + Thread.currentThread().getName() +   
  18. "换回的数据为" + data2);  
  19. }catch(Exception e){  
  20. }  
  21. }  
  22. });  
  23. service.execute(new Runnable(){  
  24. public void run() {  
  25. try {  
  26. Thread.sleep((long)(Math.random()*10000));  
  27. String data1 = "lhm";  
  28. System.out.println("线程" + Thread.currentThread().getName() +   
  29. "正在把数据" + data1 +"换出去");  
  30. String data2 = (String)exchanger.exchange(data1);  
  31. System.out.println("线程" + Thread.currentThread().getName() +   
  32. "换回的数据为" + data2);  
  33. }catch(Exception e){  
  34. }  
  35. }  
  36. });  
  37. }  
  38. }  


总结:这个程序虽然简单,但实实在在解决了一种应用问题,这就是新技术带来的价值,新技术的价值就犹如一个亿万富豪得了不治之症,目前没有什么药品可以医治,你发现了一种新药专门治这种病,你对他说,把他的亿万家产给你,你就把药片给他,你说他干不干?他绝对会干!这就是新药和新技术的威力嘛!新技术在关键时刻总能发挥特殊的作用,就看你遇到没遇到这种关键的时刻,一旦遇到,那就能产生很大价值了。

可阻塞的队列

什么是可阻塞队列,阻塞队列的作用与实际应用,阻塞队列的实现原理。

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

ArrayBlockingQueue

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

3个空间的队列来演示阻塞队列的功能和效果。

用两个具有1个空间的队列来实现同步通知的功能。

[java]  view plain copy print ?
  1. package cn.itcast.day3.thread;  
  2. import java.util.concurrent.ArrayBlockingQueue;  
  3. import java.util.concurrent.BlockingQueue;  
  4. public class BlockingQueueTest {  
  5. public static void main(String[] args) {  
  6. final BlockingQueue queue = new ArrayBlockingQueue(3);  
  7. for(int i=0;i<2;i++){  
  8. new Thread(){  
  9. public void run(){  
  10. while(true){  
  11. try {  
  12. Thread.sleep((long)(Math.random()*1000));  
  13. System.out.println(Thread.currentThread().getName() + "准备放数据!");  
  14. queue.put(1);  
  15. System.out.println(Thread.currentThread().getName() + "已经放了数据," +   
  16. "队列目前有" + queue.size() + "个数据");  
  17. catch (InterruptedException e) {  
  18. e.printStackTrace();  
  19. }  
  20. }  
  21. }  
  22. }.start();  
  23. }  
  24. new Thread(){  
  25. public void run(){  
  26. while(true){  
  27. try {  
  28. //将此处的睡眠时间分别改为100和1000,观察运行结果  
  29. Thread.sleep(1000);  
  30. System.out.println(Thread.currentThread().getName() + "准备取数据!");  
  31. queue.take();  
  32. System.out.println(Thread.currentThread().getName() + "已经取走数据," +   
  33. "队列目前有" + queue.size() + "个数据");  
  34. catch (InterruptedException e) {  
  35. e.printStackTrace();  
  36. }  
  37. }  
  38. }  
  39. }.start();  
  40. }  
  41. }  
  42. -------------------------  
  43. 在前面用Condition实现的同步通知的例子的基础上,改为用阻塞队列来实现。  
  44. 第一个线程:A.take()……..B.put()  
  45. 第二个线程:B.take()……..A.put()  
  46. package cn.itcast.day3.thread;  
  47. import java.util.concurrent.ArrayBlockingQueue;  
  48. import java.util.concurrent.BlockingQueue;  
  49. import java.util.concurrent.ExecutorService;  
  50. import java.util.concurrent.Executors;  
  51. import java.util.concurrent.locks.Condition;  
  52. import java.util.concurrent.locks.Lock;  
  53. import java.util.concurrent.locks.ReentrantLock;  
  54. public class BlockingQueueCondition {  
  55. public static void main(String[] args) {  
  56. ExecutorService service = Executors.newSingleThreadExecutor();  
  57. final Business3 business = new Business3();  
  58. service.execute(new Runnable(){  
  59. public void run() {  
  60. for(int i=0;i<50;i++){  
  61. business.sub();  
  62. }  
  63. }  
  64. });  
  65. for(int i=0;i<50;i++){  
  66. business.main();  
  67. }  
  68. }  
  69. }  
  70. class Business3{  
  71. BlockingQueue subQueue = new ArrayBlockingQueue(1);  
  72. BlockingQueue mainQueue = new ArrayBlockingQueue(1);  
  73. {  
  74. try {  
  75. mainQueue.put(1);  
  76. catch (InterruptedException e) {  
  77. e.printStackTrace();  
  78. }  
  79. }  
  80. public void sub(){  
  81. try  
  82. {  
  83. mainQueue.take();  
  84. for(int i=0;i<10;i++){  
  85. System.out.println(Thread.currentThread().getName() + " : " + i);  
  86. }  
  87. subQueue.put(1);  
  88. }catch(Exception e){  
  89. }  
  90. }  
  91. public void main(){  
  92. try  
  93. {  
  94. subQueue.take();  
  95. for(int i=0;i<5;i++){  
  96. System.out.println(Thread.currentThread().getName() + " : " + i);  
  97. }  
  98. mainQueue.put(1);  
  99. }catch(Exception e){  
  100. }  
  101. }  
  102. }  


同步集合

传统集合类在并发访问时的问题说明,见附件

传统方式下用Collections工具类提供的synchronizedCollection方法来获得同步集合,分析该方法的实现源码。

传统方式下的Collection在迭代集合时,不允许对集合进行修改。

用空中网面试的同步级线程题进行演示

根据AbstractListcheckForComodification方法的源码,分析产生ConcurrentModificationException异常的原因。

Java5中提供了如下一些同步集合类:

      通过看java.util.concurrent包下的介绍可以知道有哪些并发集合

           ConcurrentHashMap

          CopyOnWriteArrayList

          CopyOnWriteArraySet

-------新《java就业培训教程》中的并发与修改的简单例子------------

[java]  view plain copy print ?
  1. public class User implements Cloneable{  
  2. private String name;  
  3. private int age;  
  4. public User(String name, int age) {  
  5. this.name = name;  
  6. this.age = age;  
  7. }  
  8. public boolean equals(Object obj) {  
  9. if(this == obj) {  
  10. return true;  
  11. }  
  12. if(!(obj instanceof User)) {  
  13. return false;  
  14. }  
  15. User user = (User)obj;  
  16. //if(this.name==user.name && this.age==user.age)  
  17. if(this.name.equals(user.name)   
  18. && this.age==user.age) {  
  19. return true;  
  20. }  
  21. else {  
  22. return false;  
  23. }  
  24. }  
  25. public int hashCode() {  
  26. return name.hashCode() + age;  
  27. }  
  28. public String toString() {  
  29. return "{name:'" + name + "',age:" + age + "}";  
  30. }  
  31. public Object clone()  {  
  32. Object object = null;  
  33. try {  
  34. object = super.clone();  
  35. catch (CloneNotSupportedException e) {}  
  36. return object;  
  37. }  
  38. public void setAge(int age) {  
  39. this.age = age;  
  40. }  
  41. public String getName() {  
  42. return name;  
  43. }  
  44. }   


-------2----------

演示完本例子后,再用CopyOnWriteArrayList测试一下,应该就没这个问题了


[java]  view plain copy print ?
  1. import java.util.ArrayList;  
  2. import java.util.Collection;  
  3. import java.util.Iterator;  
  4. public class Ch15_Demo13 {  
  5. public static void main(String[] args) {  
  6. Collection users = new ArrayList();  
  7. users.add(new User("张三",28));  
  8. users.add(new User("李四",25));  
  9. users.add(new User("王五",31));  
  10. Iterator itrUsers = users.iterator();  
  11. while(itrUsers.hasNext()){  
  12. User user = (User)itrUsers.next();  
  13. if("张三".equals(user.getName())){  
  14. users.remove(user);  
  15. //itrUsers.remove();  
  16. else {  
  17. System.out.println(user);  
  18. }  
  19. }  
  20. }  
  21. }      


-------------------------空中网的线程同步题---------------------------------------------


[java]  view plain copy print ?
  1. pacage cn.itcast.foundationsummary;  
  2. import java.util.ArrayList;  
  3. import java.util.HashSet;  
  4. import java.util.Iterator;  
  5. import java.util.Random;  
  6. import java.util.Set;  
  7. import java.util.concurrent.CopyOnWriteArraySet;  
  8. public class TestDo {  
  9. private Set set = new CopyOnWriteArraySet();  
  10. private static TestDo _instance = new TestDo();  
  11. public static TestDo getInstance() {  
  12. return _instance;  
  13. }  
  14. // public synchronized void doSome(Object key, String value) {  
  15. public void doSome(Object key, String value) throws Exception {  
  16. // 以下代码是需要局部同步的代码  
  17. Object o = null;  
  18. if(!set.contains(key)){  
  19. set.add(key);  
  20. o = key ;  
  21. }  
  22. else{  
  23. for(Iterator it=set.iterator() ; it.hasNext() ;){  
  24. Object oo = it.next();  
  25. //为了更容易逮住ConcurrentModificationException出现的场景,加上下面这个暂停代码  
  26. try {  
  27. Thread.sleep(new Random().nextInt(100));  
  28. catch (Exception e) {  
  29. }  
  30. if(key.equals(oo)){  
  31. o = oo ;  
  32. break ;  
  33. }  
  34. }  
  35. }  
  36. synchronized (o)  
  37. {  
  38. try {  
  39. System.out.println(key + ":" + value + ":"  
  40. + (System.currentTimeMillis() / 1000));  
  41. catch (Exception e) {  
  42. e.printStackTrace();  
  43. }  
  44. Thread.sleep(1000);  
  45. }  
  46. }  
  47. private TestDo() {  
  48. }  
  49. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值