-
悲观锁
悲观,则会假设情况总是最坏的,即共同维护的数据总会被其他线程修改,所以每次取数据的时候都会上锁,避免其他人修改。
-
synchronized
关键字的实现也是悲观锁。 -
悲观锁的缺点
-
在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
-
一个线程持有锁会导致其它所有需要此锁的线程挂起。
-
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。
-
synchronized 锁的形式
public static void main(String[] args) {MyThread t1 = new MyThread("窗口一");MyThread t2 = new MyThread("窗口二");t1.start();t2.start();
}//错误情况
@Overridepublic void run() {while(count > 0) {System.out.println(Thread.currentThread().getName()+"售出:"+(count--) +" 票");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
//静态代码块上锁
@Overridepublic void run() {while(count > 0) {//静态代码块锁,定义同一个对象synchronized (obj) {System.out.println(Thread.currentThread().getName()+"售出:"+(count--) +" 票");}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}//方法上锁public synchronized void increse() {System.out.println(Thread.currentThread().getName()+"售出:"+(count--) +" 票");}@Overridepublic void run() {while(count > 0) {increse();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
//类上锁
synchronized(类名.Class) { //允许访问控制的代码
}
-
乐观锁
- 乐观,则假设情况总是最好的,即共同维护的数据不会被其他线程修改,所以不会上锁(即无锁)。
- CAS(Compare and Swap)
- 非阻塞性,不存在死锁问题。没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,具有更优越的性能。
- 算法过程
- 包含三个参数 CVS(V,E,N)。V表示要更新的变量,E表示预期值,N表示新值。仅当 V 值等于 E 值时,才会将 V 的值设为 N ,如果 V 值与 E 值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后, CAS 返回当前 V 的真实值。当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会挂起而是允许再次尝试。
-
lock锁
- Lock 只是一个顶层抽象接口,并没有实现,也没有规定是乐观锁还是悲观锁实现规则。 ReentrantLock ,ReentrantReadWriteLock 。
-
ReentrantLock()
ReentrantLock() 干了啥
public ReentrantLock() {sync = new NonfairSync();}
在lock的构造函数中,定义了一个NonFairSync,static final class NonfairSync extends Sync
NonfairSync 又是继承于Sync
abstract static class Sync extends AbstractQueuedSynchronizer
- lock的存储结构:一个int类型状态值(用于锁的状态变更),一个双向链表(用于存储等待中的线程)
- lock获取锁的过程:本质上是通过CAS来获取状态值修改,如果当场没获取到,会将该线程放在线程等待链表中。
- lock释放锁的过程:修改状态值,调整等待链表。
ReentrantReadWriterLock 通过两个内部类实现 Lock 接口,分别是 ReadLock,WriterLock 类。它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁,描述如下:
线程进入读锁的前提条件:
没有其他线程的写锁,
没有写请求或者有写请求,但调用线程和持有锁的线程是同一个。
线程进入写锁的前提条件:
没有其他线程的读锁
没有其他线程的写锁
一、synchronized和lock的用法区别
synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
lock:需要显示指定起始位置和终止位置。一般使用ReentrantLock类做为锁,多个线程中必须要使用一个ReentrantLock类做为对象才能保证锁的生效。且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。lock只能写在代码里,不能直接修改方法。
二、synchronized和lock性能区别
synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。
Java1.5中,synchronize是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。
Java1.6中,synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。
性能不一样:资源竞争激励的情况下,lock性能会比synchronize好,竞争不激励的情况下,synchronize比lock性能好,synchronize会根据锁的竞争情况,从偏向锁-->轻量级锁-->重量级锁升级,而且编程更简单。
锁机制不一样:synchronize是在JVM层面实现的,系统会监控锁的释放与否。lock是JDK代码实现的,需要手动释放,在finally块中释放。可以采用非阻塞的方式获取锁。
Synchronized的编程更简洁,lock的功能更多更灵活,缺点是一定要在finally里面 unlock()资源才行。
其他区别:
1,首先二者的关键字不同,sync是关键字,lock是接口。
2,sync会主动释放锁,lock不会,而且容易产生死锁。
3,sync默认是非公平锁,悲观锁。Lock用的是乐观锁方式,乐观锁实现的机制就是CAS操作。