Java锁使用
在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的,而JavaSE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。
Lock 锁的基本用法, l.lock()方法进行上锁, l.unlock()方法进行解锁,如下所示。
Lock lock = new ReentrantLock();
lock.lock();
try {
// ..
} finally {
lock.unlock();
}
Lock相较于Synchronized优势如下:
- 可中断获取锁:使用synchronized关键字获取锁的时候,如果线程没有获取到被阻塞了,那么这个时候该线程是不响应中断(interrupt)的,而使用Lock.lockInterruptibly()获取锁时被中断,线程将抛出中断异常。
- 可非阻塞获取锁:使用synchronized关键字获取锁时,如果没有成功获取,只有被阻塞,而使用Lock.tryLock()获取锁时,如果没有获取成功也不会阻塞而是直接返回false。
- 可限定获取锁的超时时间:使用Lock.tryLock(long time, TimeUnit unit)。
- 同一个所对象上可以有多个等待队列(Conditin,类似于Object.wait(),支持公平锁模式)。
下面是有关lock的api
两个锁的性能比较:
package Text;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReadWriteLockExample {
private static int synValue;
public static void main(String[] args){
int threadNum =0;
int maxValue =100000;
testSyns(threadNum,maxValue);
testLock(threadNum,maxValue);
}
private static void testLock(int threadNum, int maxValue) {
int[] lock = new int[0];
Long begin = System.nanoTime();
Thread[] t = new Thread[threadNum];
for (int i = 0; i < threadNum; i++) {
synValue = 0;
t[i] = new Thread(() -> {
for (int j = 0; j < maxValue; j++) {
synchronized (lock) {
++synValue;
}
}
});
}
for (int i = 0; i < threadNum; i++) {
t[i].start();
}
//main线程等待前面开启的所有线程结束
for (int i = 0; i < threadNum; i++) {
try {
t[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("使用synchronized所花费的时间为:" + (System.nanoTime() - begin));
}
private static void testSyns(int threadNum, int maxValue) {
Thread[] t =new Thread[threadNum];
Long begin =System.nanoTime();
for(int i =0;i<threadNum;i++){
Lock locks =new ReentrantLock();
synValue=0;
t[i]=new Thread(()->{
for(int j =0;j<maxValue;j++){
locks.lock();;
try{
synValue++;
}finally {
locks.unlock();
}
}
});
}
for(int i =0;i<threadNum;i++){
t[i].start();
}
for (int i =0;i<threadNum;i++){
try {
t[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("使用lock所花费的时间为:"+(System.nanoTime()-begin));
}
}
测试结果的差异还是比较明显的,Lock的性能明显高于synchronized。本次测试基于JDK1.8。
使用lock所花费的时间为:436667997
使用synchronized所花费的时间为:616882878
JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。相比之下使用Java提供的Lock对象,性能更高一些。多线程环境下,synchronized的吞吐量下降的非常严重,而ReentrankLock则能基本保持在同一个比较稳定的水平上。
到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。
公平锁和非公平锁
公平锁:先来先到
非公平锁:不是按照顺序,可插队
- 公平锁:效率相对低
- 非公平锁:效率高,但是线程容易饿死
通过这个函数Lock lock = new ReentrantLock(true);。创建一个可重入锁,true 表示公平锁,false 表示非公平锁。默认非公平锁
通过查看源码
带有参数的ReentrantLock(true)为公平锁
ReentrantLock(false)为非公平锁
主要是调用NonfairSync()与FairSync()
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
通过代码具体操作:
//第一步 创建资源类,定义属性和和操作方法
class LTicket {
//票数量
private int number = 30;
//创建可重入锁
private final ReentrantLock lock = new ReentrantLock(true);
//卖票方法
public void sale() {
//上锁
lock.lock();
try {
//判断是否有票
if(number > 0) {
System.out.println(Thread.currentThread().getName()+" :卖出"+(number--)+" 剩余:"+number);
}
} finally {
//解锁
lock.unlock();
}
}
}
public class LSaleTicket {
//第二步 创建多个线程,调用资源类的操作方法
//创建三个线程
public static void main(String[] args) {
LTicket ticket = new LTicket();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"AA").start();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"BB").start();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"CC").start();
}
}
AA :卖出:30 剩下:29
AA :卖出:29 剩下:28
AA :卖出:28 剩下:27
AA :卖出:27 剩下:26
AA: 卖出:26 剩下:25
AA :卖出:25 剩下:24
都是A线程执行,而BC线程都没执行到,出现了非公平锁
具体改变其设置可以通过可重入锁中的一个有参构造方法
修改代码为private final ReentrantLock lock = new ReentrantLock(true);
lock() 方法,unlock()方法
创建一个 Lock 锁:
Lock l = new ReentrantLock();
给代码块上锁:
l.lock();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": ");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
l.unlock();
全部代码:
package lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyLockStudy implements Runnable {
private int count;
Lock l = new ReentrantLock();
@Override
public void run() {
l.lock();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": ");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
l.unlock();
}
public static void main(String args[]) {
MyLockStudy runn = new MyLockStudy();
Thread thread1 = new Thread(runn, "thread1");
Thread thread2 = new Thread(runn, "thread2");
Thread thread3 = new Thread(runn, "thread3");
thread1.start();
thread2.start();
thread3.start();
}
}
ReentrantLock
`ReentrantLock` 是 Java 中 `java.util.concurrent.locks` 包下的一个类,它提供了一种比 `synchronized` 更灵活的线程同步控制方式。`ReentrantLock` 允许更细粒度的锁操作,例如尝试非阻塞获取锁、可中断的锁获取、超时获取锁以及公平性(即按照请求锁的顺序来获取锁)。
重入锁也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。避免死锁问题的,synchronized也可重入。
synchronized重入测试
public class ReentrantDemo {
public synchronized void method1() {
System.out.println("synchronized method1");
method2();
}
public synchronized void method2() {
System.out.println("synchronized method2");
}
public static void main(String[] args) {
ReentrantDemo reentrantDemo = new ReentrantDemo();
reentrantDemo.method1();
}
运行结果:
synchronized method1
synchronized method2
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
private int sharedResource = 0;
public void increment() {
lock.lock(); // 获取锁
try {
System.out.println("Thread " + Thread.currentThread().getName() + " has acquired the lock.");
sharedResource++; // 临界区代码
} finally {
lock.unlock(); // 释放锁
}
}
public int getSharedResource() {
return sharedResource;
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
// 创建并启动线程
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final value of shared resource: " + example.getSharedResource());
}
}
在这个示例中,我们创建了一个 ReentrantLockExample 类,它有一个 increment 方法,该方法通过 lock 来同步对 sharedResource 的访问。我们创建了两个线程,它们都尝试增加 sharedResource 的值。由于使用了 ReentrantLock,即使两个线程同时运行,sharedResource 的最终值也将是 2000。
尝试非阻塞获取锁
boolean isLockAcquired = lock.tryLock();
if (isLockAcquired) {
try {
// 执行临界区代码
} finally {
lock.unlock(); // 确保释放锁
}
} else {
// 锁不可用,执行其他操作
}
可中断的锁获取
java
try {
if (lock.tryLock(10, TimeUnit.SECONDS)) {
try {
// 执行临界区代码
} finally {
lock.unlock();
}
} else {
// 锁获取超时,执行其他操作
}
} catch (InterruptedException e) {
// 线程在获取锁的过程中被中断
Thread.currentThread().interrupt(); // 重新设置中断状态
}
ReentrantLock重入测试
public class ReentrantDemo implements Runnable {
Lock lock = new ReentrantLock();
@Override
public void run() {
set();
}
public void set() {
try {
lock.lock();
System.out.println("set 方法");
get();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();// 必须在finally中释放
}
}
public void get() {
try {
lock.lock();
System.out.println("get 方法");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantDemo reentrantDemo = new ReentrantDemo();
new Thread(reentrantDemo).start();
}
}
测试结果:同一个线程,首先在set方法中获取锁,然后调用get方法,get方法中重复获取同一个锁。两个方法都执行成功。
测试结果:同一个线程,首先在set方法中获取锁,然后调用get方法,get方法中重复获取同一个锁。两个方法都执行成功。
ReentrantReadWriteLock(读写锁)
读写锁是一种同步机制,用于控制对共享资源的并发访问。读写锁允许多个线程同时读取资源,但是在写入的时候则需要独占访问,这种锁非常适合读多写少的场景,因为它可以提高并发性能。
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private int sharedResource = 0;
public void readData() {
readLock.lock();
try {
// 读取共享资源
System.out.println("Reading data: " + sharedResource);
} finally {
readLock.unlock();
}
}
public void writeData(int data) {
writeLock.lock();
try {
// 修改共享资源
sharedResource = data;
System.out.println("Writing data: " + sharedResource);
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) {
ReadWriteLockExample example = new ReadWriteLockExample();
for (int i =0;i<5;i++){
int finalI =i;
new Thread(()->{
example.readData();
}).start();
}
//创建一个写入数据的线程
new Thread(()->{
example.writeData(10);
}).start();
//稍作延迟,以确保写入线程有足够多的时间执行
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//再次创建多个读取数据的线程,以查看写入操作后的数据
for(int i =0;i<5;i++){
int finalI =i;
new Thread(()->{
example.readData();
}).start();
}
}
}
Reading data: 0
Reading data: 0
Reading data: 0
Reading data: 0
Reading data: 0
Writing data: 10
Reading data: 10
Reading data: 10
Reading data: 10
Reading data: 10
Reading data: 10