public class Widget{
public synchronized void doSomething(){
...
}
}
public class LoggingWidget extends Widget{
public sychronized void doSomething{
super.doSomething();
}
}
2.显示锁Lock
a.关于Lock
- Lock 是java中的一个接口,其提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作。与synchronized 相同的是 互斥性 与 内存可见性。当然,它也是可重入的。java.util.concurrent.locks包中有很多实现类,常用的有 ReentrantLock、ReadWriteLock,其实现都是依赖于 java.util.concurrent.AbstractQueuedSynchronizer 类
- 下面是 Lock 接口 定义的方法
public interface Lock{
void lock();
void lockInterruptily() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time,TimeUnt unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
/**
其中前面4 个方法都是用来获取锁的,unLock()方法是用来释放锁的:
1.lock()方法是用来获取锁的,如果锁已被其他线程获取,则进行等待。使用lock必须在try-catch 块中进行,释放锁必须在finally 块中进行,因为如果抛出了异常,这个锁永远都不会被释放。
2.tryLock():请求加锁,如果加锁不成功返回false,不会发生阻塞
3.tryLock(long time,TimeOut unit)throws InterruptedException:请求加锁,如果加锁不成功会在规定的时间内阻塞,超时后会返回,支持中断
4.lockInterruptibly()throws InterruptedException:请求加锁,如果请求不到锁会发生阻塞,在阻塞过程中允许中断
*/
b.关于AbstractQueueSynchronizer
- 在java.util.concurrent 中许多可阻塞类,例如 ReentrantLock、Semaphore、ReentrantReadWriteLock、CountDownLatch、SynchronousQueue 和 FutureTask等都是基于 AQS 构建的。
3.在synchronized 和 ReentrantLock 的选择
synchronized 与 lock 锁的区别:
- synchronized 是 基于软件层面,JVM 内置的锁,而 Lock是基于CPU指令的
- synchronized 是基于块结构的,而 Lock 可以有更小的粒度
- synchronized 是不可中断的,而 Lock是可中断的
- synchronized 不用自己释放锁,而lock需要显示释放锁(在finally代码块中)
由于 synchronized 是 JVM 的内置锁,所以对它可进行的干预很少,甚至不需要自己来释放锁。除此之外, JVM还对内置锁做了一些优化,所以建议只有在内置锁无法满足需求的情况下,才选择使用 ReentrantLock。比如:可定时、可轮询、可中断的锁获取操作,以及非块结构(相当于ConcurrentHashMap 中的锁分段)。
4.锁的劣势
- 当线程恢复执行时,必须等待其他线程执行完它们的时间片以后,才能被调度指向,在挂起和恢复线程等过程中存在很大的开销,并且通常存在着较长时间的中断
- 当一个线程正在等待锁时,它不能做任何其他事情,必须等待锁释放
5.非阻塞同步机制
- 基于锁的劣势,处理器提供了一种类似于 volatile变量的机制同时还支持原子的更新操作(测试并设置,比较并交换,获取并递增)。这种方法采用的是乐观的方法,意思就是如果在操作过程中,有其他线程的干扰,就重试。
- 比较并交换 CAS:首先从V中读取值A,并根据A计算新值B,然后再通过CAS以原子方式将V中的值由A变成B(只要在这段期间没有任何线程将 V的值修改为其他值)。这种方式的主要缺点是:它将是调用者处理竞争问题(重试、回退、放弃),并且在高并发的情况下,失败的几率大大增加。
- 在java中,原子变量类就直接利用了底层的并发原语(CAS)来维持线程的安全性
四、避免使用同步的方式
- 线程封闭:也就是在单线程内访问数据
- 局部变量,其封闭在执行的线程中,位于线程的栈中,其他线程无法访问这个栈
- ThreadLocal 类,这个类能使线程中的某个值与保存值的对象关联起来,其可以防止对可变的单实例变量或全局变量进行共享。每个线程中有一个 localVariable 变量,这个变量是 一个threadLocalMap 变量,当设置值的时候会获取当前线程的 Map,然后把当前 threadLocal 添加进去,key为当前 threadLocal,value为 资源,然后获取的时候也是获取当前对象的Map,然后进行获取,从而屏蔽了线程的私有变量
- 不可变对象:因为不可变对象一定是线程安全。不可变对象必须满足一下条件:
- 对象创建以后其状态就不能修改
- 对象的所有域都是final类型
- 对象是正确创建的(在对象的创建期间,this引用没有逸出)
五、并发容器 & 同步工具类
1.并发容器
Queue:
- ConcurrentLInkedQueue:先进先出的并发队列
- PriorityQueue:是一个非并发的优先队列,Queue上的操作不会阻塞,如果队列为空,获取元素的操作将会返回空值。是由LinkedList实现
BlockingQueue:可阻塞队列,如果队列为空,获取操作将一直阻塞,直到队列出现一个 可用的元素;如果队列已满,则插入元素的操作将一直阻塞,直到队列出现可用的空间。阻塞队列一般用于生产者-消费者模式
- LinkedBlockingQueue:基于链表
- ArrayBlockingQueue:基于数组
- PriorityBlockingQUeue:按照优先级排序的队列
- SynchronousQueue:其维护一组线程直接交付给处理线程,从而降低了将数据从生产者移动到消费者的延迟
Concurrent
- CopyOnWriteArrayList| CopyOnWriteArraySet:写入时复制,也就是在每次修改时,都会创建并重新发布一个新的容器副本,因此写操作花费很大,但是在访问对象不需要进行同步
- ConcurrentMap:是 Map的同步容器类
Deque:Deque是对Queue的扩展,它是一个双端队列,实现了在队列头和队列尾的高效插入和移除
2.同步工具类
- 闭锁
闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁可以用来确保某些活动直到其他活动都完成后才继续执行,用于等待事件
CountDownLatch :使一个或多个线程等待一组事件发生,countDown方法递减计数器(表示还需等待多少事件发生),而await方法等待计数器达到零
- FutureTask — 可用于实现缓存,用于检查某项任务是否已经开始,而不是检查是否完成
Future.get 的行为取决于任务的状态,如果任务已经完成,那么 get会立即返回结果,否则get 将阻塞直到任务进入 完成状态,然后返回结果或者抛出异常。FutureTask 将计算结果从执行计算的线程传递到获取这个结果的线程- 信号量
计数信号量用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。技术信号量还可以用来实现某种资源池,或者对容器施加边界
Semaphore 中管理者一组虚拟的许可,许可的初始数量可通过构造函数来指定,在执行操作时可以首先获得许可(只要还有剩余的许可 — 阻塞),并在使用后释放许可。当初始值为1 的 S恩吗炮轰热二值信号量可以用做互斥体,并具备不可重入的加锁(相当于的加锁)
可以用做 数据库连接池 、 可以使用Semaphore 将任何一种容器变成有界阻塞容器
- 栅栏 — 等待过后还要做其他事情
闭锁是一次性对象,一旦进入终止状态,就不能被重置
栅栏:所有线程必须同时到达栅栏位置才能继续执行,用于等待其他线程,等待过后还要做其他事情,用于实现一些协议
①CyclicBarrier:等待全部线程狙击,栅栏打开,线程被释放,栅栏被重置。线程如未全部通过,则线程将全部终止并抛出 BrokenBarrierException ,若成功通过,则 await将Wie每个线程返回一个唯一的到达索引号。
完成了前一步的所有操作才可以进入下一步
②另一种形式的栅栏式 Exchanger ,它是一种两方栅栏,各方在栅栏位置上交换数据,当两个线程通过 Exchanger 交换对象时,这种交换就把这两个对象安全的发布给另一方。
a. 当缓冲区被填满时,有填充任务进行交换,当缓冲区为空时,有清空任务进行交换
b. 不仅当缓冲被填满时进行交换,并且当缓冲被填充到一定程度并保持一定时间后也进行交换
参考资料: http://www.cnblogs.com/dolphin0520/p/3923167.html
- 顶
- 0
- 踩
- 0
上一篇:linux目录结详解下一篇:C#实现简单的AStar寻路算法
热门源码