Java 同步锁

一、synchronized

1.类型

(1)对象锁

对象锁是作用在实例方法或者一个对象实例上面的

一个类可以有多个实例对象,因此一个类的对象锁可能会有多个

例子:

Account account = new Account();
synchronized(account){
    ....
}

(2)类锁

而类锁是作用在静态方法或者Class对象上面的

每个类只有一个Class对象,所以类锁只有一个

类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定的是实例方法还是静态方法区别的

例子:

synchronized(Account.class){
    ....
}

2.synchronizedd的使用:

(1)锁对象的引用,以及这个锁保护的代码块。

例子:

private Object obj = new Objcet();
publci void test(){
    synchronized(obj){
    ....
    }
}

(2)如果作用在实例方法上面,锁就是该方法所在的当前对象,静态synchronized方法会从Class对象上获得锁。

例子:

public synchronized void test(){
    ....
}

(3)究竟是synchronized方法好还是synchronized代码块好呢?

有一个原则就是锁的范围越小越好,加锁的目的就是将锁进去的代码作为原子性操作,因为非原子操作都不是线程安全的,因此synchronized代码块应该是在开发过程中优先考虑使用的加锁方式。

3.synchronizedd特性

(1)synchronizedd是一种悲观锁,也称为独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。

(2)synchronized不具有继承性。

如果父类 、子类中存在同样的方法。父类方法是同步的,子类是不同步的。那么子类的方法就不会无法继承父类的方法性质。必须自己加上synchronized关键字!

例子:

class A{
    public synchronized void test(){
        System.out.println("父类");
    }
}
class B extends A{
    public void test(){
        System.out.println("子类无法继承父类的锁");
    }
}

(3)重入性

关键字synchronized具有锁重入功能,当一个线程已经持有一个对象锁后,再次请求该对象锁时是可以得到该对象的锁的,这种方式是必须的,否则在一个synchronized方法内部就没有办法调用该对象的另外一个synchronized方法了。锁重入是通过为每个所关联一个计数器和一个占有它的线程,当计数器为0时,认为锁是未被占有的。线程请求一个未被占有的锁时,JVM会记录锁的占有者,并将计数器设置为1。如果同一个线程再次请求该锁,计数器会递增,每次占有的线程退出同步代码块时计数器会递减,直至减为0时锁才会被释放。

4.实现原理

(1)Java对象头

synchronized用的锁是存在Java对象头里的,那么什么是Java对象头呢?Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。

Mark Word(标记字段)

Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。

Klass Pointer(类型指针)

Klass Pointer是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,

(2)Monitor Record

Monitor Record 是线程私有的数据结构,每一个线程都有一个可用monitor record(监视记录)列表,同时还有一个全局的可用列表。每一个被锁住的对象都和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识。

Monitor Record 其结构如下:

Owner: 初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL;

EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程。

RcThis:表示blocked或waiting在该monitor record上的所有线程的个数

Nest:用来实现重入锁的计数。

HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)

Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。

同步代码块

monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁。

同步方法:

synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。

二、ReentrantLock

1.轮询锁的和定时锁

可轮询和可定时的锁请求是通过tryLock()方法实现的,和无条件获取锁不一样. ReentrantLock可以有灵活的容错机制.死锁的很多情况是由于顺序锁引起的, 不同线程在试图获得锁的时候阻塞,并且不释放自己已经持有的锁, 最后造成死锁. tryLock()方法在试图获得锁的时候,如果该锁已经被其它线程持有,则按照设置方式立刻返回,而不是一直阻塞等下去,同时在返回后释放自己持有的锁.可以根据返回的结果进行重试或者取消,进而避免死锁的发生。

2.公平性

ReentrantLock构造函数中提供公平性锁和非公平锁(默认)两种选择。所谓公平锁,线程将按照他们发出请求的顺序来获取锁,不允许插队;但在非公平锁上,则允许插队:当一个线程发生获取锁的请求的时刻,如果这个锁是可用的,那这个线程将跳过所在队列里等待线程并获得锁。我们一般希望所有锁是非公平的。因为当执行加锁操作时,公平性将讲由于线程挂起和恢复线程时开销而极大的降低性能。考虑这么一种情况:A线程持有锁,B线程请求这个锁,因此B线程被挂起;A线程释放这个锁时,B线程将被唤醒,因此再次尝试获取锁;与此同时,C线程也请求获取这个锁,那么C线程很可能在B线程被完全唤醒之前获得、使用以及释放这个锁。这是种双赢的局面,B获取锁的时刻(B被唤醒后才能获取锁)并没有推迟,C更早地获取了锁,并且吞吐量也获得了提高。在大多数情况下,非公平锁的性能要高于公平锁的性能。

3.可中断获锁获取操作

lockInterruptibly方法能够在获取锁的同时保持对中断的响应,因此无需创建其它类型的不可中断阻塞操作。

4.Condition

Lock提供的线程之间交互类:

await:阻塞线程

signal:释放阻塞的线程

signalAll:释放所有阻塞的线程

5.ReadWriteLock

(1)ReentrantLock是一种标准的互斥锁,每次最多只有一个线程能持有锁。读写锁不一样,暴露了两个Lock对象,其中一个用于读操作,而另外一个用于写操作。

(2)ReentrantReadWriteLock

ReentrantReadWriteLock实现了ReadWriteLock接口,构造器提供了公平锁和非公平锁两种创建方式。读写锁适用于读多写少的情况,可以实现更好的并发性,性能高于重入锁。

ReadLock:读锁

WriteLock:写锁

6.ReentrantLock特性

(1)ReentrantLock是可重入的锁,它不同于内置锁, 它在每次使用都需要显示的加锁和解锁, 而且提供了更高级的特性:公平锁, 定时锁, 有条件锁, 可轮询锁, 可中断锁. 可以有效避免死锁的活跃性问题.ReentrantLock实现了。

(2)ReentrantLock是一种乐观锁。

7.实现原理

(1)ReentrantLock的方法都依赖于AbstractQueuedSynchronizer的实现 (AQS)

(2)ReentrantLock的锁资源以state状态描述,利用CAS则实现对锁资源的抢占,并通过一个CLH队列阻塞所有竞争线程,在后续则逐个唤醒等待中的竞争线程。ReentrantLock继承AQS完全从代码层面实现了java的同步机制,相对于synchronized,更容易实现对各类锁的扩展。同时,AbstractQueuedSynchronizer中的Condition配合ReentrantLock使用,实现了wait/notify的功能

部分信息来自网络

你可能感兴趣的:(同步锁,锁,java)