锁机制

概述:

在java多线程中,有synchronized关键字来实现线程间的同步互斥工作

那么其实还有一个更优秀的机制去完成这个“同步互斥”工作,他就是Lock对象

用得最多的是重入锁ReentrantLock读写锁ReentrantReadWriteLock。他们具有比synchronized更为强大的功能,
并且有嗅探锁定、多路分支等功能。

重入锁ReentrantLock:

在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定,不然会造成锁永远无法释放,其他线程永远进不来的结果。

public class UseReentrantLock {
	
	private Lock lock = new ReentrantLock();
	
	public void method1(){
		try {
			lock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method1..");
			Thread.sleep(1000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method1..");
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			
			lock.unlock();
		}
	}
	
	public void method2(){
		try {
			lock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method2..");
			Thread.sleep(2000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method2..");
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			
			lock.unlock();
		}
	}
	
	public static void main(String[] args) {
		final UseReentrantLock ur = new UseReentrantLock();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				ur.method1();
				ur.method2();
			}
		}, "t1");

		t1.start();
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

读写锁ReentrantReadWriteLock:

其核心就是实现读写分离的锁。在高并发访问下,尤其是读多写少的情况下,性能要远高于重入锁

synchronized、ReentrantLock,同一时间内,只能有一个线程进行访问被锁定的代码,

那么读写锁则不同,其本质是分成两个锁,即读锁、写锁。在读锁下,多个线程可以并发的进行访问,但是在写锁的时候,只能一个一个的顺序访问。

口诀:读读共享(只有所有线程都是读才是共享),写写互斥,读写互斥(重要:这个一定得互斥)。

package com.bjsxt.height.lock021;

import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

public class UseReentrantReadWriteLock {

	private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
	private ReadLock readLock = rwLock.readLock();
	private WriteLock writeLock = rwLock.writeLock();
	
	public void read(){
		try {
			readLock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			readLock.unlock();
		}
	}
	
	public void write(){
		try {
			writeLock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			writeLock.unlock();
		}
	}
	
	public static void main(String[] args) {
		
		final UseReentrantReadWriteLock urrw = new UseReentrantReadWriteLock();
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				urrw.read();
			}
		}, "t1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				urrw.read();
			}
		}, "t2");
		Thread t3 = new Thread(new Runnable() {
			@Override
			public void run() {
				urrw.write();
			}
		}, "t3");
		Thread t4 = new Thread(new Runnable() {
			@Override
			public void run() {
				urrw.write();
			}
		}, "t4");		
		
		/**
		 * 两个读锁:
		 * 当前线程:t2进入...
		 * 当前线程:t1进入...
		 * 当前线程:t2退出...
		 * 当前线程:t1退出...
		 * 结论:两个都是读锁,可以同时进入
		 */
//		t1.start();
//		t2.start();
		
		/**
		 * 读写两个锁:
		 * 当前线程:t1进入...
		 * 当前线程:t1退出...
		 * 当前线程:t3进入...
		 * 当前线程:t3退出...
		 * 结论:读写两个锁:互斥进入,谁先抢到锁,谁先进入,下个线程只有等前一个锁释放了才能进
		 */
//		t1.start(); // R 
//		t3.start(); // W
		
		/**
		 * 两个写锁:
		 * 当前线程:t3进入...
		 * 当前线程:t3退出...
		 * 当前线程:t4进入...
		 * 当前线程:t4退出...
		 * 结论:互斥
		 */
		t3.start();
		t4.start();
	}
}

锁与等待/通知(Lock替代synchronize,Condition代替wait,notify)
使用synchronized的时候,如果需要多线程间进行协作工作则需要Object的wait()和notify()、notifyAll()方法进行配合工作。

那么同样,我们在使用Lock的时候,可以使用一个新的等待/通知的类,它就是Condition。

这个Condition一定是针对具体某一把锁的。也就是在只有锁的基础之上才会产生Condition。

public class UseCondition {

	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	
	public void method1(){
		try {
			lock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入等待状态..");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "释放锁..");
			condition.await();	//相当于 Object wait
			System.out.println("当前线程:" + Thread.currentThread().getName() +"继续执行...");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void method2(){
		try {
			lock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "发出唤醒..");
			condition.signal();	 //相当于 Object notify
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public static void main(String[] args) {
		final UseCondition uc = new UseCondition();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				uc.method1();
			}
		}, "t1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				uc.method2();
			}
		}, "t2");
		
		t1.start();
		t2.start();
	}
}
当前线程:t1进入等待状态..
当前线程:t1释放锁..
当前线程:t2进入..
当前线程:t2发出唤醒..
当前线程:t1继续执行...

我们可以通过一个Lock对象产生多个Condition进行多线程间的交互,非常的灵活。可以使得部分需要唤醒的线程唤醒,其他线程则继续等待通知。
package com.bjsxt.height.lock020;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class UseManyCondition {

	private ReentrantLock lock = new ReentrantLock();
	private Condition c1 = lock.newCondition();
	private Condition c2 = lock.newCondition();
	
	public void m1(){
		try {
			lock.lock();
			System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m1等待..");
			c1.await();  //await能释放锁,但是会阻塞在这里
			System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m1继续..");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void m2(){
		try {
			lock.lock();
			System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m2等待..");
			c1.await();   //await能释放锁,但是会阻塞在这里
			System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m2继续..");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void m3(){
		try {
			lock.lock();
			System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m3等待..");
			c2.await();   //await会释放锁,但是会阻塞在这里
			System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m3继续..");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void m4(){
		try {
			lock.lock();
			System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
			c1.signalAll();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void m5(){
		try {
			lock.lock();
			System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
			c2.signal();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public static void main(String[] args) {
		final UseManyCondition umc = new UseManyCondition();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				umc.m1();
			}
		},"t1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				umc.m2();
			}
		},"t2");
		Thread t3 = new Thread(new Runnable() {
			@Override
			public void run() {
				umc.m3();
			}
		},"t3");
		Thread t4 = new Thread(new Runnable() {
			@Override
			public void run() {
				umc.m4();
			}
		},"t4");
		Thread t5 = new Thread(new Runnable() {
			@Override
			public void run() {
				umc.m5();
			}
		},"t5");
		
		t1.start();	// c1
		t2.start();	// c1
		t3.start();	// c2
		
		//过一会再唤醒线程
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		t4.start();	// c1
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t5.start();	// c2
	}
}
当前线程:t1进入方法m1等待..
当前线程:t3进入方法m3等待..
当前线程:t2进入方法m2等待..
当前线程:t4唤醒..
当前线程:t1方法m1继续..
当前线程:t2方法m2继续..
当前线程:t5唤醒..
当前线程:t3方法m3继续..
公平锁和非公平锁:
Lock lock = new ReentrantLock(boolean isFair);  //不公平锁,顺序由CPU定,公平锁,要维护顺序,性能不及不公平锁
lock用法:
tryLock(): 尝试获得锁,获得结果用true/false返回。
tryLock():在给定的时间内尝试获得锁,获得结果用true/false返回。
isFair():是否是公平锁。
isLocked():是否锁定。
getHoldCount(): 查询当前线程保持此锁的个数,也就是调用lock()次数。
lockInterruptibly():优先响应中断的锁。
getQueueLength():返回正在等待获取此锁定的线程数。
getWaitQueueLength():返回等待与锁定相关的给定条件Condition的线程数。
hasQueuedThread(Thread thread): 查询指定的线程是否正在等待此锁。
hasQueuedThreads(): //查询是否有线程正在等待此锁。
hasWaiters():查询是否有线程正在等待与此锁定有关的condition条件。

优化:
1 避免死锁
2 减小锁的持有时间
3 减小锁的粒度
4 锁的分离
5 尽量使用无锁的操作,如原子操作(Atomic系列类),volatile关键字
<think>首先,用户的问题是:“我想了解Java锁机制的相关信息 请问Java锁机制介绍”。所以,我需要提供一个清晰的解释Java锁机制的介绍。 从系统级指令: - 所有行内数学表达式必须使用$...$格式。但在这个上下文中,Java锁机制是关于编程的,可能没有数学表达式。如果有变量或概念,我需要处理。例如,提到CAS时,可能有数学概念,但可能不需要数学格式。指令说“包括变量、不等式、方程等”,但在Java锁机制中,常见的是变量如锁对象或线程概念。如果需要,我可以使用$...$,但尽量不要过度应用。 - 独立公式使用$$...$$并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题:在回答末尾生成相关问题,用§§相关问题§§分隔。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。引用来自用户提供的引用:[^1], [^2], [^3], [^4]。这些引用是关于Java锁机制的,所以我应该在回答中提到这些概念时引用它们。 用户提供的引用: - [^1]: 提到锁的分类:公平锁/非公平锁等。 - [^2]: 详细目录结构,涉及锁的分类、ReentrantReadWriteLock、synchronized等。 - [^3]: 关于可重入锁的例子。 - [^4]: 解释可重入锁的定义和优点。 我的回答需要引用这些内容,自然地添加引用标识。 回答结构: - 结构清晰,帮助用户逐步解决问题。 - 尽量保证真实可靠。 - 使用中文。 主题:Java锁机制介绍。 核心内容包括: - 锁的基本概念:锁是用于线程同步的机制,防止多个线程同时访问共享资源。 - 分类:参考引用,列出常见的锁类型: - 公平锁 vs 非公平锁 - 可重入锁 vs 不可重入锁 - 独占锁 vs 共享锁 - 互斥锁 vs 读写锁 - 乐观锁 vs 悲观锁 - 分段锁 - 偏向锁/轻量级锁/重量级锁 - 自旋锁 - Java中的具体实现:如synchronized关键字、ReentrantLock类等。 - 锁的升级和优化:如从偏向锁到重量级锁的升级。 - 优点和用例:避免死锁、提高性能。 逐步介绍: 1. 引言:什么是Java锁机制?它是用于多线程编程中确保线程安全的工具。 2. 锁的分类:简要介绍主要类别。 3. 详细解释关键锁类型,如可重入锁(参考[^4]),使用例子。 4. Java中的实现:介绍synchronized和ReentrantLock。 5. 锁的状态升级:偏向锁、轻量级锁、重量级锁(参考[^2])。 6. 总结锁的重要性。 生成相关问题:在回答结束后,添加相关问题部分,标记为§§相关问题§§。 引用添加:当提到具体概念时,自然地添加引用标识。例如: - “可重入锁允许同一个线程多次获取同一把锁,避免死锁”[^4]。 - 确保引用是自然的。 避免数学表达式除非必要。在Java锁机制中,可能涉及CAS操作,如果提到,可以写成$CAS$ if needed, but it's not mathematical. CAS是Compare and Swap,可以用$...$包裹变量。 回答结构: - 开头:介绍Java锁机制。 - 主体:分小节解释关键点。 - 结尾:总结。 - 然后相关问题。 确保回答真实可靠:基于提供的引用和一般知识。 最终输出: - 用中文。 - 末尾有相关问题。 </think>以下是关于 Java 锁机制的详细介绍。Java 锁机制是多线程编程中用于实现线程同步的关键工具,它可以防止多个线程同时访问共享资源,从而避免数据不一致和并发问题。锁机制并非 Java 独有,但 Java 提供了丰富的内置实现(如 `synchronized` 关键字和 `java.util.concurrent.locks` 包)。接下来,我将从基础概念、常见分类、Java 核心实现等方面逐步解释,帮助您全面理解。本文内容基于您提供的引用进行整理,确保真实可靠。 ### 1. Java 锁机制的基本概念 Java 锁机制主要用于解决多线程环境下的资源竞争问题。当多个线程访问共享数据时,锁可以确保只有一个线程在特定时间内执行关键代码(临界区),从而实现线程安全。锁的核心作用是实现同步,防止并发错误如死锁或数据竞争。锁机制在 Java 中广泛应用于数据库操作、缓存管理等高并发场景[^1]。 ### 2. Java 锁的主要分类 Java 锁可以根据不同特性进行分类。以下是常见分类(基于引用内容整理),每个类别都有其适用场景: - **公平锁 vs 非公平锁**: - 公平锁:线程获取锁的顺序遵循先来先服务原则(FIFO),确保公平性,但可能导致性能开销。例如,`ReentrantLock` 可以通过构造函数设置为公平模式。 - 非公平锁:线程获取锁的顺序不保证公平性,可能导致线程“插队”,但通常性能更高(默认模式)。 - **可重入锁 vs 不可重入锁**: - 可重入锁:同一个线程可以多次获取同一把锁,而不会造成死锁。这在递归调用或嵌套方法中尤为重要。Java 中的 `synchronized` 和 `ReentrantLock` 都是可重入锁。例如,在方法调用链中,线程获取锁后进入内层方法会自动持有锁[^4]。 ```java public class Test { public void a() { ReentrantLock lock = new ReentrantLock(true); // 创建一个公平的可重入锁 lock.lock(); // 第一次获取锁 lock.lock(); // 第二次获取锁不会阻塞,因为可重入 // ... 执行代码 lock.unlock(); // 需要对应解锁 lock.unlock(); } } ``` - 不可重入锁:如果线程重复获取同一把锁,会引发死锁,因为锁无法被再次获取。 - **独占锁(写锁) vs 共享锁(读锁)**: - 独占锁:仅允许一个线程持有锁,用于写操作,防止其他线程读或写。例如,`ReentrantLock` 默认是独占锁。 - 共享锁:允许多个线程同时持有锁,用于读操作,提高并发性能。`ReentrantReadWriteLock` 实现了读写分离,读锁是共享的,写锁是独占的[^2]。 - **乐观锁 vs 悲观锁**: - 乐观锁:假设并发冲突较少,不使用锁,仅在提交时检测冲突(如使用 CAS 操作)。适合读多写少场景,例如 `java.util.concurrent.atomic` 包中的原子类。 - 悲观锁:假设并发冲突频繁,始终使用锁保护资源。例如 `synchronized` 关键字。 - **其他分类**: - **互斥锁 vs 读写锁**:互斥锁是独占锁的特例;读写锁结合了共享和独占特性(如 `ReentrantReadWriteLock`)。 - **分段锁**:将数据分成多个段,每个段独立加锁,减少锁竞争。例如 `ConcurrentHashMap` 的实现。 - **偏向锁/轻量级锁/重量级锁**:这是锁状态升级的过程(详见第 4 节)。 - **自旋锁**:线程在获取锁失败时,不立即阻塞,而是循环尝试(自旋),减少上下文切换开销。常用于短时操作[^2]。 ### 3. Java 中的核心锁实现 Java 提供了多种锁实现,主要分为两类: - **内置锁(synchronized)**:使用 `synchronized` 关键字实现,是一种可重入的互斥锁。它自动管理锁状态升级(从偏向锁到重量级锁),简化编程但灵活性较低。 ```java public synchronized void method() { // 临界区代码 } ``` - **显式锁(java.util.concurrent.locks)**:通过 `Lock` 接口及其实现类提供更丰富的功能。 - `ReentrantLock`:可重入锁,支持公平性和不公平性设置,提供 `tryLock` 等高级方法。 - `ReentrantReadWriteLock`:实现读写分离,提高读操作的并发性。 - `StampedLock`(JDK 8+):邮戳锁,结合乐观锁机制,性能更高,适合读多写少场景[^2]。 - `Condition`:条件变量,允许线程在特定条件下等待或唤醒,常用于生产者-消费者模式。 ### 4. 锁的状态升级和优化 Java 锁(尤其是 `synchronized`)会根据线程竞争情况动态升级状态,以平衡性能和安全性: - **无锁**:初始状态,没有线程竞争。使用 CAS(Compare and Swap)操作实现原子更新(例如 $CAS(\text{expectedValue}, \text{newValue})$ 比较并交换变量)。 - **偏向锁**:当只有一个线程访问时,锁被“偏向”该线程,减少开销。实现原理是标记线程 ID 在对象头中[^2][^4]。 - **轻量级锁**:当多个线程轻度竞争时,使用自旋等待。线程通过 CAS 尝试获取锁,避免阻塞。 - **重量级锁**:当竞争激烈时,锁升级为重量级锁,线程进入阻塞状态,依赖操作系统互斥量。这会增加开销但保证公平性。 锁升级的过程是不可逆的,旨在优化并发性能,避免不必要的系统调用[^2]。 ### 5. 锁机制的优势和注意事项 - **优势**:可重入锁能预防死锁(如递归调用),共享锁提升读取并发性,乐观锁减少锁争用。整体上,锁机制确保了线程安全和数据一致性。 - **注意事项**: - 过度使用锁可能导致性能瓶颈或死锁(例如线程相互等待)。 - 优先选择高级锁(如 `StampedLock`)来优化性能。 - 使用工具如 `CountDownLatch` 或 `Semaphore` 辅助线程协调[^2]。 Java 锁机制是多线程编程的基石,理解其分类和实现可以帮助您设计高效的并发应用。建议通过实际代码(如上述示例)加深理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值