Java并发——Synchronized和ReentrantLock的联系与区别

本文通过生产消费场景演示了如何使用synchronized与Lock实现线程间的同步,并对比了这两种锁机制的特点与区别,包括它们的工作原理、性能表现及应用场景。

0 前言

本文通过使用synchronized以及Lock分别完成“生产消费场景”再引出两种锁机制的关系和区别,以及一些关于锁的知识点。

本文原创,转载请注明出处:Java并发——Synchronized和ReentrantLock的联系与区别_SEU_Calvin的博客-CSDN博客

1. synchronized, wait, notify结合实现生产消费场景

1.1 生产者类

/*
*@author SEU_Calvin
*@date   2016/09/01
*/
public class Producer implements Runnable {
	@Override
	public void run() {
		int count = LockTest.count;
		while (count <= 3) {
			synchronized (LockTest.obj) {
				LockTest.count++;
				System.out.println("生产者生产产品...现在有"+LockTest.count+"个");
				if(LockTest.count >= 3){
					System.out.println("现在产品充足,待消费...");
					LockTest.obj.notify();//不会立即释放锁,而是等syn代码块执行完再释放锁
					try {
						LockTest.obj.wait();//立即释放锁,第三种释放锁是异常导致线程中止
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
			}
		}
	}
}

1.2 消费者类

/*
*@author SEU_Calvin
*@date   2016/09/01
*/
public class Consumer implements Runnable {
	@Override
	public synchronized void run() {
		int count = LockTest.count;
		while (count >= 0) {
			synchronized (LockTest.obj) {
				LockTest.count--;
				System.out.println("消费者消费产品...现在有"+LockTest.count+"个");
				if(LockTest.count <= 0){
					System.out.println("现在产品缺货,待生产...");
					LockTest.obj.notify(); // 主动释放对象锁
					try {
						LockTest.obj.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
			}
		}
	}
}

1.3 测试

public class LockTest {
	public static final Object obj = new Object();
	public static int count = 0;
	public static void main(String[] args) {
		 new Thread(new Producer()).start();
         new Thread(new Consumer()).start();
	}

}

1.4 运行结果

这个实例比较简单,主要是通过synchronized,wait, notify结合来实现线程的顺序切换

2. Lock类

除了wait()notify()以及synchronized协作完成线程同步之外,使用Lock也可以达到同样的目的

/*
*@author SEU_Calvin
*@date   2016/09/01
*/
public class ReentrantLockTest {
    private volatile int stopFalg = 10;//控制程序执行次数
    private volatile int count = 0;
    private Lock lock = new ReentrantLock();
    private ArrayList<Thread> threads = new ArrayList<Thread>();

    public static void main(String[] args) throws InterruptedException {
        final ReentrantLockTest test = new ReentrantLockTest();
        new Thread("Producer") { //开启生产者线程
            public void run() {
                test.threads.add(this);
                while (test.stopFalg > 0) {
                    test.operateResource(this.getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();
        Thread.sleep(1000); //保证生产者线程先启动,继而两者同时生产、消费
        new Thread("Consumer") {
            public void run() {
                test.threads.add(this);
                while (test.stopFalg > 0) {
                    test.operateResource(this.getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();
    }

    public void operateResource(String id) {
        lock.lock();  //lock开启,锁两个线程都会访问到的同一区域的代码
        try {
            if ("Producer".equals(id)) {
                if (count < 10) {
                    count++;
                    stopFalg--;
                    System.out.println("Producer=>" + count);
                }
            } else if ("Consumer".equals(id)) {
                if (count > 0) {
                    count--;
                    System.out.println("Consumer=>" + count);
                }
            }
        } finally {
            lock.unlock();//必须unlock
        }
    }
}

2.1 运行结果

3. 两者关系与区别汇总

(1)synchronizedJVM层面的实现的,JVM会确保释放锁,而且synchronized使用简单;而Lock是个普通类,需要在代码中finally显式释放锁lock.unlock(),但是使用灵活

(2synchronized采用的是悲观锁机制,线程获得独占锁,而其他线程只能阻塞来等待释放锁。当竞争激烈时CPU频繁的上下文切换会降低效率。而Lock乐观锁机制,每次假设不存在竞争而不上锁,若存在竞争就重试。当竞争激烈时JVM可以花更少的时间来调度线程,把更多时间用在执行线程上,因此性能最佳

(3ReentrantLock可以实现定时锁、轮询锁,可以选择放弃等待或者轮询请求。有效防止了死锁。

lock();//用来获取锁,如果锁已被其他线程获取,则进行等待
tryLock(); //尝试获取锁,若成功返回true,失败(即锁已被其他线程获取)则返回false
tryLock(long timeout, TimeUnit unit); //在拿不到锁时会等待一定的时间
//两个线程同时通过lock.lockInterruptibly()想获取某个锁时
//若线程A获取到了锁,而线程B在等待
//线程B调用threadB.interrupt()方法能够中断线程B的等待过程
lockInterruptibly();

(4synchronized是非公平锁。而ReentrantLock可以通过构造函数传入true实现公平锁,即按照申请锁顺序获得锁。

(5ReentrantLock类有一个重要的函数newCondition(),用于获取Lock上的一个条件,Condition可用于线程间通信。

4. 可重入锁

synchronized以及Lock类锁,两者都是可重入锁

class MyClass {
    public synchronized void method1() {
        method2();
    }
    public synchronized void method2() {    
    }
}

可重入锁的意思是,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是线程A已经持有了该对象的锁,这样线程A会一直等待永远不会获取到的锁。

<think> 首先,我需要理解用户的查询。用户明确要求了解synchronizedReentrantLock区别,并提供了四个引用资料作为参考。这些引用来自站内内容,我需要基于这些信息构建一个清晰、结构化的回答。 回顾引用资料的关键点:引用[1]提到了用法、机制、类型、响应中断底层实现等五个区别;引用[2]补充了公平细节;引用[3]强调了中断机制的优势;引用[4]展示了ReentrantLock的代码示例。这些内容覆盖了核心差异,但需要整合避免冗余。 接下来,规划回答框架。用户要求结构清晰,逐步解决问题,所以我准备分点对比主要区别。系统指令强调使用中文特定LaTeX格式——行内数学表达式如$...$,独立公式用$$...$$单独成段。虽然这个问题不涉及复杂公式,但我会确保任何技术术语(如"AQS")格式正确。 核心区别定在五个方面:用法、管理、公平性、中断响应底层实现。每个点需简明扼要: - 用法:synchronized更灵活(方法/代码块),ReentrantLock仅限代码块; - 管理:synchronized自动,ReentrantLock手动(需finally块确保释放); - 公平性:synchronized固定非公平,ReentrantLock可配置; - 中断响应:ReentrantLock支持线程中断,synchronized不支持; - 底层:synchronized基于JVM监视器,ReentrantLock基于AQS。 引用资料提到"投票"等高级特性,但用户需求聚焦基础区别,故不深入展开。最后按指令生成相关问题,围绕应用场景、性能或实战选择设计三个问题,帮助用户延伸学习。 完成前检查:中文表述、LaTeX合规(虽无数学内容)、引用标识[^1][^2]附加于对应描述末尾,确保回答真实可靠。 </think> 基于提供的引用资料,以下是 `synchronized` `ReentrantLock` 的核心区别总结,分点清晰对比: ### 1. **用法灵活性** - **synchronized**:可直接修饰普通方法、静态方法或代码块(如 `synchronized void method()` 或 `synchronized(object) {...}`),语法更简洁。 - **ReentrantLock**:仅能通过代码块显式调用(需创建 `Lock` 实例),如 `lock.lock(); try {...} finally { lock.unlock(); }`[^1][^4]。 ### 2. **的获取释放机制** - **synchronized**:自动加释放(进入同步块时加,退出时自动释放)。 - **ReentrantLock**:需手动调用 `lock()` `unlock()`,且必须将 `unlock()` 置于 `finally` 块避免死[^1][^4]。 ```java lock.lock(); // 手动加 try { count++; } finally { lock.unlock(); } // 手动释放 ``` ### 3. **的公平性** - **synchronized**:**仅支持非公平**(线程获取的顺序随机)。 - **ReentrantLock**:默认为**非公平**,但可通过构造函数 `new ReentrantLock(true)` 创建**公平**(按等待顺序分配)[^1][^2]。 ### 4. **响应中断能力** - **synchronized**:**不支持中断**。若线程A持有,线程B会无限阻塞等待,无法被外部中断[^1][^3]。 - **ReentrantLock**:支持**中断等候**(通过 `lockInterruptibly()` 方法)。线程B等待时可响应中断信号,转而执行其他任务,避免死[^1][^3]。 ### 5. **底层实现** - **synchronized**:基于 **JVM 监视器(Monitor)** 实现,由 JVM 管理状态(如 `monitorenter`/`monitorexit` 指令)。 - **ReentrantLock**:基于 **AQS(AbstractQueuedSynchronizer)** 实现,提供更细粒度的控制(如可重入性、条件变量)[^1][^3]。 ### 关键区别总结表 | 特性 | synchronized | ReentrantLock | |---------------------|----------------------|-----------------------------------| | **用法** | 方法/代码块 | 仅代码块 | | **释放** | 自动 | 手动(需 `finally` 块) | | **公平性** | 非公平 | 可选公平或非公平 | | **响应中断** | ❌ 不支持 | ✅ 支持(`lockInterruptibly()`) | | **底层** | JVM 监视器 | AQS 框架 | > 注:在高并发场景下,`ReentrantLock` 的灵活性中断机制可优化系统健壮性,但 `synchronized` 在简单场景中更易用[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值