文章目录
为了保证并发编程安全,java提供了各种各样的锁,将并发操作通过逻辑转化为顺序执行,了解锁的分类有助于从整体上对java锁理解。
1 线程要不要锁住同步资源:乐观锁、悲观锁
2 根据加锁粒度:类级锁、对象锁、分段锁、单个变量/字段加锁
2.1 类级锁
当一个线程获得一个该类创建的对象锁时,其它该对象实例都不能被其它线程再加锁。
实现方式:
1)使用synchronized修饰类的静态成员方法
2)使用synchronized修饰类中静态代码块,括号使用ClassName.class对类加锁。
public class Taxi{
public synchronized static void methodA(){//doSomething}
public void methodB{synchronized(TestClass.class){//doSomething}}
}
2.2 对象锁
一个对象创建一把锁,多个线程可以同时对一个类的多个实例对象同时加锁,彼此之间互不冲突。
实现方式:
1)使用synchronize修饰类的实例方法。
2)使用synchronized修饰类的代码块,括号中填写this对当前实例对象加锁。
public class Taxi{
public synchronized void methodA(){//doSomething}
public void methodB{synchronized(this){//doSomething}}
}
2.3 分段锁
对一个类中部门操作使用同步代码块,多个同步代码块之间彼此互不影响。
实现方式:
1)通过synchronized修饰代码块实现。
2) 通过lock实现
public class Taxi{
private Driver driver;
private Car car;
public void methodA(){ synchronized(driver){//doSomething}}
public void methodB{synchronized(car){//doSomething}}
}
public class Taxi{
public void methodA{
Lock lock = new ReentrantLock();
lock.lock();
try{
}finally(){
lock.unlock();
}
}
}
典型代表ConcurrentHashMap分段锁
jdk1.7 ConcurrentHashMap数据结构:
jdk7 元素更新加锁过程:
1 根据key值的hash值确定元素锁在段,同时对获得该段的锁,使用ReentrantLock对HashEntry数组进行加锁;
2 再次哈希确定元素所在链表。
3 通过循环遍历链表,确定元素位置,更新元素。
4 释放段的锁
总结:jdk1.7中锁是通过segent+hashEntry+ReentrantLock
JDK 8 ConcurrentHashMap数据结构:
1 计算key的哈希值确定要插入数组中的节点位置。
2 节点位置有四种情况:
2.1 table还没有创建,先初始化数据table。
2.2 节点为空,采用cas更新值。
2.3 节点正在迁移,走迁移插入流程。
2.4 先使用sychronized对第一个节点加锁,如果第一个节点keyhash值大于等于0,是单链表,按照单链表查找更新;否则,为红黑树,按照红黑树方式查找更新。
3 更新完之如果是对单链表中插入,判断单链表长度大于8,转化为红黑树,同时这个map中元素个数大于64。
小结:jdk 1.8使用了Node+cas + synchronized实现了加锁。
2.4 单个变量/字段加锁
使用CAS和java中原子引用类,通过保证编发编程中原子性,实现数据安全同步更新。
3 根据所的兼容性:共享锁、排他锁
4 多个资源竞争时要不要排队:排队-公平锁,不排队-非公平锁
5 一个线程中多个流程能不能获得同一把锁:重入锁和不可重入锁
重入锁:同一个线程多次进入同一把锁。当一个操作要分多个部分执行,不同部分访问了了同一份临界资源。
ReentrantLock使用:
class ReenTrantLockDemo{
private int counter = 0;
private Lock lock = new ReentrantLock();
public int getCounter() {
return counter;
}
public void method1(){
lock.lock();
try{
method2();
counter++;
}finally {
lock.unlock();
}
}
public void method2(){
lock.lock();
try{
counter++;
}finally {
lock.unlock();
}
}
}
6 锁同步资源失败是否需要阻塞:不阻塞-自旋锁,阻塞-阻塞锁
自旋锁使用
如果持有锁的线程能在短时间内释放锁,则线程不用在用户态到内核态进行切换。加锁操作执行时间很长的时候,不建议使用自旋锁。
class SpinLockDemo{
AtomicBoolean available = new AtomicBoolean(false );
public void setLock(){
while(!tryLock()){}
}
public boolean tryLock(){
return available.compareAndSet(false,true);
}
public void unlock(){
}
}