**本文是在阅读java并发编程实战时,随手记录的笔记,可能会有点凌乱,欢迎各位指正~**
1、共享对象
1.1 可见性
如果不用volatile保护变量,共享变量可能会获取到过期值。但是如果是64位的变量值如double、long,则可能会获取一个线程的高八位,和另一个线程的低八位,导致数据错误异常。此处的八位其实是2^8个字节位,也就是前32位,后32位。
注意用加锁的方式来保护变量,亦可以达到volatile的效果,甚至更强,保护变量在其他线程中可见,否则很有可能在其他线程不可见。
1.2 this引用逸出
在构造函数中进行发布对象,由于发布的内部对象含有对外部构造函数构造的对象this的隐式指向,从而导致构造函数所在的对象this引用逸出,产生安全问题,即可能构造尚未完成,但是发布出去的内部对象被他人使用该this引用,使用了未构造完成的对象。
防止this引用逸出,可以通过私有化构造函数,提供共有的工厂方法来完成须在构造函数中的初始化工作,从而防止在构造未结束之前引用逸出。
1.3 Final关键字的使用
final关键字应尽可能地使用,因为final可以保证不可变对象(所有属性或变量均是不可变的)的线程安全性,可对其他线程的可见性,还能保证对象正确初始化完毕后对其他线程的可见性。
2、组合对象
2.1 错误的锁
并不是给方法或代码片段加了锁就可以保证并发性的安全问题,如果加锁的对象不是同一个的话,并不能保证并发性安全。
例如ConcurrentHashMap,对它的加锁与它自身的内部分段锁不是同一个锁,所以不能保证它并发下的独占式访问。
3、构建块
3.1 CyclicBarrier 循环栅栏
通过构造CyclicBarrier时,传入数量和到达栅栏时的最后一个进程的执行事件,完成一个栅栏的构建,内部通过barrier.await()来等待所有线程到达栅栏,注意可循环使用,每次await()即可。
Cyclicbarrier barrier = new CyclicBarrier(threadNum,ThreadDeomo);
barrier.await();
3.2 Semaphore 信号量
初始化时,需要指令信号量容量, 可以阻塞至信号量被消费完,空余出剩余容量,继续执行,类似行车通道的栅栏。
Semaphore semaphore = new Semaphore(2);
//使用信号量
semaphore.acquire();
//释放信号量
semaphore.release();
4、任务的取消和中断
4.1 interrupt
thread调用interrupt()后,会向线程中的阻塞方法发送一个中断请求,如果线程被中断,会向外抛出interruptedException,并清除中断标志,即通过thread.isInterrupted()不能获取中断标志了,这就需要我们手动调用thread.interrupt()来恢复中断标志。
如果线程运行结束,进入terminated状态,也会清除中断标志。
interrupt其实并不是直接中断线程,而是将当前线程的interrupt状态置位true,如果不是阻塞方法实现了这个标志位校验,则需要我们手动去获取并处理该标志位。
不是所有的阻塞方法都能够提前抛出InterruptedException来实现对中断的响应,如果是类似I/O等待或者内部锁等待的阻塞,中断仅仅只能改变线程的中断状态标志,并不能实际中断线程。
4.2 线程池
线程池ThreadPoolExecutor是通过创建线程,然后通过start启动,每次实际调用的run方法实际是从blockingQueue中获取的工作任务,实现线程复用。
线程池参数有核心线程数、最大线程数、有效存活时间、缓冲队列长度,线程工厂、拒绝策略等,线程池如果不shutdown,将会一直运行,shutdown其实是通过interrupt机制,传递线程终止状态信息,结束线程的while循环实现的。
线程池的停止有两种方法,shutdown() shutdownNow() 区别在于停止时,前者会继续运行等待队列的任务,后者会返回等待队列的任务并向运行中的线程发送intrrupted指令。调用线程池停止后,可以通过调用awaittermination() 来阻塞返回线程池是否关闭成功。
4.3 未捕获异常
UncaughtExceptionHandle接口的实现类可以通过Thread.setUncaughtExceptionHandler()来注入未捕获异常的处理,然后再停止线程。
5、JVM关闭
5.1 jvm退出
jvm的关闭可以使用System.exit(0) 或者Runtime.getRuntime().halt()来关闭,区别在于前者是正常关闭,会执行runtime中注册的shutdownHook子线程,后者是强制关闭,类似于kill命令,不会执行hook线程。
jvm在正常退出时,会检查是否有精灵线程之外的运行中线程,精灵线程不会阻碍jvm关闭。
5.2 运行系统命令
runtime可以通过Runtime,getRuntime().exec(“msg /server:10.240.0.105 * 这是一条测试命令”); 来运行系统命令,完成调用外部应用的功能。