Lock 是 Java.util.concurrent.locks 包下的接口,Lock 实现提供了比 synchronized 关键字更广泛的锁操作,锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象

Lock 是一个接口,接口的实现类有 ReentrantLock 和内部类 ReentrantReadWriteLock
- public interface Lock
- {
- void lock();
- void lockInterruptibly() throws InterruptedException;
- boolean tryLock();
- boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
- void unlock();
- Condition newCondition();
- }
- 复制代码
lock () 方法是平常使用最多的一个方法,就是用来获取锁,如果锁已被其他线程获取,则进行等待。
采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此,一般来说,使用 Lock 必须在 try-catch 块中进行,并且将释放锁的操作放在 finally 块中进行,以保证锁一定被释放,防止死锁的发生。通常使用 Lock 来进行同步的话,是以下面这种形式去使用的。
- Lock lock = ...;
- lock.lock();
- try{
- //处理任务
- }
- catch(Exception ex){
- }finally{
- lock.unlock(); //释放锁
- }
- 复制代码
该方法表示用来尝试获取锁,但是该方法是有返回值的,如果获取成功,则返回 true,如果获取失败,则返回 false,也就是说这个方法无论如何都会立即返回,在拿不到锁时也不会一直在那等待。
- private static Lock lock = new ReentrantLock();
-
- public static void main(String[] args) {
-
- if(lock.tryLock()) {
- try{
- System.out.println("成功获取锁!!");
- }catch(Exception e){
- e.printStackTrace();
- }finally{
- lock.unlock();
- }
- }else {
- System.out.println("未获取锁,先干别的");
- }
-
- }
- 复制代码
关键字 synchronized 与 wait\notify 这两个方法可以一起使用实现等待通知功能,lock 锁的 newCondition 方法返回 Condition 对象,Condition 类也可以实现等待\通知模式。
用 notify()通知时,JVM 会随机唤醒某个等待的线程,使用 Condition 类可以进行选择性的通知。它有两个常用方法:
ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更多的方法。下面通过一些实例看具体如何使用。
- public class LockTest {
- private ArrayList<Integer> arrayList = new ArrayList<Integer>();
-
- public static void main(String[] args) {
- LockTest test = new LockTest();
- new Thread(()->{test.insert(Thread.currentThread());}).start();
- new Thread(()->{test.insert(Thread.currentThread());}).start();
- new Thread(()->{test.insert(Thread.currentThread());}).start();
- }
-
- public void insert(Thread thread) {
- Lock lock = new ReentrantLock(); //注意这个地方
- lock.lock();
- try {
- System.out.println(thread.getName()+"得到了锁");
- Thread.sleep(1000);
- } catch (Exception e) {
- // TODO: handle exception
- }finally {
- lock.unlock();
- System.out.println(thread.getName()+"释放了锁");
- }
- }
- }
- 复制代码
ReentrantLock 意为可重入锁,关于可重入锁的概念将在后面详细讲述。它与 synchronized 一样,一个线程可以多次获取同一个锁。然而它比 synchronized 更安全,不会在 trylock 失败的时候发生死锁。
ReadWriteLock 它是一个接口,在它里面只定义了两个方法。
- public interface ReadWriteLock {
- /**
- * Returns the lock used for reading.
- */
- Lock readLock();
-
- /**
- * Returns the lock used for writing.
- */
- Lock writeLock();
- }
- 复制代码
两个方法,一个用来获取写锁,另一个用来获取读锁。
也就是说将文件的读写操作分开,分成 2个锁来分配给线程,从而使得多个线程可以同时读操作,或者一个线程存在写操作,读写不能同时存在。
下面的 ReentrantReadWriteLock 实现了ReadWriteLock 接口。
该实现类里面提供了两个主要的方法:readLock()和 writeLock() 用来获取读锁和写锁;
- public class ReadWriteLockTest {
-
- private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
-
- public static void main(String[] args) {
- final ReadWriteLockTest test = new ReadWriteLockTest();
-
- new Thread(()->{test.get(Thread.currentThread());}).start();
- new Thread(()->{test.get(Thread.currentThread());}).start();
- new Thread(()->{test.set(Thread.currentThread());}).start();
- }
-
- public void get(Thread thread)
- {
- rwl.readLock().lock();
- try {
- System.out.println(thread.getName()+"正在进行读操作");
- Thread.sleep(1000);
- System.out.println(thread.getName()+"读操作完毕");
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } finally
- {
- rwl.readLock().unlock();
- }
- }
- public void set(Thread thread)
- {
- rwl.writeLock().lock();
- try {
- System.out.println(thread.getName()+"正在进行写操作");
- Thread.sleep(2000);
- System.out.println(thread.getName()+"写操作完毕");
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } finally
- {
- rwl.writeLock().unlock();
- }
- }
- }
- 复制代码
线程1 和线程2 可以同时读取数据。而线程3写锁则互斥。
1、首先 synchronized 是 Java 内置关键字,在 JVM 层面,Lock 是个 Java 类;
2、synchronized 无法判断是否获取锁的状态,Lock 可以判断是否获取到锁;
3、synchronized 会自动释放锁(a 线程执行完同步代码会释放锁;b 线程执行过程中发生异常会释放锁)
4、用 synchronized 关键字的两个线程1 和线程2 ,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了。
5、synchronized 是锁可重入、不可中断,非公平,而 Lock 锁可重入,可判断,可公平(两者皆可)
6、Lock 锁适合大量同步的代码的同步问题,synchronized 锁适合代码少量的同步问题。
synchronized 的作用是保证在同一时刻,被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果。
synchronized 是 Java 中解决并发问题的一个常用的方法。
主要作用有三个:
1、修饰实例方法,作用于当前实例,进入同步代码前需要先获取实例的锁
2、修饰静态方法,作用于类的 Class 对象,进入修饰的静态方法前需要先获取类的 Class 对象的锁
3、修饰代码块,需要指定加锁对象(记作 lockobj),在进入同步代码块前需要先获取 lockobj 的锁
synchronize作用于实例方法需要注意:
当synchronized作用于静态方法时,锁的对象就是当前类的Class对象。

天生带着 Object 的属性。
认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。
synchronized 关键字和 Lock 的实现类都是悲观锁。
- //=============悲观锁的调用方式
- public synchronized void m1()
- {
- //加锁后的业务逻辑......
- }
-
- // 保证多个线程使用的是同一个lock对象的前提下
- ReentrantLock lock = new ReentrantLock();
- public void m2() {
- lock.lock();
- try {
- // 操作同步资源
- }finally {
- lock.unlock();
- }
- }
- 复制代码
乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有么有别的线程更新了这个数据。
如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作
乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。
适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
乐观锁则直接去操作同步资源,是一种无锁算法,得之我幸不得我命,再抢
乐观锁一般有两种实现方式:
- //=============乐观锁的调用方式
- // 保证多个线程使用的是同一个AtomicInteger
- private AtomicInteger atomicInteger = new AtomicInteger();
- atomicInteger.incrementAndGet();
- 复制代码
原则:能用无锁就不要用锁。能用对象锁,就不用要类锁。
线程操纵资源类
- class Phone{
- public synchronized void sendEmail(){
- System.out.println("发送邮件!");
- }
- public synchronized void sendMsg(){
- System.out.println("发送短信!");
- }
- }
- public class Lock8 {
- public static void main(String[] args) {
- Phone phone = new Phone();
- new Thread(()->{
- phone.sendEmail();
- },"a").start();
- new Thread(()->{
- phone.sendMsg();
- },"b").start();
- }
- }
-
- //发送邮件!
- //发送短信!
- 复制代码
在发送邮件方法中假如暂停 3秒钟,先打印哪个?
- class Phone{
- public synchronized void sendEmail(){
- try {
- TimeUnit.SECONDS.sleep(3);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("发送邮件!");
- }
- public synchronized void sendMsg(){
- System.out.println("发送短信!");
- }
- }
- public class Lock8 {
- public static void main(String[] args) {
- Phone phone = new Phone();
- new Thread(()->{
- phone.sendEmail();
- },"a").start();
- new Thread(()->{
- phone.sendMsg();
- },"b").start();
- }
- }
- ======
- 发送邮件!
- 发送短信!
- 复制代码
加入一个普通的方法。
答:hello 不用去拿锁
- class Phone{
- public synchronized void sendEmail(){
- try {
- TimeUnit.SECONDS.sleep(3);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("发送邮件!");
- }
- public synchronized void sendMsg(){
- System.out.println("发送短信!");
- }
- public void hello(){
- System.out.println("hello!");
- }
- }
- public class Lock8 {
- public static void main(String[] args) {
- Phone phone = new Phone();
- new Thread(()->{
- phone.sendEmail();
- },"a").start();
- new Thread(()->{
- phone.hello();
- },"b").start();
- }
- }
- ======
- hello!
- 发送邮件!
- 复制代码
两个对象
答:此时锁的不是同个对象
- class Phone{
- public synchronized void sendEmail(){
- try {
- TimeUnit.SECONDS.sleep(3);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("发送邮件!");
- }
- public synchronized void sendMsg(){
- System.out.println("发送短信!");
- }
- public void hello(){
- System.out.println("hello!");
- }
- }
- public class Lock8 {
- public static void main(String[] args) {
- Phone phone = new Phone();
- Phone phone2 = new Phone();
- new Thread(()->{
- phone.sendEmail();
- },"a").start();
- new Thread(()->{
- phone2.sendMsg();
- },"b").start();
- }
- }
- ====
- 发送短信!
- 发送邮件!
- 复制代码
加上静态方法
答:静态方法锁的是类
- class Phone{
- public static synchronized void sendEmail(){
- try {
- TimeUnit.SECONDS.sleep(3);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("发送邮件!");
- }
- public static synchronized void sendMsg(){
- System.out.println("发送短信!");
- }
- public void hello(){
- System.out.println("hello!");
- }
- }
- public class Lock8 {
- public static void main(String[] args) {
- Phone phone = new Phone();
- Phone phone2 = new Phone();
- new Thread(()->{
- phone.sendEmail();
- },"a").start();
- new Thread(()->{
- phone.sendMsg();
- },"b").start();
- }
- }
- ==
- 发送邮件!
- 发送短信!
- 复制代码
- class Phone{
- public static synchronized void sendEmail(){
- try {
- TimeUnit.SECONDS.sleep(3);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("发送邮件!");
- }
- public static synchronized void sendMsg(){
- System.out.println("发送短信!");
- }
- public void hello(){
- System.out.println("hello!");
- }
- }
- public class Lock8 {
- public static void main(String[] args) {
- Phone phone = new Phone();
- Phone phone2 = new Phone();
- new Thread(()->{
- phone.sendEmail();
- },"a").start();
- new Thread(()->{
- phone2.sendMsg();
- },"b").start();
- }
- }
- ====
- 发送邮件!
- 发送短信!
- 复制代码
- class Phone{
- public static synchronized void sendEmail(){
- try {
- TimeUnit.SECONDS.sleep(3);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("发送邮件!");
- }
- public synchronized void sendMsg(){
- System.out.println("发送短信!");
- }
- public void hello(){
- System.out.println("hello!");
- }
- }
- public class Lock8 {
- public static void main(String[] args) {
- Phone phone = new Phone();
- Phone phone2 = new Phone();
- new Thread(()->{
- phone.sendEmail();
- },"a").start();
- new Thread(()->{
- phone2.sendMsg();
- },"b").start();
- }
- }
- =====
- 发送短信!
- 发送邮件!
- 复制代码
- class Phone{
- public static synchronized void sendEmail(){
- try {
- TimeUnit.SECONDS.sleep(3);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("发送邮件!");
- }
- public synchronized void sendMsg(){
- System.out.println("发送短信!");
- }
- public void hello(){
- System.out.println("hello!");
- }
- }
- public class Lock8 {
- public static void main(String[] args) {
- Phone phone = new Phone();
- Phone phone2 = new Phone();
- new Thread(()->{
- phone.sendEmail();
- },"a").start();
- new Thread(()->{
- phone.sendMsg();
- },"b").start();
- }
- }
- =====
- 发送短信!
- 发送邮件!
- 复制代码
由 情况1-2 得知:
一个对象里面如果有多个 synchronized 方法,某一时刻内,只要一个线程去调用其中的一个 synchronized 方法了。
其他的线程都只能等待,换句话说。某一个时刻内,只能有唯一的一个线程去访问这些 synchronized 方法。
锁的是当前对象的 this,被锁定后,其他的线程都不能进入当前对象的其他的 synchronized 方法。
加上静态方法,此时锁的是类。
公平锁:
非公平锁:
可重入锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有 synchronzied 修饰的递归方法,程序第二次进入被自己阻塞了岂不是天大的笑话。所以Java 中 ReetrantLock 和 synchronized 都是可重入锁。
可重入锁的一个优点是可一定程度避免死锁。
可:可以。
重:再次
入:进入
锁:同步锁
一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。自己可以获取自己的内部锁
1、隐式锁(synchronized 关键字使用的锁)默认是可重入锁
指的是可重复可递归调用的锁。
在外层使用锁之后,在内层仍然可以使用,并且不发生死锁。
简单的来说就是:在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的
与可重入锁相反,不可重入锁不可递归调用,递归调用就发生死锁。
同步块:
- public class ReEntryLockDemo{
- public static void main(String[] args){
- final Object objectLockA = new Object();
-
- new Thread(() -> {
- synchronized (objectLockA){
- System.out.println("-----外层调用");
- synchronized (objectLockA){
- System.out.println("-----中层调用");
- synchronized (objectLockA){
- System.out.println("-----内层调用");
- }
- }
- }
- },"a").start();
- }
- }
- 复制代码
同步方法:
- public class ReEntryLockDemo{
- public synchronized void m1(){
- System.out.println("-----m1");
- m2();
- }
- public synchronized void m2(){
- System.out.println("-----m2");
- m3();
- }
- public synchronized void m3(){
- System.out.println("-----m3");
- }
-
- public static void main(String[] args){
- ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
-
- reEntryLockDemo.m1();
- }
- }
- 复制代码
2、显示锁(Lock)也有 ReetrantLock 这样的可重入锁
- public class Demo4 {
- private static int num = 0;
- private static ReentrantLock lock = new ReentrantLock();
- private static void add() {
- lock.lock();
- lock.lock();
- try {
- num++;
- } finally {
- lock.unlock();
- lock.unlock();
- }
- }
- public static class T extends Thread {
- @Override
- public void run() {
- for (int i = 0; i < 10000; i++) {
- Demo4.add();
- }
- }
- }
- public static void main(String[] args) throws InterruptedException {
- T t1 = new T();
- T t2 = new T();
- T t3 = new T();
- t1.start();
- t2.start();
- t3.start();
- t1.join();
- t2.join();
- t3.join();
- System.out.println(Demo4.num);
- }
- }
- 复制代码
上面代码中add()方法中,当一个线程进入的时候,会执行2次获取锁的操作,运行程序可以正常结束,并输出和期望值一样的30000,假如ReentrantLock是不可重入的锁,那么同一个线程第2次获取锁的时候由于前面的锁还未释放而导致死锁,程序是无法正常结束的。ReentrantLock命名也挺好的Re entrant Lock,和其名字一样,可重入锁。
代码中还有几点需要注意:
- /**
- * @create 2020-05-14 11:59
- * 在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远可以得到锁的
- */
- public class ReEntryLockDemo{
- static Lock lock = new ReentrantLock();
-
- public static void main(String[] args){
- new Thread(() -> {
- lock.lock();
- try
- {
- System.out.println("----外层调用lock");
- lock.lock();
- try
- {
- System.out.println("----内层调用lock");
- }finally {
- // 这里故意注释,实现加锁次数和释放次数不一样
- // 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。
- lock.unlock(); // 正常情况,加锁几次就要解锁几次
- }
- }finally {
- lock.unlock();
- }
- },"a").start();
-
- new Thread(() -> {
- lock.lock();
- try
- {
- System.out.println("b thread----外层调用lock");
- }finally {
- lock.unlock();
- }
- },"b").start();
-
- }
- }
- 复制代码
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
当执行 monitorenter 时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java 虚拟机会将该所对象的持有线程设置为当前线程,并且将其计数器加1 .
在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1 ,否则需要等待,直至持有线程释放该锁。
当执行 monitorexit 时,Java 将锁对象的计数器 减少1,计数器为零代表锁被释放。
死锁是指两个或两个以上线程在执行过程中,因为争抢资源而造成的一种互相等待的现象。

1、系统资源不足
2、进程运行推进的顺序不合适
3、资源分配不当
- public class DeadLockDemo{
- public static void main(String[] args){
- final Object objectLockA = new Object();
- final Object objectLockB = new Object();
-
- new Thread(() -> {
- synchronized (objectLockA){
- System.out.println(Thread.currentThread().getName()+"\t"+"自己持有A,希望获得B");
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- synchronized (objectLockB)
- {
- System.out.println(Thread.currentThread().getName()+"\t"+"A-------已经获得B");
- }
- }
- },"A").start();
-
- new Thread(() -> {
- synchronized (objectLockB){
- System.out.println(Thread.currentThread().getName()+"\t"+"自己持有B,希望获得A");
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- synchronized (objectLockA){
- System.out.println(Thread.currentThread().getName()+"\t"+"B-------已经获得A");
- }
- }
- },"B").start();
-
- }
- }
- 复制代码
1、纯命令
- jps -l
- jstack 进程编号
- 复制代码
2、图形化
jconsole