synchronized与volatile的区别:
1. volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
2. volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
3. volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
4. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
5. volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
线程不安全的原因:竞态条件
解决线程不安全的问题:
- 程序封闭(线程封闭、栈封闭、Threadlocal封闭)
- 只读共享(不可变对象和事实不可变对象,在没有额外的同步的情况下,都是线程安全的)
- 线程安全共享(线程安全的对象内部实现同步,多线程可以通过对象的公有接口进行访问)
- 保护对象(将其封装在其他线程安全的容器中:vector,hashtable,concurrentSet,concurrentMap,concurrentLinkedList,copyOnWriteArrayList,copyOnWriteArraySet,blockingQueue,synchronizedList,synchronizedSet,synchronizedMap等等)
- static初始化由于JVM内部同步机制,也是线程安全的
private static final Test test = new Test();
封闭模式:保证任意时刻只有一个线程能够访问对象,常用方法是加锁机制
// 线程安全的对象,外部不能直接访问
private static class Test {
// 非线程安全的对象
private HashSet<Integer> set = new HashSet<>();
// 封闭的方法
public synchronized boolean contains(int x) {
return set.contains(x);
}
// 封闭的方法
public synchronized void add(int x) {
set.add(x);
}
}
监视器模式:
// 线程安全
private static class Test {
// 非线程安全
private HashSet<Integer> set = new HashSet<>();
private final Object lock = new Object();
// 封闭的方法
public boolean contains(int x) {
synchronized (lock) {
return set.contains(x);
}
}
// 封闭的方法
public void add(int x) {
synchronized (lock) {
set.add(x);
}
}
}
委托给线程安全的容器进行管理(多个状态变量时):
// 线程安全
private static class Test {
// 委托给线程安全的容器进行管理
private Set<Integer> set = Collections.synchronizedSet(new HashSet<>());
// 线程安全的方法
public boolean contains(int x) {
return set.contains(x);
}
// 线程安全的方法
public void add(int x) {
set.add(x);
}
}
必须保证逻辑上的操作是线程安全的:
// 非线程安全
private static class Test {
// 线程安全的对象
private Set<Integer> set = Collections.synchronizedSet(new HashSet<>());
// 线程安全的对象
private AtomicInteger currInteger = new AtomicInteger();
// 逻辑上非线程安全的方法
public void set(int x) {
synchronized (this) {
set.add(x);
currInteger.set(x);
}
}
}
SynchronizedCollection源码:
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
private static final long serialVersionUID = 3053995032091335093L;
final Collection<E> c; // Backing Collection
final Object mutex; // Object on which to synchronize
SynchronizedCollection(Collection<E> c) {
this.c = Objects.requireNonNull(c);
mutex = this;
}
SynchronizedCollection(Collection<E> c, Object mutex) {
this.c = Objects.requireNonNull(c);
this.mutex = Objects.requireNonNull(mutex);
}
public int size() {
synchronized (mutex) {return c.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return c.isEmpty();}
}
public boolean contains(Object o) {
synchronized (mutex) {return c.contains(o);}
}
public Object[] toArray() {
synchronized (mutex) {return c.toArray();}
}
public <T> T[] toArray(T[] a) {
synchronized (mutex) {return c.toArray(a);}
}
public Iterator<E> iterator() {
return c.iterator(); // Must be manually synched by user!
}
public boolean add(E e) {
synchronized (mutex) {return c.add(e);}
}
public boolean remove(Object o) {
synchronized (mutex) {return c.remove(o);}
}
public boolean containsAll(Collection<?> coll) {
synchronized (mutex) {return c.containsAll(coll);}
}
public boolean addAll(Collection<? extends E> coll) {
synchronized (mutex) {return c.addAll(coll);}
}
public boolean removeAll(Collection<?> coll) {
synchronized (mutex) {return c.removeAll(coll);}
}
public boolean retainAll(Collection<?> coll) {
synchronized (mutex) {return c.retainAll(coll);}
}
public void clear() {
synchronized (mutex) {c.clear();}
}
public String toString() {
synchronized (mutex) {return c.toString();}
}
// Override default methods in Collection
@Override
public void forEach(Consumer<? super E> consumer) {
synchronized (mutex) {c.forEach(consumer);}
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
synchronized (mutex) {return c.removeIf(filter);}
}
@Override
public Spliterator<E> spliterator() {
return c.spliterator(); // Must be manually synched by user!
}
@Override
public Stream<E> stream() {
return c.stream(); // Must be manually synched by user!
}
@Override
public Stream<E> parallelStream() {
return c.parallelStream(); // Must be manually synched by user!
}
private void writeObject(ObjectOutputStream s) throws IOException {
synchronized (mutex) {s.defaultWriteObject();}
}
}
}
synchronized的嵌套可能造成死锁
synchronizedCollection错误使用:
// 非线程安全
private static class Test {
// 线程安全的对象
private Set<Integer> set = Collections.synchronizedSet(new HashSet<>());
// 逻辑上非线程安全的方法
public synchronized void set(int x) {
// set的加锁对象为自身的mutex,所以无法保证在其他的方法调用里出现线程不安全情况
if(!set.contains(x)) {
set.add(x);
}
}
// 逻辑上线程安全的方法
public void set2(int x) {
synchronized (set) {
if (!set.contains(x)) {
set.add(x);
}
}
}
}
同步容器并非绝对不存在线程不安全的操作:例如同步容器的复合操作:
// 逻辑上非线程安全的方法
public void set(int x) {
if (!set.contains(x)) {
set.add(x);
}
}
ArrayIndexOutOfBoundsException :数组越界异常
// 复合操作多线程出现问题,应该加上同步
for (int i = 0; i < vector.size(); i++) {
vector.get(i);
}
// 防止别的线程进行操作修改,例如remove
// 缺点是开销大
// 还可以使用克隆再遍历,但是效率也不高
synchronized (vector) {
for (int i = 0; i < vector.size(); i++) {
vector.get(i);
}
}
同步容器(将所有访问操作都串行化):
vector,hashtable,synchronizedSet等等
ConcurrentModificationException:快速失败异常,迭代的过程中发现元素被其它线程修改了就会马上抛出异常
想要避免快速失败:
1. 持有容器的锁,不允许其他线程修改。缺点:如果容器数据过多,造成长时间持有锁,容易导致饥饿和死锁。
2. 克隆容器,在副本上进行遍历操作。缺点:额外开销
注意:有些函数操作隐藏着迭代器遍历的操作,例如removeAll(),containsAll()
并发容器(代替同步容器,提高伸缩性并降低风险):
ConcurrentHashMap,CopyOnWriteArrayList等等
1. BlockingQueue:为空时阻塞掉获取的线程,等待元素进入队列
2. ConcurrentHashMap:分段锁,弱一致性非快速失败(迭代的过程可以进行修改,并且反应到其他的线程中),实现了复合操作接口
3. CopyOnWriteArrayList:写入时复制,迭代远大于修改操作使用,写入时会进行加锁,读取时不加锁,所以不能保证读取的数据一定最新,非快速失败
4. BlockingQueue:并发安全,重入锁
中断机制:中断机制是一种协作机制,一个线程不能强制停止正在执行的操作而去执行其他操作。
不能捕获不做处理,否则上层的代码无法处理中断
1. 传递中断:直接抛出(明智)
2. 恢复中断:为了更高层次的代码能够看见次异常
private class MyThread implements Runnable {
@Override
public void run() {
try{
//
}catch (InterruptedException e){
Thread.currentThread().interrupt();
}
}
}
同步工具类:
- 信号量(Semaphore,控制资源访问)
- 栅栏(Barrier,相对于闭锁,等待最后结束并且决定要做什么)
- 闭锁(Latch,控制线程同时开始和等待最终的结束)