1并发的多面性
并发编程令人困惑的一个主要原因是是:使用并发时需要解决的问题有多个,而实现并发的方式也有多种,并且这两者之间没有明显的映射关系。
用并发解决的问题大体上分为“速度”和“设计可管理性”两种。
1、更快的执行
并发是用于多处理器编程的基本工具。
但是,并发通常是提高运行在单处理器上的程序的性能。(但是,会出现上下文切换的代价)
但是,单处理器上,还可以处理阻塞的问题。
在处理单处理器系统中的性能提高的常见示例是事件驱动编程。
实现并发最直接的方式是操作系统级别使用进程。(但是,任务之间对资源的使用是一个问题)
2、改进代码设计
协作多线程。Java的线程机制是抢占式的。
2基本的线程机制
1、定义任务
线程可以驱动任务,因此你需要一种描述任务的方式,这可以由Runnable接口来提供。
要想定义任务,只需实现Runnable接口并发编写run()方法,是的该任务可以执行你的命令。
demo:
2、Thread类
将Runnable对象转变为工作任务的传统方式是把他提交给一个Thread构造器。
Thread构造器只需要一个Runnable对象。调用Thread对象的start()方法为该线程执行必需的初始化操作,然后调用Runnable的run()方法,以便在这个新线程中启动该任务。
3、使用Executor
Java SE5的java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程。
Executor在客户端和任务执行之间提供了一个间接层;与客户端直接执行任务不同,这个中介对象将执行任务。
demo:
单个的Executor被用来创建和管理系统中所有的任务。
对shutDown方法的调用可以防止新任务被提交给这个Executor,当前线程将继续运行在shutDown()被调用之前的任务。完成任务后Executor会尽快退出。
Executors.newFixedThreadPool(5)--FixedThreadPool:
创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。
一次性预先执行代价高昂的线程分配,因而也就可以限制线程的数量了。
相反Executors.newCachedThreadPool()通常会创建与所需数量相同的线程,然后在他回收就线程时停止创建新线程。
SingleThreadExecutor就像是线程数量为1的FixedThreadPool。
4、从任务中产生返回值
如果你希望任务在完成时能够返回一个值,可以实现Callable接口。
在Java SE5中引用的Callable是一种具有类型参数的泛型,他的类型参数表示是从方法call()中返回的值,并且必须是使用ExecutorService.submit()方法调用他。
demo:
5、休眠
Java SE5引入了更加显示的sleep()版本作为TimeUnit类的一部分,这个方法允许你指定sleep()延迟的时间单元,因此可以提供更好的可阅读性。TimeUnit还可以被用来执行转换,就想稍后你会在本书中看到的那样。
6、优先级
线程的优先级将该线程的重要性传递给了调度器。
你可以用getPriority()来读取现有线程的优先级,并且在任何时刻都可以通过setPriority()来修改他。
demo:
一别建议使用三个常量:Thread.MIN_PRIORITY、NORM_PRIORITY、Thread.MAX_PRIORITY。
7、让步
如果知道已经完成了在run()方法的循环的一次迭代过程中所需要的工作,就可以给线程调度一个机制暗示:你的工作已经做的差不多了,可以让给别的线程使用CPU了。通过调用yield()来实现。
8、后台进程
所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺部分。
(当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中所有后台线程。反过来说,只要有任何非后台线程还在运行,程序就不会终止。)
demo:
使用Executor,且实现后台线程:
Demo:
你还可以考虑和Thread.yield()结合来转交控制权等。
示例:
finally的内容会打印么?
Demo:
9、编码的变体
Demo:
测试:
10、术语
描述将要执行的工作使用术语“任务”,只有在引用到驱动任务的具体机制时,才使用“线程”。因为,如果你在概念级别上讨论系统,那就可以指使用“任务”,而压根不需要提及驱动机制。
11、加入一个线程
一个线程可以在其他线程之上调用join()方法,起效果是等待一段时间直到第二个线程结束才继续执行。
如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(即t.isAlive()返回为假)。
也可以在调用join()时带上一个超时参数,这样如果目标线程在这段时间到期时还没有结束回话,join()方法总能返回。
对join()方法的调用可以被中断,做法是调用线程上调用interrupt()方法,这是需要用到try-catch子句。
12、创建有相应的用户界面
System.in.read();
13、线程组
Joshua Bloch:“最好吧线程组看成是一次不成功的尝试,你只要忽略它就好了”。
Joseph Stiglitz:“继续错误的代价由别人来承担,而承认错误的代价由自己承担。”
14、捕获异常
由于线程的本质特性,使得你不能捕获从线程中逃逸的异常。
为了解决这个问题,我们要修改Executor产生线程的方式。
Thread.UncaughtExceptionHandler是Java SE5中的新街口,他允许你在每个Thread对象上附着一个异常处理器。
Demo:
如果在你的代码中,多出需要使用相同的异常处理器,简单的方式是在Thread类中设置一个静态域,给予一个默认的未捕获异常处理器:
当然,如果单独设置,默认会失效
3共享受限资源
1、不正确地访问资源
在java中,递增不是原子性的操作。因此,如果不保护任务,即使单一的递增也是不安全的。
Demo:
2、解决共享资源的竞争
对于并发工作,你需要某种方式来防止两个任务访问相同的资源,至少在关键阶段不能出现这种情况。
基本大家都是使用序列化访问共享资源的方案。
锁语句产生了一种花香排斥的效果,所以这种机制常常成为互斥量(mutex)。
Java以提供关键字synchronized的形式,来防止资源冲突提供了内置支持。
所以对象都自动含有单一的锁(也称为监视器)。当在对象上调用起任意synchronized方法的时候,此对象都被加锁,这时该对象上的其他synchronized方法之一等到前一个方法调用完毕并释放了锁之后才能被调用。
在使用并发时,将域设置为private是非常重要的,否则,synchronized关键字就不能防止其他任务直接访问域,这样就会发生冲突。
一个任务可以多次获得对象的锁。如果一个方法在同一个对象上调用了第二个方法,后者又调用了同一个对象上的另一个方法,就会发生这种情况。
JVM负责跟踪对象被加锁的次数。如果一个对象被解锁(即锁被完全释放),其计数变为0。
在任务第一次给对象加锁的时候,计数变为1。每当这个相同的任务在这个对象上获得锁时,计数都会递增。显然,只有首先获得了锁的任务才能允许继续获得多个锁。每当任务离开 一个synchronized方法,计数递减,当计数为零的时候,所被完全释放,此时别的任务就可以使用此资源。
针对每个类,也有一个锁(作为ie累的Class对象的一部分),所以synchronized static方法可以在类的范围内防止对static数据的并发访问。
Demo:
使用显示的Lock对象:
Lock对象必须被显式的创建、锁定和释放。
3、原子性和易变性
(警告:不要依赖于原子性替代同步)
原子性可以应用于除long和double之外的所有基本类型之上的“简单操作”。
在多处理器系统上,性对于蛋处理器系统而言,可视性问题远比原子性问题多得多。
volatile关键字还确保了应用中的可视性。(volatile会把修改立即写入到主存中,即便你使用了本地缓存)
当一个域的值依赖于他之前的值时,volatile就无法工作了。
使用volatile而不是synchronized的唯一安全的情况是类中只要一个可变的域。
上面的AtomicityTest类也讲述了原子性的问题。
基本上,如果一个域可能会被多个任务同时访问,合作和这些任务中至少有一个是写入任务,那么你就应该将这个域设置为volatile。
Demo:
4、原子类
Java SE5引入了诸如AtomicInteger、AtomicLong、AtomicReference等特殊的原子性变量类。
这些类被调整为可以使用在某些现代处理器上的可获得的,并且是在机器级别上的原子性,因此在使用他们时,通常我们不需要担心。
(但是,还是需要强调,通常依赖于锁活更安全一些,除非在特殊的情况下在自己的代码中使用他们)
Demo:
5、临界区
有时,你只是希望防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法。
通过这种方式分离出来的代码段成为临界区(critical section),他也使用synchronized关键字建立。
这里,synchronized被用来指定某个对象,次对象的锁被用来对花括号内的代码进行同步控制:
synchronized(syncObject){
//This code can be accessed
//by only one task at a time
}
这也被成为同步控制块;在进入次代码前,必须得到syncObject对象的锁。
如果其他对象已经得到这个锁,那么就的等到锁被释放以后,才能进入临界区。
或者你还可使用Lock对象创建临界区。
6、在其他对象上同步
synchronized块必须给定一个在其上进行同步的对象,并且最合理的方式是,使用其方法正在被调用的当前对象:synchronized(this).
有时必须在另一个对象上同步,但是如果你要这么做,就必须确保所有相关的任务都是在同一个对象上同步的。
Demo:
7、线程本地存储
防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。
线程本地存储是一种自动化机制,可以为使用相同变量的不同的线程都创建不同的存储。
可以由java.lang.ThreadLocal类来实现。
Demo:
并发编程令人困惑的一个主要原因是是:使用并发时需要解决的问题有多个,而实现并发的方式也有多种,并且这两者之间没有明显的映射关系。
用并发解决的问题大体上分为“速度”和“设计可管理性”两种。
1、更快的执行
并发是用于多处理器编程的基本工具。
但是,并发通常是提高运行在单处理器上的程序的性能。(但是,会出现上下文切换的代价)
但是,单处理器上,还可以处理阻塞的问题。
在处理单处理器系统中的性能提高的常见示例是事件驱动编程。
实现并发最直接的方式是操作系统级别使用进程。(但是,任务之间对资源的使用是一个问题)
2、改进代码设计
协作多线程。Java的线程机制是抢占式的。
2基本的线程机制
1、定义任务
线程可以驱动任务,因此你需要一种描述任务的方式,这可以由Runnable接口来提供。
要想定义任务,只需实现Runnable接口并发编写run()方法,是的该任务可以执行你的命令。
demo:
- publicclassLiftOffimplementsRunnable {
- protectedintcountDown =10;
- privatestaticinttaskCount =0;
- /** 用来区分任务的多个实例 */
- //因为他是final的,一旦被初始化之后就不希望被修改
- privatefinalintid = taskCount++;
- publicLiftOff(){}
- publicLiftOff(intcountDown){
- this.countDown = countDown;
- }
- publicString status(){
- return"#"+ id +"("+ (countDown >0?countDown :"LiftOff!") +"), ";
- }
- @Override
- publicvoidrun() {
- while(countDown-- >0){
- System.out.print(status());
- //线程调度器:表示“我们已经执行完声明周期中最重要的部分了,此刻正是切换给其他任务执行一段时间的大好时机”
- Thread.yield();
- }
- }
- publicstaticvoidmain(String[] args) {
- oneThread();
- }
- /**
- * main只是一个单线程的调用
- */
- privatestaticvoidoneThread() {
- LiftOff launch =newLiftOff();
- launch.run();
- //后台打印:
- //#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!),
- }
- }
2、Thread类
将Runnable对象转变为工作任务的传统方式是把他提交给一个Thread构造器。
Thread构造器只需要一个Runnable对象。调用Thread对象的start()方法为该线程执行必需的初始化操作,然后调用Runnable的run()方法,以便在这个新线程中启动该任务。
- publicstaticvoidmain(String[] args) {
- moreBasicThreads();
- }
- /**
- * 添加更多的线程去驱动更多的任务
- */
- privatestaticvoidmoreBasicThreads() {
- for(inti=0;i<5;i++){
- newThread(newLiftOff()).start();
- }
- System.out.println("Waiting for LiftOff");
- //后台打印:
- //Waiting for LiftOff
- //#4(9), #3(9), #1(9), #0(9), #1(8), #3(8), #2(9), #4(8), #2(8), #3(7), #1(7), #0(8), #1(6), #3(6), #3(5), #2(7), #4(7), #2(6), #3(4), #1(5), #2(5), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #2(4), #3(3), #1(4), #4(6), #1(3), #3(2), #2(3), #0(LiftOff!), #2(2), #2(1), #2(LiftOff!), #3(1), #1(2), #3(LiftOff!), #4(5), #1(1), #1(LiftOff!), #4(4), #4(3), #4(2), #4(1), #4(LiftOff!),
- }
3、使用Executor
Java SE5的java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程。
Executor在客户端和任务执行之间提供了一个间接层;与客户端直接执行任务不同,这个中介对象将执行任务。
demo:
- packagecom.partner4java.tij;
- importjava.util.concurrent.ExecutorService;
- importjava.util.concurrent.Executors;
- publicclassCachedTreadPool {
- publicstaticvoidmain(String[] args) {
- //我们可以使用Executor来代替显示的创建Thread对象
- ExecutorService executorService = Executors.newCachedThreadPool();
- for(inti=0;i<10;i++){
- executorService.execute(newLiftOff());
- }
- executorService.shutdown();
- }
- }
单个的Executor被用来创建和管理系统中所有的任务。
对shutDown方法的调用可以防止新任务被提交给这个Executor,当前线程将继续运行在shutDown()被调用之前的任务。完成任务后Executor会尽快退出。
Executors.newFixedThreadPool(5)--FixedThreadPool:
创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。
一次性预先执行代价高昂的线程分配,因而也就可以限制线程的数量了。
相反Executors.newCachedThreadPool()通常会创建与所需数量相同的线程,然后在他回收就线程时停止创建新线程。
SingleThreadExecutor就像是线程数量为1的FixedThreadPool。
4、从任务中产生返回值
如果你希望任务在完成时能够返回一个值,可以实现Callable接口。
在Java SE5中引用的Callable是一种具有类型参数的泛型,他的类型参数表示是从方法call()中返回的值,并且必须是使用ExecutorService.submit()方法调用他。
demo:
- packagecom.partner4java.tij;
- importjava.util.concurrent.Callable;
- /**
- * 具有返回值的任务
- * @author partner4java
- *
- */
- publicclassTaskWithResultimplementsCallable<String> {
- privateintid;
- publicTaskWithResult(intid) {
- this.id = id;
- }
- @Override
- publicString call()throwsException {
- return"result of TaskWithResult "+ id;
- }
- }
- packagecom.partner4java.tij;
- importjava.util.ArrayList;
- importjava.util.List;
- importjava.util.concurrent.ExecutionException;
- importjava.util.concurrent.ExecutorService;
- importjava.util.concurrent.Executors;
- importjava.util.concurrent.Future;
- publicclassCallableDemo {
- publicstaticvoidmain(String[] args) {
- ExecutorService executorService = Executors.newCachedThreadPool();
- List<Future<String>> results =newArrayList<Future<String>>();
- for(inti=0;i<10;i++){
- //submit()方法会产生Future对象,他用Callable返回结果的特定类型进行了参数化。
- //你可以用isDone()方法来查询Future是否已经完成。
- results.add(executorService.submit(newTaskWithResult(i)));
- }
- for(Future<String> res:results){
- try{
- //当任务完成时,他具有一个结果,你可以调用get()方法来获取该结果。
- System.out.println(res.get());
- }catch(InterruptedException e) {
- e.printStackTrace();
- }catch(ExecutionException e) {
- e.printStackTrace();
- }
- }
- }
- }
5、休眠
Java SE5引入了更加显示的sleep()版本作为TimeUnit类的一部分,这个方法允许你指定sleep()延迟的时间单元,因此可以提供更好的可阅读性。TimeUnit还可以被用来执行转换,就想稍后你会在本书中看到的那样。
- packagecom.partner4java.tij;
- importjava.util.concurrent.ExecutorService;
- importjava.util.concurrent.Executors;
- importjava.util.concurrent.TimeUnit;
- publicclassSleepingTaskextendsLiftOff {
- @Override
- publicvoidrun() {
- //super.run();
- try{
- while(countDown-- >0){
- System.out.print(status());
- TimeUnit.MILLISECONDS.sleep(1000);
- }
- }catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- publicstaticvoidmain(String[] args) {
- ExecutorService executorService = Executors.newCachedThreadPool();
- for(inti=0;i<5;i++){
- executorService.execute(newSleepingTask());
- }
- executorService.shutdown();
- }
- }
6、优先级
线程的优先级将该线程的重要性传递给了调度器。
你可以用getPriority()来读取现有线程的优先级,并且在任何时刻都可以通过setPriority()来修改他。
demo:
- packagecom.partner4java.tij;
- importjava.util.concurrent.ExecutorService;
- importjava.util.concurrent.Executors;
- publicclassSimplePrioritiesimplementsRunnable {
- privateintcountDown =5;
- privatevolatiledoubled;//No optimization
- privateintpriority;
- publicSimplePriorities(intpriority){
- this.priority = priority;
- }
- @Override
- publicString toString() {
- returnThread.currentThread() +" : "+ countDown;
- }
- @Override
- publicvoidrun() {
- Thread.currentThread().setPriority(priority);
- while(true){
- //加一个这么大的循环的目的是,确保不进行任何编译器优化
- //An expensive,interruptable operation:
- for(inti=1;i<100000;i++){
- d += (Math.PI + Math.E) / (double)i;
- if(i %1000==0){
- Thread.yield();
- }
- }
- System.out.println(this+" : "+ priority);
- if(--countDown ==0)return;
- }
- }
- publicstaticvoidmain(String[] args) {
- ExecutorService executorService = Executors.newCachedThreadPool();
- for(inti=0;i<5;i++){
- executorService.execute(newSimplePriorities(Thread.MIN_PRIORITY));
- }
- executorService.execute(newSimplePriorities(Thread.MAX_PRIORITY));
- executorService.shutdown();
- //本身假象状态会先打印最后一个线程,因为给予了Thread.MAX_PRIORITY级别的优先级,单位我CPU是双核4线程的,就没那么明显,不过,线程6还是基本优于其他线程打印
- }
- }
一别建议使用三个常量:Thread.MIN_PRIORITY、NORM_PRIORITY、Thread.MAX_PRIORITY。
7、让步
如果知道已经完成了在run()方法的循环的一次迭代过程中所需要的工作,就可以给线程调度一个机制暗示:你的工作已经做的差不多了,可以让给别的线程使用CPU了。通过调用yield()来实现。
8、后台进程
所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺部分。
(当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中所有后台线程。反过来说,只要有任何非后台线程还在运行,程序就不会终止。)
demo:
- packagecom.partner4java.tij;
- importjava.util.concurrent.TimeUnit;
- /**
- * 后台(daemon)线程<br/>
- * Daemon threads don't prevent the program from ending.
- * @author partner4java
- *
- */
- publicclassSimpleDaemonsimplementsRunnable {
- @Override
- publicvoidrun() {
- try{
- while(true){
- //当你把这个设置成大于200的时候,就会发生什么都不打印的情况,也是前面完事了,就的杀死后面的
- TimeUnit.MILLISECONDS.sleep(100);
- System.out.println(Thread.currentThread() +" "+this);
- }
- }catch(InterruptedException e) {
- //e.printStackTrace();
- System.out.println("sleep() interrupted");
- }
- }
- publicstaticvoidmain(String[] args)throwsInterruptedException {
- for(inti=0;i<10;i++){
- Thread daemon =newThread(newSimpleDaemons());
- //必须在线程启动之前调用setDaemon方法,才能把它设置为后台线程
- daemon.setDaemon(true);//不添加后台就会在一直不停的打
- daemon.start();
- }
- System.out.println("All daemons started");
- TimeUnit.MILLISECONDS.sleep(175);
- }
- }
使用Executor,且实现后台线程:
Demo:
- packagecom.partner4java.tij;
- importjava.util.concurrent.ThreadFactory;
- importjava.util.concurrent.atomic.AtomicInteger;
- /**
- * 这与普通的ThreadFactory的唯一差异就是他讲后台状态全部设置成了true
- * @author partner4java
- *
- */
- publicclassDaemonThreadFactoryimplementsThreadFactory {
- @Override
- publicThread newThread(Runnable r) {
- Thread thread =newThread(r);
- thread.setDaemon(true);
- returnthread;
- }
- }
- //
- //默认的那个:
- ///**
- // * The default thread factory
- // */
- //static class DefaultThreadFactory implements ThreadFactory {
- //static final AtomicInteger poolNumber = new AtomicInteger(1);
- //final ThreadGroup group;
- //final AtomicInteger threadNumber = new AtomicInteger(1);
- //final String namePrefix;
- //
- //DefaultThreadFactory() {
- //SecurityManager s = System.getSecurityManager();
- //group = (s != null)? s.getThreadGroup() :
- // Thread.currentThread().getThreadGroup();
- //namePrefix = "pool-" +
- //poolNumber.getAndIncrement() +
- // "-thread-";
- //}
- //
- //public Thread newThread(Runnable r) {
- //Thread t = new Thread(group, r,
- //namePrefix + threadNumber.getAndIncrement(),
- //0);
- //if (t.isDaemon())
- //t.setDaemon(false);
- //if (t.getPriority() != Thread.NORM_PRIORITY)
- //t.setPriority(Thread.NORM_PRIORITY);
- //return t;
- //}
- //}
- packagecom.partner4java.tij;
- importjava.util.concurrent.ExecutorService;
- importjava.util.concurrent.Executors;
- importjava.util.concurrent.TimeUnit;
- publicclassDaemonFromFactoryimplementsRunnable {
- @Override
- publicvoidrun() {
- try{
- TimeUnit.MILLISECONDS.sleep(100);
- System.out.println(Thread.currentThread() +" "+this);
- }catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- publicstaticvoidmain(String[] args)throwsInterruptedException {
- ExecutorService executorService = Executors.newCachedThreadPool(newDaemonThreadFactory());
- for(inti=0;i<10;i++){
- executorService.execute(newDaemonFromFactory());
- }
- System.out.println("All daemons started");
- TimeUnit.MILLISECONDS.sleep(175);
- }
- }
你还可以考虑和Thread.yield()结合来转交控制权等。
示例:
finally的内容会打印么?
Demo:
- packagecom.partner4java.tij;
- importjava.util.concurrent.TimeUnit;
- publicclassADaemonimplementsRunnable {
- @Override
- publicvoidrun() {
- try{
- System.out.println("Starting ADaemon");
- TimeUnit.SECONDS.sleep(1);
- }catch(InterruptedException e) {
- e.printStackTrace();
- }finally{
- System.out.println("This should always run?");
- }
- }
- }
- packagecom.partner4java.tij;
- publicclassDaeonsDontRunFinally {
- publicstaticvoidmain(String[] args) {
- Thread thread =newThread(newADaemon());
- thread.setDaemon(true);
- thread.start();
- //后台打印:
- //Starting ADaemon
- }
- }
9、编码的变体
Demo:
- packagecom.partner4java.tij;
- /**
- * 定义一个内部的Thread类
- * @author partner4java
- *
- */
- publicclassInnerThread1 {
- privateintcountDown =5;
- privateInner inner;
- publicInnerThread1(String name){
- inner =newInner(name);
- }
- privateclassInnerextendsThread{
- Inner(String name){
- super(name);
- start();
- }
- @Override
- publicvoidrun() {
- try{
- while(true){
- System.out.println(this);
- if(--countDown ==0)return;
- sleep(10);
- }
- }catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- @Override
- publicString toString() {
- returngetName() +":"+ countDown;
- }
- }
- }
- packagecom.partner4java.tij;
- /**
- * 在构造器中直接new出来一个Thread
- * @author partner4java
- *
- */
- publicclassInnerThread2 {
- privateintcountDown =5;
- privateThread thread;
- publicInnerThread2(String name){
- thread =newThread(name){
- publicvoidrun() {
- try{
- while(true){
- System.out.println(this);
- if(--countDown ==0)return;
- sleep(10);
- }
- }catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- @Override
- publicString toString() {
- returngetName() +":"+ countDown;
- }
- };
- thread.start();
- }
- }
- packagecom.partner4java.tij;
- /**
- * 单独起一个方法来启动和创建Thread
- * @author partner4java
- *
- */
- publicclassThreadMethod {
- privateintcountDown =5;
- privateThread thread;
- privateString name;
- publicThreadMethod(String name) {
- this.name = name;
- }
- publicvoidrunTask(){
- if(thread ==null){
- thread =newThread(name){
- publicvoidrun() {
- try{
- while(true){
- System.out.println(this);
- if(--countDown ==0)return;
- sleep(10);
- }
- }catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- publicString toString() {
- returngetName() +":"+ countDown;
- }
- };
- thread.start();
- }
- }
- }
测试:
- packagecom.partner4java.tij;
- publicclassThreadVariations {
- publicstaticvoidmain(String[] args) {
- newInnerThread1("InnerThread1");
- newInnerThread2("InnerThread2");
- newThreadMethod("ThreadMethod").runTask();
- }
- }
10、术语
描述将要执行的工作使用术语“任务”,只有在引用到驱动任务的具体机制时,才使用“线程”。因为,如果你在概念级别上讨论系统,那就可以指使用“任务”,而压根不需要提及驱动机制。
11、加入一个线程
一个线程可以在其他线程之上调用join()方法,起效果是等待一段时间直到第二个线程结束才继续执行。
如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(即t.isAlive()返回为假)。
也可以在调用join()时带上一个超时参数,这样如果目标线程在这段时间到期时还没有结束回话,join()方法总能返回。
对join()方法的调用可以被中断,做法是调用线程上调用interrupt()方法,这是需要用到try-catch子句。
12、创建有相应的用户界面
System.in.read();
13、线程组
Joshua Bloch:“最好吧线程组看成是一次不成功的尝试,你只要忽略它就好了”。
Joseph Stiglitz:“继续错误的代价由别人来承担,而承认错误的代价由自己承担。”
14、捕获异常
由于线程的本质特性,使得你不能捕获从线程中逃逸的异常。
为了解决这个问题,我们要修改Executor产生线程的方式。
Thread.UncaughtExceptionHandler是Java SE5中的新街口,他允许你在每个Thread对象上附着一个异常处理器。
Demo:
- packagecom.partner4java.tij.util;
- importjava.lang.Thread.UncaughtExceptionHandler;
- /**
- * 当 Thread 因未捕获的异常而突然终止时,调用处理程序的接口。 <br/>
- * 当某一线程因未捕获的异常而即将终止时,Java 虚拟机将使用 Thread.getUncaughtExceptionHandler()
- * 查询该线程以获得其 UncaughtExceptionHandler 的线程,并调用处理程序的 uncaughtException 方法,
- * 将线程和异常作为参数传递。如果某一线程没有明确设置其 UncaughtExceptionHandler,
- * 则将它的 ThreadGroup 对象作为其 UncaughtExceptionHandler。
- * 如果 ThreadGroup 对象对处理异常没有什么特殊要求,那么它可以将调用转发给默认的未捕获异常处理程序。
- * @author partner4java
- *
- */
- publicclassMyUncaughtExceptionHandlerimplementsUncaughtExceptionHandler {
- @Override
- publicvoiduncaughtException(Thread t, Throwable e) {
- System.out.println("caught "+ e);
- }
- }
- packagecom.partner4java.tij.util;
- publicclassExceptionThreadimplementsRunnable {
- @Override
- publicvoidrun() {
- Thread thread = Thread.currentThread();
- System.out.println("run() by "+ thread);
- System.out.println("en = "+ thread.getUncaughtExceptionHandler());
- thrownewRuntimeException("error by slef");
- }
- }
- packagecom.partner4java.tij.util;
- importjava.util.concurrent.ThreadFactory;
- publicclassHandlerThreadFactoryimplementsThreadFactory {
- @Override
- publicThread newThread(Runnable r) {
- System.out.println(this+" creating new Thread");
- Thread thread =newThread(r);
- System.out.println("created "+ thread);
- thread.setUncaughtExceptionHandler(newMyUncaughtExceptionHandler());
- System.out.println("en = "+ thread.getUncaughtExceptionHandler());
- returnthread;
- }
- }
- packagecom.partner4java.tij.util;
- importjava.util.concurrent.ExecutorService;
- importjava.util.concurrent.Executors;
- publicclassCaptureUncaughtException {
- publicstaticvoidmain(String[] args) {
- ExecutorService executorService = Executors.newCachedThreadPool(newHandlerThreadFactory());
- executorService.execute(newExceptionThread());
- //最终在UncaughtExceptionHandler里面得到了捕获
- }
- }
如果在你的代码中,多出需要使用相同的异常处理器,简单的方式是在Thread类中设置一个静态域,给予一个默认的未捕获异常处理器:
当然,如果单独设置,默认会失效
- packagecom.partner4java.tij.util;
- importjava.util.concurrent.ExecutorService;
- importjava.util.concurrent.Executors;
- publicclassSettingDefaultHandler {
- publicstaticvoidmain(String[] args) {
- Thread.setDefaultUncaughtExceptionHandler(newMyUncaughtExceptionHandler());
- ExecutorService executorService = Executors.newCachedThreadPool();
- executorService.execute(newExceptionThread());
- //后台打印:
- //run() by Thread[pool-1-thread-1,5,main]
- //en = java.lang.ThreadGroup[name=main,maxpri=10]
- //caught java.lang.RuntimeException: error by slef
- //最终在UncaughtExceptionHandler里面得到了捕获
- }
- }
3共享受限资源
1、不正确地访问资源
在java中,递增不是原子性的操作。因此,如果不保护任务,即使单一的递增也是不安全的。
Demo:
- packagecom.partner4java.tij.sharing;
- /**
- * 包含消费者任务(EvenChecker)必须了解的必不可少的方法
- * @author partner4java
- *
- */
- publicabstractclassIntGenerator {
- //为了保证可视性,添加了volatile
- privatevolatilebooleancanceled =false;
- /**
- * 产生偶数
- * @return
- */
- publicabstractintnext();
- /**
- * 修改boolean类型的canceled标志的状态
- */
- publicvoidcancel(){
- canceled =true;
- }
- /**
- * 查看是否已经被取消
- * @return
- */
- publicbooleanisCanceled(){
- returncanceled;
- }
- }
- packagecom.partner4java.tij.sharing;
- importjava.util.concurrent.ExecutorService;
- importjava.util.concurrent.Executors;
- /**
- * 任务检测
- * @author partner4java
- *
- */
- publicclassEvenCheckerimplementsRunnable {
- privateIntGenerator generator;
- privatefinalintid;//看到了么?final类型的全局变量不一定非的马上赋值,也可以通过构造器来赋值
- publicEvenChecker(IntGenerator generator,intid) {
- this.generator = generator;
- this.id = id;
- }
- @Override
- publicvoidrun() {
- while(!generator.isCanceled()){
- intval = generator.next();
- //当没有问题时,就是是偶数,那么将一直死循环下去。但是,当出现了资源共享问题,就会通过这个if
- if(val %2!=0){
- System.out.println(val +" not even!");
- generator.cancel();//Cancels all EventCheckers
- }
- }
- }
- //Test any type of IntGenerator:
- publicstaticvoidtest(IntGenerator generator,intcount) {
- System.out.println("Presss Control-C to exit");
- ExecutorService executorService = Executors.newCachedThreadPool();
- for(inti=0;i<count;i++){
- executorService.execute(newEvenChecker(generator, i));
- }
- executorService.shutdown();
- }
- //Default value of count:
- publicstaticvoidtest(IntGenerator generator){
- test(generator,10);
- }
- }
- packagecom.partner4java.tij.sharing;
- /**
- * 一个简单实现
- * @author partner4java
- *
- */
- publicclassEvenGeneratorextendsIntGenerator {
- privateintcurrentEventValue =0;
- @Override
- publicintnext() {
- ++currentEventValue;//Danger point here!
- ++currentEventValue;
- returncurrentEventValue;
- }
- publicstaticvoidmain(String[] args) {
- EvenChecker.test(newEvenGenerator());
- //后台打印:
- //Presss Control-C to exit
- //447 not even!
- //447 not even!
- //449 not even!
- //那么就证明了有问题,正常的情况会全部是偶数,不会出现非偶数
- }
- }
2、解决共享资源的竞争
对于并发工作,你需要某种方式来防止两个任务访问相同的资源,至少在关键阶段不能出现这种情况。
基本大家都是使用序列化访问共享资源的方案。
锁语句产生了一种花香排斥的效果,所以这种机制常常成为互斥量(mutex)。
Java以提供关键字synchronized的形式,来防止资源冲突提供了内置支持。
所以对象都自动含有单一的锁(也称为监视器)。当在对象上调用起任意synchronized方法的时候,此对象都被加锁,这时该对象上的其他synchronized方法之一等到前一个方法调用完毕并释放了锁之后才能被调用。
在使用并发时,将域设置为private是非常重要的,否则,synchronized关键字就不能防止其他任务直接访问域,这样就会发生冲突。
一个任务可以多次获得对象的锁。如果一个方法在同一个对象上调用了第二个方法,后者又调用了同一个对象上的另一个方法,就会发生这种情况。
JVM负责跟踪对象被加锁的次数。如果一个对象被解锁(即锁被完全释放),其计数变为0。
在任务第一次给对象加锁的时候,计数变为1。每当这个相同的任务在这个对象上获得锁时,计数都会递增。显然,只有首先获得了锁的任务才能允许继续获得多个锁。每当任务离开 一个synchronized方法,计数递减,当计数为零的时候,所被完全释放,此时别的任务就可以使用此资源。
针对每个类,也有一个锁(作为ie累的Class对象的一部分),所以synchronized static方法可以在类的范围内防止对static数据的并发访问。
Demo:
- packagecom.partner4java.tij.sharing;
- publicclassSynchronizedEvenGeneratorextendsIntGenerator {
- privateintcurrentEventValue =0;
- @Override
- publicsynchronizedintnext() {
- ++currentEventValue;//Danger point here!
- Thread.yield();
- ++currentEventValue;
- returncurrentEventValue;
- }
- publicstaticvoidmain(String[] args) {
- //这样就永远出来不,就打印一个“Presss Control-C to exit”
- EvenChecker.test(newSynchronizedEvenGenerator());
- //把currentEventValue改成static,再加上下面这句话,就会出现问题了,
- //这样可以证明,非静态方法的锁属于这个new出来的对象而不是这个类。
- //如果在方法上也加上static,就没问题了,还是同一把锁
- EvenChecker.test(newSynchronizedEvenGenerator());
- }
- }
- packagecom.partner4java.tij.sharing;
- importjava.util.concurrent.ExecutorService;
- importjava.util.concurrent.Executors;
- publicclassAtomicityTestimplementsRunnable {
- privateintcountDown =0;
- publicintgetCountDown() {
- returncountDown;
- }
- privatesynchronizedvoidevenIncrement(){
- ++countDown;
- ++countDown;
- }
- @Override
- publicvoidrun() {
- while(true){
- evenIncrement();
- }
- }
- publicstaticvoidmain(String[] args) {
- ExecutorService executorService = Executors.newCachedThreadPool();
- AtomicityTest atomicityTest =newAtomicityTest();
- executorService.execute(atomicityTest);
- while(true){
- intval = atomicityTest.getCountDown();
- //正常的情况是不会打印下面if里面的内容
- if(val %2!=0){
- System.out.println(val);
- System.exit(0);
- }
- //当你给getCountDown方法也加上synchronized的时候就正确了,原因前面说过。
- }
- }
- }
使用显示的Lock对象:
Lock对象必须被显式的创建、锁定和释放。
- packagecom.partner4java.tij.sharing;
- importjava.util.concurrent.locks.Lock;
- importjava.util.concurrent.locks.ReentrantLock;
- /**
- * 显示的Lock<br/>
- * 与synchronized相比代码量多,但是,可以拿到异常在finally里
- * @author partner4java
- *
- */
- publicclassMutexEvenGeneratorextendsIntGenerator {
- privateintcurrentEventValue =0;
- privateLock lock =newReentrantLock();
- @Override
- publicintnext() {
- lock.lock();
- //还有一个优点就是可以尝试去获取锁,如果获取不到,就可以去做别的
- //lock.tryLock(2, TimeUnit.SECONDS);
- try{
- ++currentEventValue;//Danger point here!
- Thread.yield();//Cause failure faster
- ++currentEventValue;
- //不能忽略这个return,否则,还是会报错。(以确保unlock()不会过早的发生)
- returncurrentEventValue;
- }catch(Exception e) {
- e.printStackTrace();
- }finally{
- lock.unlock();
- }
- returncurrentEventValue;
- }
- publicstaticvoidmain(String[] args) {
- EvenChecker.test(newMutexEvenGenerator());
- //当然,使用这种方式,就是同事new出来多少个对象也不会出现问题,因为lock是非静态且本身是独立的。
- //currentEventValue如果为static,也是会有问题
- EvenChecker.test(newMutexEvenGenerator());
- }
- }
3、原子性和易变性
(警告:不要依赖于原子性替代同步)
原子性可以应用于除long和double之外的所有基本类型之上的“简单操作”。
在多处理器系统上,性对于蛋处理器系统而言,可视性问题远比原子性问题多得多。
volatile关键字还确保了应用中的可视性。(volatile会把修改立即写入到主存中,即便你使用了本地缓存)
当一个域的值依赖于他之前的值时,volatile就无法工作了。
使用volatile而不是synchronized的唯一安全的情况是类中只要一个可变的域。
上面的AtomicityTest类也讲述了原子性的问题。
基本上,如果一个域可能会被多个任务同时访问,合作和这些任务中至少有一个是写入任务,那么你就应该将这个域设置为volatile。
Demo:
- packagecom.partner4java.tij.sharing;
- importjava.util.concurrent.ExecutorService;
- importjava.util.concurrent.Executors;
- importjava.util.concurrent.TimeUnit;
- publicclassMyVolatile {
- privateintcount;
- privatevoidupper() {
- ++count;
- }
- privatevoidlower() {
- --count;
- }
- publicstaticvoidmain(String[] args) {
- //test1();
- test2();
- }
- privatestaticvoidtest1() {
- finalMyVolatile myVolatile =newMyVolatile();
- ExecutorService executorService = Executors.newCachedThreadPool();
- executorService.execute(newRunnable() {
- @Override
- publicvoidrun() {
- for(inti =0; i <100000; i++) {
- myVolatile.upper();
- }
- System.out.println("myVolatile.upper is ok");
- }
- });
- executorService.execute(newRunnable() {
- @Override
- publicvoidrun() {
- for(inti =0; i <100000; i++) {
- myVolatile.lower();
- }
- System.out.println("myVolatile.lower is ok");
- }
- });
- try{
- TimeUnit.MILLISECONDS.sleep(1000);
- }catch(InterruptedException e) {
- e.printStackTrace();
- }
- executorService.shutdown();
- System.out.println(myVolatile.count);
- //后台打印:
- //myVolatile.lower is ok
- //myVolatile.upper is ok
- //4747
- //你会想,怎么会这样呢?明明调用的次数相同,你是不是会想给int加个volatile,加上之后还是会有问题,不信你试试。
- //这个例子和前面提到的也很像,那你就给两个方法都加上对象锁,才可以
- }
- //那么volatile到底哪些时候比较有用呢?
- privatestaticvoidtest2() {
- finalMyVolatile myVolatile =newMyVolatile();
- ExecutorService executorService = Executors.newCachedThreadPool();
- executorService.execute(newRunnable() {
- @Override
- publicvoidrun() {
- for(inti =0; i <100000; i++) {
- myVolatile.upper();
- }
- System.out.println("myVolatile.upper is ok");
- }
- });
- executorService.execute(newRunnable() {
- @Override
- publicvoidrun() {
- for(inti =0; i <100000; i++) {
- myVolatile.upper();
- }
- System.out.println("myVolatile.lower is ok");
- }
- });
- try{
- TimeUnit.MILLISECONDS.sleep(1000);
- }catch(InterruptedException e) {
- e.printStackTrace();
- }
- executorService.shutdown();
- System.out.println(myVolatile.count);
- //后台打印:
- //myVolatile.upper is ok
- //myVolatile.lower is ok
- //187213
- //是不是有些出乎我们的意料不是200000,我都是调用的++啊,这样也会出现问题。
- //那么我们就想到了可见性,那么在count前面加上个volatile,还是会有问题?为什么?
- }
- }
4、原子类
Java SE5引入了诸如AtomicInteger、AtomicLong、AtomicReference等特殊的原子性变量类。
这些类被调整为可以使用在某些现代处理器上的可获得的,并且是在机器级别上的原子性,因此在使用他们时,通常我们不需要担心。
(但是,还是需要强调,通常依赖于锁活更安全一些,除非在特殊的情况下在自己的代码中使用他们)
Demo:
- packagecom.partner4java.tij.sharing;
- importjava.util.concurrent.ExecutorService;
- importjava.util.concurrent.Executors;
- importjava.util.concurrent.atomic.AtomicInteger;
- publicclassAtomicityIntegerTestimplementsRunnable {
- privateAtomicInteger countDown =newAtomicInteger(0);
- publicintgetCountDown() {
- returncountDown.get();
- }
- privatevoidevenIncrement(){
- countDown.addAndGet(2);
- }
- @Override
- publicvoidrun() {
- while(true){
- evenIncrement();
- }
- }
- publicstaticvoidmain(String[] args) {
- ExecutorService executorService = Executors.newCachedThreadPool();
- AtomicityIntegerTest atomicityTest =newAtomicityIntegerTest();
- executorService.execute(atomicityTest);
- while(true){
- intval = atomicityTest.getCountDown();
- //正常的情况是不会打印下面if里面的内容
- if(val %2!=0){
- System.out.println(val);
- System.exit(0);
- }
- //我们去掉了evenIncrement的synchronized,但是也没有出现错误的情况,看来AtomicInteger还是好使的,而且会提高性能
- }
- }
- }
5、临界区
有时,你只是希望防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法。
通过这种方式分离出来的代码段成为临界区(critical section),他也使用synchronized关键字建立。
这里,synchronized被用来指定某个对象,次对象的锁被用来对花括号内的代码进行同步控制:
synchronized(syncObject){
//This code can be accessed
//by only one task at a time
}
这也被成为同步控制块;在进入次代码前,必须得到syncObject对象的锁。
如果其他对象已经得到这个锁,那么就的等到锁被释放以后,才能进入临界区。
或者你还可使用Lock对象创建临界区。
6、在其他对象上同步
synchronized块必须给定一个在其上进行同步的对象,并且最合理的方式是,使用其方法正在被调用的当前对象:synchronized(this).
有时必须在另一个对象上同步,但是如果你要这么做,就必须确保所有相关的任务都是在同一个对象上同步的。
Demo:
- packagecom.partner4java.tij.sharing;
- /**
- * 两个方法互不干扰,在不同的锁里面
- * @author partner4java
- *
- */
- publicclassDualSynch {
- privateObject synchObject =newObject();
- publicsynchronizedvoidf(){
- for(inti=0;i<5;i++){
- System.out.println("f()");
- Thread.yield();
- }
- }
- publicvoidg(){
- synchronized(synchObject) {
- for(inti=0;i<5;i++){
- System.out.println("g()");
- Thread.yield();
- }
- }
- }
- publicstaticvoidmain(String[] args) {
- finalDualSynch dualSynch =newDualSynch();
- newThread(){
- @Override
- publicvoidrun() {
- dualSynch.f();
- }
- }.start();
- dualSynch.g();
- //后台打印:
- //g()
- //f()
- //g()
- //f()
- //f()
- //f()
- //f()
- //g()
- //g()
- //g()
- //如果改成synchronized (this),就会出现先打印完g(),再打印f()的情况,因为互斥了
- }
- }
7、线程本地存储
防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。
线程本地存储是一种自动化机制,可以为使用相同变量的不同的线程都创建不同的存储。
可以由java.lang.ThreadLocal类来实现。
Demo:
- packagecom.partner4java.tij.sharing;
- importjava.util.Random;
- importjava.util.concurrent.ExecutorService;
- importjava.util.concurrent.Executors;
- importjava.util.concurrent.TimeUnit;
- publicclassAccessorimplementsRunnable {
- privatefinalintid;
- publicAccessor(intid) {
- this.id = id;
- }
- @Override
- publicvoidrun() {
- while(!Thread.currentThread().isInterrupted()){
- ThreadLocalVariableHolder.increment();
- System.out.println(this);
- Thread.yield();
- }
- }
- @Override
- publicString toString() {
- return"#"+ id +": "+ ThreadLocalVariableHolder.get();
- }
- publicstaticvoidmain(String[] args)throwsInterruptedException {
- ExecutorService executorService = Executors.newCachedThreadPool();
- for(inti=0;i<5;i++){
- executorService.execute(newAccessor(i));
- }
- TimeUnit.MILLISECONDS.sleep(3000);
- executorService.shutdown();
- }
- }
- classThreadLocalVariableHolder{
- privatestaticThreadLocal<Integer> value =newThreadLocal<Integer>(){
- privateRandom random =newRandom(47);
- @Override
- protectedInteger initialValue() {
- returnrandom.nextInt(10000);
- }
- };
- publicstaticvoidincrement(){
- value.set(value.get() +1);
- }
- publicstaticintget(){
- returnvalue.get();
- }
- }
- //其中,get()方法将返回与其线程相关联的对象的副本,而set()会讲参数插入到为其线程存储的对象中,并返回存储中原有的对象。