内置锁
内置锁也叫同步代码块
,关键字synchronized
,
- 锁 为对象方法加锁,锁是这个对象,为类方法加锁,锁是类对象。所以提供锁的总是对象,如果两个线程同时请求锁,后来者要阻塞等待直到锁释放
- 重入 同一个线程可以再次获取一个已经持有的锁,保证不会因为是同一个线程而发生死锁,即获取锁的操作粒度是
线程
而不是调用
; - 重排序 在没有同步的情况下,编译器、处理器以及运行时都可能对操作的执行顺序进行一些意想不到的调整;
- 最低安全性 在没有同步的情况下读取变量,可能得到一个失效值,但至少是之前某一个线程设置的,而不会是一个随机值;非volatile的64位数值(double、long)不适用,JVM运行将64位的读操作或写操作分解为两个32位的操作,也就不具备了原子性;
- 可见性 为了状态变量的一致性,所有执行读操作或写操作的的线程都必须在同一个锁上同步,否则同步就会失效;
volatile 变量
volatile变量是一种稍弱的同步机制,用来确保将变量对更新操作通知到其他线程。
- 当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起
重排序
; - volatial变量不会被缓存在寄存器或对其他处理器不可见的地方,这样读取变量时总是最新写入的值;
- 访问volatile变量时不会执行加锁操作,也就不会线程阻塞,因此是一种比synchronized更轻量级的同步机制;
- 不要过度使用volatile,通常用作某个操作完成、发生中断或者其他状态的标志,一种典型用法是检查某个状态标志判断是否退出循环
volatile boolean asleep;
...
while (!asleep)
countSomeSheep();
- volataile只确保可见性,如不能保证递增操作(count++)的原子性;
ThreadLocal
ThreadLocal提供里get与set等访问接口,为每个使用该变量的线程都存有一份独立的副本,维持线程封闭性
- ThreadLocal将特定与线程的值保存在Thread对象中,当线程终止后,这些值会作为垃圾回收;
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
/**
* Provides the initial value of this variable for the current thread.
* The default implementation returns {@code null}.
*
* @return the initial value of the variable.
*/
protected T initialValue() {
return null;
}
/**
* Sets the value of this variable for the current thread. If set to
* {@code null}, the value will be set to null and the underlying entry will
* still be present.
*
* @param value the new value of the variable for the caller thread.
*/
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
/**
* Gets Values instance for this thread and variable type.
*/
Values values(Thread current) {
return current.localValues;
}
实例封闭
将数据封装在对象内部,这样对数据的访问就限制在对象的方法上,从个可以更容易的加锁。
- 实例封闭是构建线程安全类的一个最简单的方式,还使得不同的状态变量可以由不同的锁来保护;
- Collections.synchronizedXXX()方法就是通过对容器进行包装,包装类内部加锁,从而实现线程安全;
并发容器
Java5.0提供了多种并发容器来改进同步容器的性能。
- ConcurrentHashMap代替HashMap,其内部通过分段锁,来提高并发读写的效率
- CopyOnWriteArrayList,每当修改容器时都会复制底层数组,会有一定开销,所以仅当迭代操作远多于修改操作时,才应该使用
Executor框架
提供了一种标准当方法将任务的提交过程与执行过程解耦开来。
线程池:
- newFixedThreadPool 创建一个固定长度的线程池,每提交一个任务就创建一个线程直到线程数达到最大值;
- newCachedThreadPool 创建一个可缓存的线程池,当线程池规模超过了处理需求将回收空闲线程,需求增加就添加新线程,大小不受限制;
- newSingleThreadExecutor 单线程
- newScheduledThreadPool 创建固定长度的线程池,而且以延时或定时方式执行任务;
为了解决执行服务的生命周期问题,Executor扩展出了ExecutorService接口,添加了一些生命周期管理的方法。