Juc简称是并发包的简写。在jdk1.5之后,并发包新增了Lock接口,以及很多Lock锁的实现类用来实现锁的功能,Lock锁提供了和Synchroized类似的功能,但是Lock锁需要手动加锁和释放锁。
Lock锁的实现:
锁的可重入是指,当一个线程获得一个对象锁后,再次请求该对象锁时依旧可以获得锁。
public class Ticket {
private synchronized void print1(){
System.out.println("进入同步方法1...");
print2();
}
private synchronized void print2(){
print3();
System.out.println("进入同步方法2...");
}
private synchronized void print3(){
System.out.println("进入同步方法3...")
}
}
public class TicketMain{
public static void main(String[] args){
//创建资源类
Ticket t = new Ticket();
//创建线程
new Thread(() ->{
t.ptint1();
},"TicketThreadA").start();
}
}
预计执行结果:
线程执行print1()方法,默认this作为锁的对象,在print1()方法 中调用print()2方法,需要的注意的是锁依旧是当前this对象,这里可以得出sychronized锁是可重入性的,如果不是,可能会导致死锁。
调用 lock()方法获得锁,调用 unlock()释放锁,同样可以重入锁。
ReentrantLock锁有两个模式:
公平锁 ----> new ReentrantLock(true)
解释:可以先来先到
非公平锁 ----> new ReentrantLock() 默认是非公平锁
解释:可以插队
下面我们看一个ReentrantLock可重入锁的模版代码
//默认是非公平锁
Lock lock = new ReentrantLock();
//不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
lock.lock();
try{
//执行业务逻辑
}finally{
//一定要在try的finally内释放锁
lock.unlock();
}
ReentrantLock可重入锁的案例代码:
public class Ticket{
private int number = 30;
Lock lock = new ReentrantLock();
public void sale(){
lock.lock();
try{
//票数大于0继续卖
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+ (number--)+"票,剩余:"+number);
}
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
public class Ticket Main {
public static void main(String[] args){
}
}
package cn.bugoverflow.thread.pc;
/**
*
* 线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一个变量 num = 0
* A num+1
* B num-1
* Description: 生产者消费者问题
*
* ProducerConsumerDemo
*
* @author laiql
* @date 2022/10/17 14:22
*/
public class ProducerConsumerDemo {
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.listener(new Producer());
}
}
class Producer {
private int number = 0;
/**
* 递增
*/
public synchronized void increment() throws InterruptedException {
if (number != 0) {
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "-->" + number);
//通知其他线程
this.notifyAll();
}
/**
* 递减
*/
public synchronized void decrement() throws InterruptedException {
if (number == 0) {
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "-->" + number);
//通知其他线程
this.notifyAll();
}
}
class Consumer {
/**
* 消费者监听器
*
* @param producer 生产者
*/
public void listener(Producer producer) {
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
producer.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
producer.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "B").start();
}
}
存在问题,ABCD四个线程,虚假唤醒
我们根据官方文档的提示将if判断改成while循环
package cn.bugoverflow.thread.pc;
/**
*
* 线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一个变量 num = 0
* A num+1
* B num-1
* Description: 生产者消费者问题
*
* ProducerConsumerDemo
*
* @author laiql
* @date 2022/10/17 14:22
*/
public class ProducerConsumerDemo {
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.listener(new Producer());
}
}
class Producer {
private int number = 0;
/**
* 递增
*/
public synchronized void increment() throws InterruptedException {
while(number !=0){
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "-->" + number);
//通知其他线程
this.notifyAll();
}
/**
* 递减
*/
public synchronized void decrement() throws InterruptedException {
while(number == 0){
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "-->" + number);
//通知其他线程
this.notifyAll();
}
}
class Consumer {
/**
* 消费者监听器
*
* @param producer 生产者
*/
public void listener(Producer producer) {
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
producer.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
producer.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
producer.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
producer.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "D").start();
}
}
测试代码:
package cn.bugoverflow.thread.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Data {
//公平锁
private Lock lock = new ReentrantLock();
//监视器1
private Condition condition1 = lock.newCondition();
//监视器2
private Condition condition2 = lock.newCondition();
//监视器3
private Condition condition3 = lock.newCondition();
//number=1唤醒A
//number=2唤醒B
//number=3唤醒C
private int number = 1;
public void printA() {
lock.lock();
try {
while (number != 1) {
//等待单个
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "=>AAAA");
//唤醒B
number = 2;
condition2.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while (number != 2) {
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBBB");
number = 3;
condition3.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
while (number != 3) {
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "=>CCCC");
number = 1;
condition1.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
public class DataThreadMain {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printA();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printC();
}
}, "C").start();
}
}
分析:
如何避免死锁
(1)保持加锁顺序:当多个线程都需要加相同的几个锁的时候(例如上述情况一的死锁),按照不同的顺序枷锁那么就可能导致死锁产生,所以我们如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。
(2)获取锁添加时限:上述死锁代码情况二就是因为出现了获取锁失败无限等待的情况,如果我们在获取锁的时候进行限时等待,例如wait(1000)或者使用ReentrantLock的tryLock(1,TimeUntil.SECONDS)这样在指定时间内获取锁失败就不等待;
(3)进行死锁检测:我们可以通过一些手段检查代码并预防其出现死锁。
死锁检测
Java中死锁检测手段最多的就是使用JDK带有的jstack和JConsole工具了。下面我们以jstack为例来进行死锁的检测。
(1)先运行我们的代码程序。
(2)使用JDK的工具JPS查看运行的进程信息。
(3) 使用jps查看到的进程ID对其进行jstack 进程分析。
(4) 根据关键词"Found one Java-level deadlock"找到死锁。
Found one Java-level deadlock:
=============================
"Thread-0":
waiting to lock monitor 0x00007f9b1000a000 (object 0x0000000787ede4e8, a java.lang.Object),
which is held by "Thread-1"
"Thread-1":
waiting to lock monitor 0x00007f9b2800ff00 (object 0x0000000787ede4d8, a java.lang.Object),
which is held by "Thread-0"
Java stack information for the threads listed above:
===================================================
概述:
自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的持有者已经释放了锁,"自旋"一词就是因此而得名。
由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。
信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用(_trylock的变种能够在中断上下文使用),而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。
如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共巷资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。
自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。
跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。
无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个持有者,也就说,在任何时刻最多只能有一个执行单元获得锁。
package cn.bugoverflow.thread.lock;
import java.util.concurrent.TimeUnit;
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
// ReentrantLock reentrantLock = new ReentrantLock();
// reentrantLock.lock();
// reentrantLock.unlock();
//底层使用的自旋锁CAS
SpinlockDemo lock = new SpinlockDemo();
new Thread(() -> {
lock.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
}, "T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
lock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
}, "T2").start();
}
}
package cn.bugoverflow.thread.lock;
import java.util.concurrent.atomic.AtomicReference;
/*** 自旋锁 */
public class SpinlockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
/**
* 加锁
*/
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "==> mylock");
//自旋锁
while (!atomicReference.compareAndSet(null, thread)) {
}
}
/**
* 解锁
*/
public void myUnLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "==> myUnlock");
atomicReference.compareAndSet(thread, null);
}
}