1.1 简介
java.util.concurrent.locks.Lock 是一个类似于synchronized 块的线程同步机制。但是Lock比synchronized 块更加灵活。Lock是个接口,有个实现类是ReentrantLock。
1.2 Lock和syncronized的区别
synchronized是Java语言的关键字。Lock是一个类。
synchronized不需要用户去手动释放锁,发生异常或者线程结束时自动释放锁;Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
lock可以配置公平策略,实现线程按照先后顺序获取锁。
提供了trylock方法 可以试图获取锁,获取到或获取不到时,返回不同的返回值 让程序可以灵活处理。
lock()和unlock()可以在不同的方法中执行,可以实现同一个线程在上一个方法中lock()在后续的其他方法中unlock(),比syncronized灵活的多。
1.3 Lock接口抽象方法
Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
void lock():获取锁,如果锁不可用,则出于线程调度的目的,当前线程将被禁用,并且在获取锁之前处于休眠状态。
Lock lock = ...;
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}
boolean tryLock():如果锁可用立即返回true,如果锁不可用立即返回false。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException:如果锁可用,则此方法立即返回true。 如果该锁不可用,则当前线程将出于线程调度目的而被禁用并处于休眠状态,直到发生以下三种情况之一为止:当前线程获取到该锁;当前线程被其他线程中断,并且支持中断获取锁;经过指定的等待时间如果获得了锁,则返回true,没获取到锁返回false。
void unlock():释放锁。释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
1.4 ReentrantLock
重入锁也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。避免死锁问题的,synchronized也可重入。
synchronized重入测试:
package com.weige.javaskillpoint;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@Slf4j
@SpringBootTest
class JavaSkillPointApplicationTests {
public synchronized void eat() {
log.info("小明正在认真的吃饭:"+ "synchronized eat");
drinkWater();
}
public synchronized void drinkWater() {
log.info("小明吃饭到一半噎着了,马上喝水:"+ "synchronized drinkWater");
}
public static void main(String[] args) {
JavaSkillPointApplicationTests reentrantDemo = new JavaSkillPointApplicationTests();
reentrantDemo.eat();
}
}
package com.weige.javaskillpoint;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
@SpringBootTest
class JavaSkillPointApplicationTests {
public static void main(String[] args) {
ReentrantDemo reentrantDemo = new ReentrantDemo();
reentrantDemo.eat();
}
}
@Slf4j
class ReentrantDemo implements Runnable {
Lock lock = new ReentrantLock();
@Override
public void run() {
eat();
}
public void eat() {
try {
lock.lock();
log.info("lock加锁:" + "小明正在认真的吃饭");
drinkWater();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();// 必须在finally中释放
}
}
public void drinkWater() {
try {
lock.lock();
log.info("lock加锁:" + "小明吃饭到一半噎着了,马上喝水");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
同一个线程,首先在eat方法中获取锁,然后调用drinkWater方法,drinkWater方法中重复获取同一个锁。两个方法都执行成功。
1.5 NonReentrantLock
不可重入锁:
package com.weige.javaskillpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
@SpringBootTest
class JavaSkillPointApplicationTests {
public static void main(String[] args) {
NonLockDemo nonLockDemo = new NonLockDemo();
nonLockDemo.eat();
}
}
@Slf4j
class NonLockDemo implements Runnable {
Lock lock = new NonReentrantLock();
@Override
public void run() {
eat();
}
public void eat() {
try {
lock.lock();
log.info("lock加锁:" + "小明正在认真的吃饭");
drinkWater();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();// 必须在finally中释放
}
}
public void drinkWater() {
try {
lock.lock();
log.info("lock加锁:" + "小明吃饭到一半噎着了,马上喝水");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
class NonReentrantLock extends ReentrantLock {
@Override
public void lock() {
if (isHeldByCurrentThread()) {
throw new RuntimeException("当前线程已经持有锁,不可重入");
}
super.lock();
}
}
同一个线程先调用eat方法并获取到锁后继续调用drinkWater方法,此时set方法还未执行所得释放,在drinkWater方法中尝试获取锁时返回false。
1.6 ReentrantReadWriteLock
读写锁,可以分别获取读锁或写锁。也就是说将数据的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。读锁使用共享模式;写锁使用独占模式;读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁。
writeLock():获取写锁。
readLock():获取读锁。
执行三个线程进行读写操作,并设置一个屏障,线程依次准备就绪后未获取锁之前都在等待,当第三个线程执行 cyclicBarrier.await();后屏障解除,三个线程同时执行。
package com.weige.javaskillpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
@SpringBootTest
class JavaSkillPointApplicationTests {
public static void main(String[] args) {
WriteAndReadLockTest.start();
}
}
@Slf4j
class WriteAndReadLockTest {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
// 参数解释
// corePoolSize 表示线程池的常驻核心线程数。如果设置为 0,则表示在没有任何任务时,销毁线程池;如果大于 0,即使没有任务时也会保证线程池的线程数量等于此值。但需要注意,此值如果设置的比较小,则会频繁的创建和销毁线程(创建和销毁的原因会在本课时的下半部分讲到);如果设置的比较大,则会浪费系统资源,所以开发者需要根据自己的实际业务来调整此值。
// maximumPoolSize 表示线程池在任务最多时,最大可以创建的线程数。官方规定此值必须大于 0,也必须大于等于 corePoolSize,此值只有在任务比较多,且不能存放在任务队列时,才会用到。
// keepAliveTime 表示线程的存活时间,当线程池空闲时并且超过了此时间,多余的线程就会销毁,直到线程池中的线程数量销毁的等于 corePoolSize 为止,如果 maximumPoolSize 等于 corePoolSize,那么线程池在空闲的时候也不会销毁任何线程。
// unit 表示存活时间的单位,它是配合 keepAliveTime 参数共同使用的。
// workQueue 表示线程池执行的任务队列,当线程池的所有线程都在处理任务时,如果来了新任务就会缓存到此任务队列中排队等待执行。
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
private static int i = 1999;
public static void start() {
threadPoolExecutor.execute(()->{
read(Thread.currentThread());
});
threadPoolExecutor.execute(()->{
write(Thread.currentThread());
});
threadPoolExecutor.execute(()->{
read(Thread.currentThread());
});
threadPoolExecutor.shutdown();
}
// cyclicBarrier.await()解释
// 在CyclicBarrier上等待的线程数量达到parties,则所有线程被释放,继续执行。
// 当前线程被中断,则抛出InterruptedException异常,并停止等待,继续执行。
// 其他等待的线程被中断,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
// 其他等待的线程超时,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
// 其他线程调用CyclicBarrier.reset()方法,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
private static void read(Thread thread) {
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
reentrantReadWriteLock.readLock().lock();
try {
log.info("读线程 "+ thread.getName() + " 开始执行, i=" + i);
Thread.sleep(1000);
log.info(thread.getName() +"读取结束!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantReadWriteLock.readLock().unlock();
}
}
private static void write(Thread thread) {
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
reentrantReadWriteLock.writeLock().lock();
try {
i++;
log.info("写线程 "+ thread.getName() + " is doing, i=" + i);
log.info(thread.getName() +"写入结束!");
} finally {
reentrantReadWriteLock.writeLock().unlock();
}
}
}
线程1先获取到了读锁,因为读锁时可以共享的,所有线程3也可以获取到读锁,线程1、3读操作完成后将读锁释放后,线程2才能获取到写锁并开始执行写操作。
1.7 公平锁与非公平锁
公平锁:就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。
非公平锁:比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。
实现:
ReentrantLock:模式是非公平锁。也可通过构造方法创建公平锁。
package com.weige.javaskillpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
@SpringBootTest
class JavaSkillPointApplicationTests {
public static void main(String[] args) {
// 通过构造方法 传true则为公平锁
ReentrantLock reentrantLock = new ReentrantLock(true);
// 默认为非公平锁
// public ReentrantLock() {
// sync = new ReentrantLock.NonfairSync();
// }
// public ReentrantLock( boolean fair){
// sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync();
// }
}
}
ReentrantReadWriteLock:默认是非公平锁,也可以通过构造方法创建公平锁。
package com.weige.javaskillpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
@SpringBootTest
class JavaSkillPointApplicationTests {
public static void main(String[] args) {
// 通过构造方法 传true则为公平锁
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(true);
// 默认为非公平锁
// public ReentrantReadWriteLock() {
// this(false);
// }
// public ReentrantReadWriteLock(boolean fair) {
// sync = fair ? new ReentrantReadWriteLock.FairSync() : new ReentrantReadWriteLock.NonfairSync();
// readerLock = new ReentrantReadWriteLock.ReadLock(this);
// writerLock = new ReentrantReadWriteLock.WriteLock(this);
// }
}
}
优缺点:非公平锁性能高于公平锁性能。首先,在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。而且,非公平锁能更充分的利用cpu的时间片,尽量的减少cpu空闲的状态时间。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/234913.html