------------android培训、java培训、期待与您交流! -------------
一、传统线程机制的回顾
1、创建线程的两种传统方式
1) 在Thread子类覆盖的run方法中编写运行代码
问:能否在run方法声明上抛出InterruptedException异常,以便省略run方法内部 对Thread.sleep()语句的try…catch处理?
答:不能,因为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方法
分析静态方法所使用的同步监视器对象是什么?目标类的字节码文件对象
wait与notify实现线程间的通信
多个线程访问共享对象和数据的方式
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) 例如Strut2的ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,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、线程2 、线程3访问的GlobalData.var得到的对象是否是同一个?要反复强调这是同一个对象。但是,使用的GlobalData.var.get()得到数据是肯定同一个吗?那就不一定了!例如这里要讲的Threadlocal就可以为三个线程分别返回三个不同的值。
三个线程用如下代码来set这个GlobalData.var对象的值时
GlobalData.var.set(new Random().nextInt(10000));
最终存进去了几个值?这时候要为每个线程各自分别存储进去一个值,即总共存储进了三个值。
------------------------------
- 通过ThreadLocal类的示意代码进行原理分析:
- ThreadLocal
- {
- HashMap hashMap = new HashMap();
- void set(Object obj)
- {
- hashMap.put(Thread.currentThread(),obj);
- }
- object get()
- {
- return hashMap.get(Thread.currentThread());
- }
- }
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超链接。
要简要介绍下atomic和lock这两个子包,
Volatile的意思是说:在jvm中,一个线程更新了共享变量i,另外一个线程立即去读取共享区中的i时,读到的可能不是刚才另外那个线程更新过的结果,这就类似数据库中的事务隔离级别中的read uncommited,volatile就是解决这个问题的。
看到了下面这个静态方法,再结合AtomicIntegerFieldUpdater类的文档,就可以理解AtomicIntegerFieldUpdater类的作用了
AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName)
线程池
线程池的概念与Executors类的应用
创建固定大小的线程池
创建缓存线程池
创建单一线程池(如何实现线程死了以后重新启动?单一线程就能实现,一个死了,就会开启另一个,保证始终一个线程运行)
关闭线程池
shutdown与shutdownNow的比较
shutdown()当线程池调用该方法时,线程池的状态则立刻变成shudown状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
shutdownNow() 根据JDK文档描述,大致意思是:执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。
它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出
用线程池启动定时器
调用ScheduledExecutorService的schedule方法,返回的ScheduleFuture对象可以取消任务。
支持间隔重复任务的定时方式,不直接支持绝对定时方式,需要转换成相对时间方式。
关于线程池的讲解:
首先介绍在Tcp服务器编程模型的原理,每一个客户端连接用一个单独的线程为之服务,当与客户端的会话结束时,线程也就结束了,即每来一个客户端连接,服务器端就要创建一个新线程。这好比假设每个报名学员都要通过我来亲自接待,以便给每个学员一种好的感觉,但每个学员报名手续要花费半个小时,对于50名同学,我一个个接待和为之办理手续,显然不实际,我会怎么做呢?我会先接待每一个学员,打完招呼后,再把他分配给一名工作人员去办理手续,这样,我就接待了每名学员。
如果访问服务器的客户端很多,那么服务器要不断地创建和销毁线程,这将严重影响服务器的性能。如果真的来一名学员,我们都安排一名新工作人员为之服务,也是不可能的,那公司岂不是要招聘很多工作人员?而是应该一名工作人员服务完一名学员,空闲下来后,一旦有新的学员要服务,我又立即安排该工作人员为新学员服务。线程池的概念与此类似,首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,这就是封装。记住,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
固定大小的线程池&缓存线程池-----------------:
步骤1:用3个大小的固定线程池去执行10个内部循环10次就结束的任务,为了观察固定线程池下的其他任务一直再等待,希望打印出正在执行的线程名、任务序号和任务内部的循环次数,刚开始看到只有3个线程在执行,并看到任务前仆后继的效果。
注意:这10个任务要用各自独立的runnable对象,才能看到任务的序号。
步骤2:改为缓存线程池,可以看到当前有多少个任务,就会分配多少个线程为之服务。
- package cn.itcast.foundationsummary;
- 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;
- //仔细品味runnable对象放到循环里面和外面的区别,为了让每个对象有自己独立的编号
- 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);
- //scheduleAtFixedRate 调度,用固定的频率,下面是5秒之后开始炸,然后每隔一秒再炸
- scheduledService.scheduleAtFixedRate(
- new Runnable(){
- public void run() {
- System.out.println("bomb!!!");
- }},
- 5,
- 1,
- TimeUnit.SECONDS);
- }
- }
Callable&Future
--->Future取得的结果类型和Callable返回的结果类型必须一致,这是通过泛型来实现的。
--->Callable要采用ExecutorSevice的submit方法提交,返回的future对象可以取消任务。
--->CompletionService用于提交一组Callable任务,其take方法返回已完成的一个Callable任务对应的Future对象。
好比我同时种了几块地的麦子,然后就等待收割。收割时,则是那块先成熟了,则先去收割哪块麦子。
- package cn.itcast.day3.thread;
- 举例的程序代码如下:
- import java.util.concurrent.Callable;
- import java.util.concurrent.CompletionService;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.ExecutorCompletionService;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Future;
- public class FutureTest {
- public static void main(String[] args) {
- ExecutorService service = Executors.newSingleThreadExecutor();
- class MyCallable implements Callable<String>{
- public String call() throws Exception {
- for(int i=0;i<10;i++)
- {
- Thread.sleep(1000);
- System.out.println("calling " + i);
- }
- return "hello";
- }
- }
- //Future<String> future = service.submit(new MyCallable());
- class MyCallable2<T> implements Callable<T>{
- public T call() throws Exception {
- for(int i=0;i<10;i++)
- {
- Thread.sleep(1000);
- System.out.println("calling " + i);
- }
- return null;
- }
- }
- Future<String> future = service.submit(new Callable<String>(){
- public String call() throws Exception {
- for(int i=0;i<10;i++)
- {
- Thread.sleep(1000);
- System.out.println("calling " + i);
- }
- return "hello";
- }
- });
- try {
- //加上下面这句则暂停失败,还能看到上面打印的calling。
- //Thread.sleep(2000);
- future.cancel(false);
- System.out.println(future.isCancelled());
- if(false)
- System.out.println(future.get());
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("end!");
- service.shutdown();
- }
- }
-----------------用了随机值的CompletionService------------------
- CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(service);
- for(int i=0;i<10;i++){
- final Integer seq = i+1;
- completionService.submit(new Callable<Integer>(){
- public Integer call() throws Exception {
- try{
- Thread.sleep((long)(Math.random() * 1000));}catch(Exception e){}
- return seq;
- }
- });
- }
- for(int i=0;i<10;i++){
- Future<Integer> f = completionService.take();
- System.out.println(f.get());
- }
--------------------------------------------------------------------------------------------------
Lock&Condition实现线程同步通信
Ø Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。
Ø readWriteLock读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!
在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。
一个锁内部可以有多个Condition,即有多路等待和通知,可以参看jdk1.5提供的Lock与Condition实现的可阻塞队列的应用案例,从中除了要体味算法,还要体味面向对象的封装。在传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。(如果只用一个Condition,两个放的都在等,一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走。)
-----------------锁的例子----------------------------------
- package thread;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- public class LockTest {
- public static void main(String[] args) {
- final Business business = new Business();
- ExecutorService executor = Executors.newFixedThreadPool(3);
- for(int i=0;i<3;i++)
- {
- executor.execute(
- new Runnable()
- {
- public void run()
- {
- business.service();
- }
- }
- );
- }
- executor.shutdown();
- }
- private static class Business
- {
- private int count = 0;
- Lock lock = new ReentrantLock();
- public void service()
- {
- lock.lock();
- try {
- count++;
- try {
- Thread.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(count);
- } catch (RuntimeException e) {
- e.printStackTrace();
- }
- finally
- {
- lock.unlock();
- }
- }
- }
- }
-----------------------读写锁的例子---------------------------
注意:刚开始用eclipse for jee自己的jdk,没有看到读锁可以并发的效果,后来换成sun的jdk,就看到了效果!
- import java.util.concurrent.locks.ReentrantReadWriteLock;
- public class ReadWriteLockTest {
- public static void main(String[] args) {
- final Queue3 q3 = new Queue3();
- for(int i=0;i<3;i++)
- {
- new Thread(){
- public void run(){
- while(true){
- q3.get();
- }
- }
- }.start();
- }
- for(int i=0;i<3;i++)
- {
- new Thread(){
- public void run(){
- while(true){
- q3.put(new Random().nextInt(10000));
- }
- }
- }.start();
- }
- }
- }
- class Queue3{
- private Object data = null;//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。
- private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
- public void get(){
- rwl.readLock().lock();
- System.out.println(Thread.currentThread().getName() + " be ready to read data!");
- try {
- Thread.sleep((long)(Math.random()*1000));
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + “have read data :“ + data);
- rwl.readLock().unlock();
- }
- public void put(Object data){
- rwl.writeLock().lock();
- System.out.println(Thread.currentThread().getName() + " be ready to write data!");
- try {
- Thread.sleep((long)(Math.random()*1000));
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- this.data = data;
- System.out.println(Thread.currentThread().getName() + " have write data: “ + data);
- rwl.writeLock().unlock();
- }
- }
------------------ Condition的例子1:实现两个线程交替执行-----------------------------
- public class ConditionTest {
- public static void main(String[] args) {
- ExecutorService service = Executors.newSingleThreadExecutor();
- final Business2 business = new Business2();
- 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 Business2{
- Lock lock = new ReentrantLock();
- Condition condition = lock.newCondition();
- boolean bShouldSub = true;
- public void sub(){
- lock.lock();
- if(!bShouldSub)
- try {
- condition.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- try
- {
- for(int i=0;i<10;i++){
- System.out.println(Thread.currentThread().getName() + " : " + i);
- }
- bShouldSub = false;
- condition.signal();
- }finally{
- lock.unlock();
- }
- }
- public void main(){
- lock.lock();
- if(bShouldSub)
- try {
- condition.await();
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- try
- {
- for(int i=0;i<5;i++){
- System.out.println(Thread.currentThread().getName() + " : " + i);
- }
- bShouldSub = true;
- condition.signal();
- }finally{
- lock.unlock();
- }
- }
- }
--------- Condition的例子2:实现三个线程交替运行的效果--------------------------
- 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();
- }
- }
- }
------------------------------------------------------------------------------------
Semaphore实现信号灯
Semaphore可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。
Semaphore实现的功能就类似厕所有5个坑,假如有十个人要上厕所,那么同时能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中的任何一个人让开后,其中在等待的另外5个人中又有一个可以占用了。
另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。
单个信号量的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);
- }
- }
- }
其他同步工具类
CyclicBarrier
表示大家彼此等待,大家集合好后才开始出发,分散活动后又在指定地点集合碰面,这就好比整个公司的人员利用周末时间集体郊游一样,先各自从家出发到公司集合后,再同时出发到公园游玩,在指定地点集合后再同时开始就餐,…。
CountDownLatch
犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。这直接通过代码来说明CountDownLatch的作用,这样学员的理解效果更直接。可以实现一个人(也可以是多个人)等待其他所有人都来通知他,这犹如一个计划需要多个领导都签字后才能继续向下实施。还可以实现一个人通知多个人的效果,类似裁判一声口令,运动员同时开始奔跑。用这个功能做百米赛跑的游戏程序不错哦!
Exchanger
用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人将一直等待第二个人拿着数据到来时,才能彼此交换数据。
Cyclic:循环的,有周期性的
Barrier:障碍物,屏障
Latch:门闩,闩锁
讲解CyclicBarrier的功能时,通过辅助画图的方式说明,效果会更好。
\ /
\ | /
------------------------三个线程干完各自的任务,在不同的时刻到达集合点后,就可以接着忙各自的工作去了,再到达新的集合点,再去忙各自的工作,
到达集合点了用CyclicBarrier对象的await方法表示。
/ | \
/ | \
-------------------
为什么几个人能碰到一起,说白了,就是大家都把手头这一阶段的工作做完了,就可以碰到一起了。譬如,我下楼等方老师,就是等他手头工作做完了,到达了它要集合的状态,就集合了。
-----------------CyclicBarrier的代码:---------------------------------
- package cn.itcast.day3.thread;
- import java.util.concurrent.CyclicBarrier;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Semaphore;
- public class CyclicBarrierTest {
- public static void main(String[] args) {
- ExecutorService service = Executors.newCachedThreadPool();
- final CyclicBarrier cb = new CyclicBarrier(3);
- for(int i=0;i<3;i++){
- Runnable runnable = new Runnable(){
- public void run(){
- try {
- Thread.sleep((long)(Math.random()*10000));
- System.out.println("线程" + Thread.currentThread().getName() +
- "即将到达集合地点1,当前已有" + cb.getNumberWaiting() + "个已经到达,正在等候");
- cb.await();
- Thread.sleep((long)(Math.random()*10000));
- System.out.println("线程" + Thread.currentThread().getName() +
- "即将到达集合地点2,当前已有" + cb.getNumberWaiting() + "个已经到达,正在等候");
- cb.await();
- Thread.sleep((long)(Math.random()*10000));
- System.out.println("线程" + Thread.currentThread().getName() +
- "即将到达集合地点3,当前已有" + cb.getNumberWaiting() + "个已经到达,正在等候");
- cb.await();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- };
- service.execute(runnable);
- }
- service.shutdown();
- }
- }
-----------------CountdownLatch的代码:---------------------------------
- package cn.itcast.day3.thread;
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.CyclicBarrier;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- public class CountdownLatchTest {
- public static void main(String[] args) {
- ExecutorService service = Executors.newCachedThreadPool();
- final CountDownLatch cdOrder = new CountDownLatch(1);
- final CountDownLatch cdAnswer = new CountDownLatch(3);
- for(int i=0;i<3;i++){
- Runnable runnable = new Runnable(){
- public void run(){
- try {
- System.out.println("线程" + Thread.currentThread().getName() +
- "正准备接受命令");
- cdOrder.await();
- System.out.println("线程" + Thread.currentThread().getName() +
- "已接受命令");
- Thread.sleep((long)(Math.random()*10000));
- System.out.println("线程" + Thread.currentThread().getName() +
- "回应命令处理结果");
- cdAnswer.countDown();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- };
- service.execute(runnable);
- }
- try {
- Thread.sleep((long)(Math.random()*10000));
- System.out.println("线程" + Thread.currentThread().getName() +
- "即将发布命令");
- cdOrder.countDown();
- System.out.println("线程" + Thread.currentThread().getName() +
- "已发送命令,正在等待结果");
- cdAnswer.await();
- System.out.println("线程" + Thread.currentThread().getName() +
- "已收到所有响应结果");
- } catch (Exception e) {
- e.printStackTrace();
- }
- service.shutdown();
- }
- }
---------------------------ExchangerTest-------------------------
讲解Exchanger的比喻:好比两个毒贩要进行交易,一手交钱、一手交货,不管谁先来到接头地点后,就处于等待状态了,当另外一方也到达了接头地点(所谓到达接头地点,也就是到到达了准备接头的状态)时,两者的数据就立即交换了,然后就又可以各忙各的了。
exchange方法就相当于两手高高举着待交换物,等待人家前来交换,一旦人家到来(即人家也执行到exchange方法),则两者立马完成数据的交换。
- package cn.itcast.day3.thread;
- import java.util.concurrent.Exchanger;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- public class ExchangerTest {
- public static void main(String[] args) {
- ExecutorService service = Executors.newCachedThreadPool();
- final Exchanger exchanger = new Exchanger();
- service.execute(new Runnable(){
- public void run() {
- try {
- Thread.sleep((long)(Math.random()*10000));
- String data1 = "zxx";
- System.out.println("线程" + Thread.currentThread().getName() +
- "正在把数据" + data1 +"换出去");
- String data2 = (String)exchanger.exchange(data1);
- System.out.println("线程" + Thread.currentThread().getName() +
- "换回的数据为" + data2);
- }catch(Exception e){
- }
- }
- });
- service.execute(new Runnable(){
- public void run() {
- try {
- Thread.sleep((long)(Math.random()*10000));
- String data1 = "lhm";
- System.out.println("线程" + Thread.currentThread().getName() +
- "正在把数据" + data1 +"换出去");
- String data2 = (String)exchanger.exchange(data1);
- System.out.println("线程" + Thread.currentThread().getName() +
- "换回的数据为" + data2);
- }catch(Exception e){
- }
- }
- });
- }
- }
总结:这个程序虽然简单,但实实在在解决了一种应用问题,这就是新技术带来的价值,新技术的价值就犹如一个亿万富豪得了不治之症,目前没有什么药品可以医治,你发现了一种新药专门治这种病,你对他说,把他的亿万家产给你,你就把药片给他,你说他干不干?他绝对会干!这就是新药和新技术的威力嘛!新技术在关键时刻总能发挥特殊的作用,就看你遇到没遇到这种关键的时刻,一旦遇到,那就能产生很大价值了。
可阻塞的队列
什么是可阻塞队列,阻塞队列的作用与实际应用,阻塞队列的实现原理。
阻塞队列与Semaphore有些相似,但也不同,阻塞队列是一方存放数据,另一方释放数据,Semaphore通常则是由同一方设置和释放信号量。
ArrayBlockingQueue
只有put方法和take方法才具有阻塞功能
用3个空间的队列来演示阻塞队列的功能和效果。
用两个具有1个空间的队列来实现同步通知的功能。
- package cn.itcast.day3.thread;
- import java.util.concurrent.ArrayBlockingQueue;
- import java.util.concurrent.BlockingQueue;
- public class BlockingQueueTest {
- public static void main(String[] args) {
- final BlockingQueue queue = new ArrayBlockingQueue(3);
- for(int i=0;i<2;i++){
- new Thread(){
- public void run(){
- while(true){
- try {
- Thread.sleep((long)(Math.random()*1000));
- System.out.println(Thread.currentThread().getName() + "准备放数据!");
- queue.put(1);
- System.out.println(Thread.currentThread().getName() + "已经放了数据," +
- "队列目前有" + queue.size() + "个数据");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }.start();
- }
- new Thread(){
- public void run(){
- while(true){
- try {
- //将此处的睡眠时间分别改为100和1000,观察运行结果
- Thread.sleep(1000);
- System.out.println(Thread.currentThread().getName() + "准备取数据!");
- queue.take();
- System.out.println(Thread.currentThread().getName() + "已经取走数据," +
- "队列目前有" + queue.size() + "个数据");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }.start();
- }
- }
- -------------------------
- 在前面用Condition实现的同步通知的例子的基础上,改为用阻塞队列来实现。
- 第一个线程:A.take()……..B.put()
- 第二个线程:B.take()……..A.put()
- package cn.itcast.day3.thread;
- 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){
- }
- }
- }
同步集合
传统集合类在并发访问时的问题说明,见附件
传统方式下用Collections工具类提供的synchronizedCollection方法来获得同步集合,分析该方法的实现源码。
传统方式下的Collection在迭代集合时,不允许对集合进行修改。
用空中网面试的同步级线程题进行演示
根据AbstractList的checkForComodification方法的源码,分析产生ConcurrentModificationException异常的原因。
Java5中提供了如下一些同步集合类:
通过看java.util.concurrent包下的介绍可以知道有哪些并发集合
ConcurrentHashMap
CopyOnWriteArrayList
CopyOnWriteArraySet
-------新《java就业培训教程》中的并发与修改的简单例子------------
- public class User implements Cloneable{
- private String name;
- private int age;
- public User(String name, int age) {
- this.name = name;
- this.age = age;
- }
- public boolean equals(Object obj) {
- if(this == obj) {
- return true;
- }
- if(!(obj instanceof User)) {
- return false;
- }
- User user = (User)obj;
- //if(this.name==user.name && this.age==user.age)
- if(this.name.equals(user.name)
- && this.age==user.age) {
- return true;
- }
- else {
- return false;
- }
- }
- public int hashCode() {
- return name.hashCode() + age;
- }
- public String toString() {
- return "{name:'" + name + "',age:" + age + "}";
- }
- public Object clone() {
- Object object = null;
- try {
- object = super.clone();
- } catch (CloneNotSupportedException e) {}
- return object;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public String getName() {
- return name;
- }
- }
-------2----------
演示完本例子后,再用CopyOnWriteArrayList测试一下,应该就没这个问题了
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Iterator;
- public class Ch15_Demo13 {
- public static void main(String[] args) {
- Collection users = new ArrayList();
- users.add(new User("张三",28));
- users.add(new User("李四",25));
- users.add(new User("王五",31));
- Iterator itrUsers = users.iterator();
- while(itrUsers.hasNext()){
- User user = (User)itrUsers.next();
- if("张三".equals(user.getName())){
- users.remove(user);
- //itrUsers.remove();
- } else {
- System.out.println(user);
- }
- }
- }
- }
-------------------------空中网的线程同步题---------------------------------------------
- pacage cn.itcast.foundationsummary;
- import java.util.ArrayList;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.Random;
- import java.util.Set;
- import java.util.concurrent.CopyOnWriteArraySet;
- public class TestDo {
- private Set set = new CopyOnWriteArraySet();
- private static TestDo _instance = new TestDo();
- public static TestDo getInstance() {
- return _instance;
- }
- // public synchronized void doSome(Object key, String value) {
- public void doSome(Object key, String value) throws Exception {
- // 以下代码是需要局部同步的代码
- Object o = null;
- if(!set.contains(key)){
- set.add(key);
- o = key ;
- }
- else{
- for(Iterator it=set.iterator() ; it.hasNext() ;){
- Object oo = it.next();
- //为了更容易逮住ConcurrentModificationException出现的场景,加上下面这个暂停代码
- try {
- Thread.sleep(new Random().nextInt(100));
- } catch (Exception e) {
- }
- if(key.equals(oo)){
- o = oo ;
- break ;
- }
- }
- }
- synchronized (o)
- {
- try {
- System.out.println(key + ":" + value + ":"
- + (System.currentTimeMillis() / 1000));
- } catch (Exception e) {
- e.printStackTrace();
- }
- Thread.sleep(1000);
- }
- }
- private TestDo() {
- }
- }