synchronized实现同步的基础:java中每个对象都可以作为锁。当线程试图访问同步代码时,必须先获得对象锁,退出或抛出异常时必须释放锁。
表现形式为: 代码块同步 和 方法同步 。
public synchronized void method1(){}复制代码
锁住的是该对象的 一个实例 ,当不同线程调用该实例对象中该同步方法,线程只有一个得到锁,其余被阻塞。但如果不同线程同时对该类的 不同实例 对象执行 该同步方法 ,则不会阻塞,因为他们使用不同的锁。
synchronized(this)( //ToDo} 或 synchronized(普通变量){ }复制代码
同上
public synchronized static void method3(){}复制代码
锁住的是该类,当不同线程调用该类的该 static同步方法 时,就只能有 一个线程获得锁 , 其余线程被阻塞 。
synchronized(Test.class){ //ToDo} 或 synchronized(静态变量){ //ToDo}复制代码
同上
锁一共有4种状态,级别从低到高依次是: 无锁状态 、 偏向锁状态 、 轻量级锁 和 重量级锁
锁可以升级但不能降级,意味着偏向锁升级为轻量级锁后不能降级为偏向锁。
大所述情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得所的代价更低而引入偏向锁。
当一个线程访问同步代码并获取锁时,会对对象头和栈帧中的锁记录里保存锁偏向的线程ID,以后该线程再进入和退出同步锁时,不需要进行CAS操作来加锁和解锁,而只是简单地测试一下对象头的Mark Word里是否存储执行当前线程的偏向锁。
偏向锁作用是:在没有别的线程竞争的时候,一直偏向当前线程,当前线程可以一直执行。
轻量级锁,由偏向锁升级而来。
偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁。
轻量级锁膨胀之后,就升级为重量级锁。
重量级锁时依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被称为互斥锁(synchronized就是重量级锁)。
偏向锁
优点:加锁和解锁不需要额外的消耗
缺点:线程存在竞争,会带来额外的锁撤销的消耗
场景:单一线程访问同步块场景
轻量级锁
优点:竞争的线程不会阻塞,提高了程序的响应速度。
缺点:线程自旋时不释放CPU
场景:追求响应时间,同步块执行速度非常快。
重量级锁
优点:线程竞争不使用自旋,释放CPU
缺点:线程阻塞,响应时间缓慢。
场景:追求吞吐量,同步块执行速度较长。
Lock,锁对象。在Lock接口出现之前,Java程序时靠synchronized关键字实现锁功能。而在Java SE5.0之后并发包中新增了Lock接口来实现锁的功能。
它能提供synchronized关键字类似的同步功能,但需要显示获得锁和释放锁,至于二者区别后文补充。
Lock接口的主要方法:
执行该方法时,若锁处于空闲状态,当前线程将获得锁。相反,如果锁已经被其他线程持有,则禁止当前线程获得锁。
若锁可用,则获得锁,并立即返回true,否则返回false。
tryLock()和Lock()的区别在于:
tryLock() 只是尝试获得锁,若 锁不可用 , 不会导致当前线程被禁止 ,当前线程仍然继续往下执行代码。
Lock() 则是一定要获得锁,如果 锁不可用 ,就 一直等待 ,在未获得锁之前,当前线程并不继续向下自行。
执行该方法时,当前线程将释放持有锁,锁只能由持有者释放,若线程没有持有锁,则执行该方法,可能导致异常方式。
条件对象,获得等待通知组件。该组件和当前的锁绑定,当前线程只有获得锁,才会调用该组件的await()方法,调用后,当前线程将释放锁。
Reentrantlock的使用很简单,只需要显示调用,获得同步锁,释放同步锁即可。
ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁 ..................... try { lock.lock(); //如果被其它资源锁定,会在此等待锁释放,达到暂停的效果 //操作 }catch(Exception e){ //异常处理 } finally { lock.unlock(); //释放锁 }复制代码
reentrantlock锁,在高并发的条件下使用的性能远远高于synchronized关键字。
并且reentratnlock 公平和非公平锁 的队列都是基于锁内部维护的一个 双向链表 ,表结点Node的值就是每一个请求当前锁的线程。
ReadWriteLock接口中主要方法如下:
public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing */ Lock writeLock(); }复制代码
ReadWriteLock管理一组锁,一个是只读锁,一个是写锁。
Java并发库中ReentrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性。
读锁 在 同一时刻 允许有 多个线程 在访问,但是 写进程 访问时,所有的 读线程 和 其他写进程 都 被阻塞 。
读写锁维护 一对锁,一个读锁和一个写锁 ,通过 读写锁分离 ,使得并发性相比一般的排它锁有很大的提升。
ReentrantReadWriteLock读写锁的几个特性 :
读写锁例子(程序来源于网上: blog.csdn.net/canot/artic…
):
public class Cache{ static Map<String,Object> map = new HashMap<String,Object>(); static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); static Lock rLock = rwl.readLock(); static Lock wLock = rwl.writeLock(); //获取一个key对应的value public static final Object get(String key){ r.lock(); try{ return map.get(key); }finally{ r.unlock(); } } //设置key对应的value并返回旧的value public static fianl Object put(String key,Object value){ w.lock(); try{ return map.put(key,value); }final{ w.unlock(); } } //清空缓存 public static fianl void clear(){ w.lock(); try{ map.clear(); } finally{ w.unlock(); } } }复制代码
锁降级是指写锁降级成为读锁。 如果当前线程持有写锁,然后将其释放再获取读锁的过程不能称为锁降级。锁降级指的在持有写锁的时候再获取读锁,获取到读锁后释放之前写锁的过程称为锁释放。
(程序来源于: blog.csdn.net/qq_38737992…
)
public void work() { reentrantReadWriteLock.readLock().lock(); if (!update) { reentrantReadWriteLock.readLock().unlock(); // 锁降级开始 reentrantReadWriteLock.writeLock().lock(); try { if (!update) { // 准备数据 ++index; try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } update = true; } reentrantReadWriteLock.readLock().lock(); } finally { reentrantReadWriteLock.writeLock().unlock(); // 锁降级结束,降级为读锁 } } try { // 使用数据 for (int i=0; i<5; i++) { try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + index); } } finally { reentrantReadWriteLock.readLock().unlock(); } } 复制代码
synchronized是关键字,而Lock是接口
synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁
synchronized自动释放锁(a线程执行完同步代码会自动释放锁,b线程执行过程中发生异常会释放锁)
lock需在finally中手动释放锁(unlock()方法释放锁),否则容易造成线程死锁。
synchronized关键字的两个线程1和线程2,若当前线程1获得锁,线程2等待,如果线程1阻塞,线程2会一直等待下去。
而lock锁不一定会等待下去,如果尝试获得不到锁,线程可以不用一直等待就结束了。
synchronized的锁 可重入、不可中断、非公平 。
lock锁 可重入,可中断、可公平
lock 锁适合 大量同步的代码的同步问题 , synchronized 锁适合 代码少量的同步问题
不可重入锁:自旋锁、wait()、notify()、notifyAll()
不可重入锁,即不可递归调用,递归调用会发生死锁
reentrantLock拥有synchronized相同的并发性和内存语义,此外还多列锁投票、定时锁等候和中断所等候
使用synchronized锁,A不释放,B将一直等待下去
使用reentrantlock锁,A不释放,B等待一段时间就会中断等待,而干别的事情。
synchronized 是在 JVM层面 上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且代码执行时出现异常,JVM会走到哪个释放锁定。但是Lock不行
在资源竞争不是很激烈的情况下, synchronized的性能优于reentrantlock锁 ,而竞争激烈的情况下,synchronized的性能下降几十倍,而reentrantlock的性能维持常态。
synchronized 会 多次自旋,以获得锁 ,在这个过程中等待的 线程不会被挂起 ,因而节省了挂起和唤醒的上下文切换的开销
而 reentrantlock , 不会自旋,而是直接挂起
因而在线程并发量不大的情况下,synchronized因为拥有自旋锁、偏向锁和轻量级锁的原因,不用将等待线程挂起,偏向锁甚至不用自旋,所以在这种情况下要比reenttrantlock高效。
默认情况下synchronized非公平锁;reentrantlock默认是非公平锁。
一个 Reentrantlock 对象可以同时绑定多个Condition对象,
而在synchronized中,锁对象的 wait()
和 notify()
或 notifyAll()
方法可以实现一个隐含的条件。
如果要和多余一个添加关联的时候, synchronized
就不得不额外地添加一个锁,而 Reentrantlock
则无须这么做只需要多次调用 new Condition()
方法即可。