java--加强之 Java5的线程并发库

转载请申明出处:http://blog.csdn.net/xmxkf/article/details/9945499

01. 传统线程技术回顾

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

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

涉及一个以往知识点,能否在run方法声明上抛出InterruptedException异常?以便省略run方法内部对Thread.sleep()语句的try……catch处理?

答:不能,因为Thread类的run方法没有抛异常,子类覆盖run方法时也不能抛异常。

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

Thread thread1 =new Thread(){

@Override

publicvoid run()

{

while(true)

{

try {

Thread.sleep(500);

} //不能让run方法throws异常,因为父类run没有抛异常

catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName());//this.getName()也行

}

}

};

thread1.start();

//创建Thread对象,并传入一个Runnble对象到构造方法

Thread thread2 =
new
Thread(new Runnable(){

@Override

publicvoid run()

{

while(true)

{

try {

Thread.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

//此处就不能用this.getName,因为this代表Runnable子类对象

System.out.println(Thread.currentThread().getName());

}

}

});

这两种方式有什么区别?为什么通常人们采用第二种(实现Runnable接口)?

都是创建一个线程,只是第二种方式更加体现面向对象的思想;创建一个线程,线程要运行的代码封装到Runnable对象中(run方法)。

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

Public void run()

{

if(target != null)

{//当Thread构造方法中传递了Runnable对象就执行Runnable的run方法。

target.run();

}

}

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

答:(Thread子类的run),因为子类覆盖了父类的run方法,子类对象调用start方法时,会到自己的类中找run方法,如果没有找到就会执行父类的run方法,而父类(Thread)的run方法中会去调用Runnable对象的run方法。只要覆盖了Thread中的run方法,就不会执行Runnable对象的run方法。

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

5、多线程机制会提高程序的运行效率吗?为什么会有多线程下载呢?

答:多线程不会提高程序的运行效率甚至更慢,因为cpu同一时间只能执行一个线程,如果要在几个线程之间切换需要时间。

计算机并没有快,只是抢了服务器的带宽。因为多(10)个线程下载,服务器以为多(10)个客户端在下载,所以给每个客服端提供20k,合起来就200k。

02. 传统定时器技术回顾

java.util

Timer

一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。

void

schedule(TimerTask task, long delay)

安排在指定延迟后执行指定的任务。

void

schedule(TimerTask task, long delay, long period)

安排指定的任务从指定的延迟后开始进行重复的固定延迟执行

java.util

TimerTask  由 Timer安排为一次执行或重复执行的任务。实现了Runnable接口。

boolean

cancel()

取消此计时器任务。

abstract void

run()

此计时器任务要执行的操作。

定时器的应用:

//两个时间间隔发生,方式一:定义计数器

class MyTimerTaskextends TimerTask

{

@Override

publicvoid run()

{

count= (count+1)%2;

System.out.println("bombing!");

new Timer().schedule(new
MyTimerTask(), 2000+2000*count);

}

}

new Timer().schedule(new MyTimerTask(), 2000); //启动炸弹,2秒后

03. 传统线程互斥技术

线程的互斥与同步通信:

1、线程安全问题可以用银行转账来解释;两个业务同时拿到余额,一个存一个取。

2、使用synchronized代码块及其原理;  任意对象都可作为锁

3、使用synchronized方法;   this

4、分析静态方法所使用的同步监视器对象是什么?

方法所在类文件字节码

总结:如果多个线程执行的代码要求同步,需要加上synchronized;即一个线程没有将synchronized修饰的代码执行完时,其他线程不能进来执行。这里要注意,需要同步的代码(同一份)可以定义在不同的块中,但是为了同步,必须保证使用同一把锁。

线程的同步互斥的图文解说:

线程1        线程2         synchronized(obj)的作用主要是其中obj的作用

|               |

-------------------------------         a和b这两段代码被两个线程执行时要互斥;

a………       b……         要互斥的代码必须用synchronized代码块包围。

第二组代码要互斥,即c与d互斥,但a与c不互斥

线程3        线程4        cpu可同时进入a和c中进行替换

|               |

-------------------------------       用什么进行互斥分组?就是synchronized中的obj

c………         d……       值不同,而分为不同的互斥组。

04. 传统线程同步通信技术

5、wait与notify实现线程间的通信;

用面试宝典中的子线程循环10次和主线程循环5次,两者交替运行50的例子讲解;

为了观察程序的运行结果,可以在eclipse中设置运行对话框,让结果重定向到一个文本文件,然后用UE去查看该文件中的特殊的行号处是否正好对应了线程的切换点;

经验:要用到共同数据(包括同步锁)或同步算法的若干个方法应该归在同一个类身上,这种设计正好体现了高类聚和程序的健壮性。

package cn.itcast.heima;

/**

*
面试题:子线程循环10次,接着主线程循环10次;

*
接着又回到子线程循环10次,然后再让主线程循环10此。

*
交替循环50次

* @author Administrator

*

*/

publicclass TraditionalThreadCommunication

{

publicstaticvoid main(String[] args)

{

final Business business =new Business();    //为什么要加final

//子线程

new Thread(new Runnable(){

publicvoid run()

{

for(int i=1;i<=50;i++)

{

business.sub(i);       //每次循环,执行十次,然后等待唤醒main

}

}

}).start();

for(int i=1;i<=50;i++)

{

business.main(i);

}

}

//此处不加static,在main方法中new内部类对象会出错,

//是为什么?因为静态只能访问静态?

staticclass Business

{

privatebooleanbShouldSub =true;

publicsynchronizedvoid sub(int
i)

{

while(!bShouldSub)  //不该子线程运行了就让子线程等

{

try {

this.wait();

}
catch
(InterruptedException e) {

e.printStackTrace();

}

}

for(int j=1;j<=10;j++)

{

System.out.println("sub Thread sequece of"+j+",loop
of "+i);

}

bShouldSub=false;

this.notify();  //唤醒主线程

}

publicsynchronizedvoid main(int
i)

{

while(bShouldSub)

{

try {

this.wait();

}
catch
(InterruptedException e) {

e.printStackTrace();

}

}

for(int j=1;j<=20;j++)

{

System.out.println("main Thread "+j+",loop
of "+i);

}

bShouldSub=true;

this.notify();  //唤醒子线程

}

}

}

05. 线程范围内共享变量的概念与作用

publicclass ThreadScopeShareData

{

//存放线程和数据的映射关系

privatestatic Map<Thread, Integer>threadData
=new HashMap<Thread, Integer>();

publicstaticvoid main(String[] args)

{

for(int i=0;i<2;i++)  //产生两个线程

{

new Thread(new Runnable(){

publicvoid run()

{

int data =new Random().nextInt();

System.out.println(Thread.currentThread().getName()+" get data 
"+data);

threadData.put(Thread.currentThread(), data);

new A().get();

new B().get();

}

}).start();

}

}

06. ThreadLocal类及应用技巧

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

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

2、ThreadLocal的应用场景:

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

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

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

实例:

publicclass ThreadLocalTest

{

//定义一个全局变量的ThreaadLocal变量,只能存放一个数据

privatestatic ThreadLocal<Integer>x
=new ThreadLocal<Integer>();

//定义一个全局变量的ThreaadLocal变量,存放一个对象,线程的所有数据封装在这个对象中

privatestatic ThreadLocal<MyThreadScopeData>myThreadScopeData
=

new ThreadLocal<MyThreadScopeData>();

publicstaticvoid main(String[] args)

{

for(int i=0;i<2;i++)  //产生两个线程

{

new Thread(new Runnable(){

publicvoid run()

{

int data =new Random().nextInt();

System.out.println(Thread.currentThread().getName()+" get data 
"+data);

x.set(data); //向ThreadLocal变量x中存储一个随机值

MyThreadScopeData.getThreadInstance().setName("name"+data);

MyThreadScopeData.getThreadInstance().setAge(data);

//调用其他类的方法,获取x的值,多个类在同一线程中获取到的值是相同的

new A().get();

new B().get();

}

}).start();

}

}

staticclass A

{

publicvoid get()

{

//读取这个ThreadLocal变量的值

int data =x.get();

System.out.println("A from "+Thread.currentThread().getName()+"
get data: "+data);

MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();

System.out.println("A from "+Thread.currentThread().getName()

+" getMyData: "+ myData.getName()+","+myData.getAge());

}

}

staticclass B

{

publicvoid get()

{

int data =x.get();

System.out.println("B from "+Thread.currentThread().getName()+"
get data: "+data);

MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();

System.out.println("B from "+Thread.currentThread().getName()

+" getMyData: "+ myData.getName()+","+myData.getAge());

}

}

//将线程的局部变量封装成对象,这样ThreadLocal中存放一个对象就等于存了几个数据

staticclass MyThreadScopeData

{

//单例设计模式

private MyThreadScopeData(){}

publicstatic/*synchronized*/ MyThreadScopeData
getThreadInstance()

{

MyThreadScopeData instance =map.get();

if(instance ==null)

{

instance =new MyThreadScopeData();

map.set(instance);

}

return instance;

}

//private static MyThreadScopeData instance = null;

privatestatic ThreadLocal<MyThreadScopeData>map
=

new ThreadLocal<MyThreadScopeData>();

private Stringname;

privateintage;

public String getName() {

returnname;

}

publicvoid setName(String name) {

this.name = name;

}

publicint getAge() {

returnage;

}

publicvoid setAge(int
age) {

this.age = age;

}

}

}

07. 多个线程之间共享数据的方式探讨

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

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

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

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

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

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

08. java5原子性操作类的应用

1、了解java.util.concurrent.atomic包;

看atomic包文档页下面的介绍,可以对基本数据AtomicInteger,对数组中的基本数据AtomicIntegerArray,对类中的基本数据AtomicIntegerFieldUpdater等进行操作。

09. java5线程并发库的应用

Java5中的线程并发库:

2、看java.util.concurrent包及子包的API帮助文档;

看concurrent包的帮助文档,对并发库中涉及的内容有一个总体上的介绍。

类Executors

此包中所定义的ExecutorExecutorServiceScheduledExecutorServiceThreadFactoryCallable类的工厂和实用方法

3、了解java.util.concurrent.lock包

线程池:

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

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

B、创建固定大小的线程池:

ExecutorService threadPool = Executors.newFixedThreadPool(3);

创建缓存线程池:

ExecutorService threadPool = Executors.newCachedThreadPool();

创建单一线程池:(如何实现线程死掉后重新启动一个线程)

ExecutorService threadPool = Executors.newSingleThreadExecutor();

C、关闭线程池:

void

shutdown()

启动一次顺序关闭,执行以前提交的任务,但不接受新任务。

List<Runnable>

shutdownNow()

试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。

D、用线程池启动定时器:

调用ScheduleExecutorService的schedule方法,返回的SchedulFuture对象可以取消任务。

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

实例:

Executors.newScheduledThreadPool(3).scheduleAtFixedRate(

new Runnable(){@Override

publicvoid run() {

System.out.println(Thread.currentThread()+" bombing!");

//Thread[pool-2-thread-1,5,main]  bombing!

}},

1,    //10秒后炸

2,    //以后每隔2秒炸一次

TimeUnit.SECONDS);

10. Callable与Future的应用

java.util.concurrent

接口 Callable<V>

返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做call 的方法。Callable 接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是Runnable
不会返回结果,并且无法抛出经过检查的异常。

接口 Future<V>
表示异步计算的结果

//创建线程池

ExecutorService threadPool2 = Executors.newSingleThreadExecutor();   CompletionService<Integer> completionService =

new ExecutorCompletionService(threadPool2);

for(int i=1;i<=10;i++) //提交10个任务,任务会返回结果

{

finalint seq = i;

completionService.submit(new Callable<Integer>() {

public Integer call()throws
Exception {

Thread.sleep(new Random().nextInt(5000));//等待5秒以内随机

return seq;

}

});

}

//获取任务执行结果

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

{

try {

//获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。

System.out.println(completionService.take().get());

} catch (Exception e) {

e.printStackTrace();

}

}

11. java5的线程锁技术

java.util.concurrent.locks

接口 LockLock

实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

锁本身也应该是一个对象,两个线程执行的代码片段要实现同步互斥,他们必须使用用一个Lock对象。锁是上在代表要操作的资源的类的内部方法中,而不是线程代码中。

接口 Condition

ConditionObject 监视器方法(waitnotify
notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock
实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

12. java5读写锁技术的妙用

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

一个面试题:写一个缓存类。

import java.util.*;

import java.util.concurrent.locks.*;

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**

*
写一个缓存系统,

*
获取数据,当系统中有这个数据时,直接返回这个数据,、

*
当没有这个数据时,去数据库中找,找到后缓存起来,

*
下次获取这个数据时就直接返回。

* @author Administrator

*

*/

publicclass CacheDemo

{

private Map<String, Object>cache =new
HashMap<String, Object>();

publicstaticvoid main(String[] args)

{

}

private ReadWriteLockrw1 =new
ReentrantReadWriteLock();

public Object getData(String key)

{

rw1.readLock().lock();    //多个线程可同时读

Object value =
null
;

try{

value =
cache.get(key);

if(value ==null) //缓存中没有这个对象

{

rw1.readLock().unlock();

rw1.writeLock().lock();   //只有一个线程去从数据库拿

try{

//如果3个线程同时来读,到rw1.writeLock().lock();

//有两个线程等待了,当第一个线程已经从数据库中拿数据

//避免另外两个线程醒来时又去拿,还要判断一下

if(value ==null)

{

value ="aaaa";  //实际是去queryDB

}

}finally{

rw1.writeLock().unlock();

}

rw1.readLock().lock();

}

}finally{

rw1.readLock().unlock();

}

return value;

}

}

上一篇:java架构《并发线程高级篇一》


下一篇:使用 vue-cli 实现组件之间数据交换