第二章 Java并行程序基础
2.1 有关线程你必须知道的事
- 线程所有状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
2.2 初始线程:线程的基本操作
2.2.2 终止线程
stop()方法太过暴力,强行把执行一半的线程终止,可能会导致数据不一致。EX:参考36页
2.2.3 线程中断
- 线程中断并不会使线程立即退出[需要用户自己处理退出逻辑],中断只是给线程发送一个通知,有人希望该线程退出。
- 三个方法:
- interrupt():实例方法,通知目标线程中断,设置中断标志位
- isInterrupted():实例方法,判断当前对象是否被设置了中断
- Thread.interrupted():静态方法,用来判断当前线程的中断状态,但同时会清除当前线程的中断标志位状态
- 注意:
- Thread.sleep()方法会抛出中断异常,如果线程被中断,进行sleep就会抛出该异常,此时需要捕获该异常,并在catch中调用Thread.currentThread().interrupt()方法设置中断状态,如果不这么做,则在下一次的循环开始时,就无法捕获这个中断。EX:参考40页
2.2.4 等待(wait)和通知(notify)
wait方法必须包含在synchronized语句中,无论是wait或者notify方法都需要首先获得目标对象的一个监视器。notify只是在等待队列中随机选择一个进行唤醒,因此是不公平的。EX:参考43页
2.2.5 挂起(suspend)和继续执行(resume)线程
被挂起的线程必须等到resume操作后,才能继续执行。但suspend方法已经被废弃,因为该方法不会释放任何锁资源,会导致其他任何线程想要访问被它锁定的资源时,都会被牵连。而且对于被挂起的线程从它的线程状态上看,居然还是Runnable。
2.2.6 等待线程结束(join)和谦让(yield)
- 一个线程依赖于其他线程,需要等待其他线程执行完才能继续执行,可以通过join来实现。join的本质是让线程wait,其实现如下:
- while(isAlive()) wait(0)
- 注意:不要在应用程序中,在Thread对象实例上使用类似wait()或者notify()等方法,因为这很有可能会影响系统API的工作,或者被系统API所影响。
- yield是一个静态方法,一旦执行会使当前线程让出cpu,但其会再次进行cpu资源的竞争,因此可能会让其再次获得cpu资源继续执行。
2.3 volatile与Java内存模型
2.4 分门别类的管理:线程组
ThreadGroup,activeCount方法可以获取活动的线程总数,由于线程是动态的,因此该值是一个估算值,不是一个精确值。线程组同样有个stop方法,但同线程的stop方法一样存在问题。
2.5 驻守后台:守护线程(Daemon)
与之对应的是用户线程。当一个Java应用内,只有守护线程时,Java虚拟机就会自然退出。
2.6 先干重要的事:线程优先级
1到10的级别,但不可以依赖于这些优先级,优先级只是线程获取cpu资源的几率大些,但并不能确保一定就比低优先级的先获取到。
2.7 线程安全的概念与synchronized
- 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
- 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
- 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
2.8 程序中的幽灵:隐蔽的错误
2.8.1 无提示的错误案例
如数据类型溢出错误
2.8.2 并发下的ArrayList
保存容器大小的变量被多线程不正常访问、多线程对同一个位置进行了赋值导致异常。
2.8.3 并发下诡异的HashMap
多线程可能会导致put操作中把链表破坏,如Key1和Key2互为对方的next元素,导致成了环形,进入一个死循环,从而导致了程序无法结束。
2.8.4 初学者常见错误:错误的加锁
如Integer变量作为同步synchronized条件,Integer为不可变对象,即无法修改Integer的值,要修改只能新建一个Integer让它的值赋值为新的值;i++实际上调用了i=Integer.valueOf(i.intValue() + 1);该方法为一个工厂方法。EX:参考67页