文章目录
- Java锁的基本用法
-
- synchronized和lock
-
- synchronized
-
- 首先在没有加锁的情况下
- 加锁的情况
- Lock
-
- 首先在没有加锁的情况下
- 加锁的情况下
- 线程的通信
-
- synchronized 通过wait和notifyAll进行通信
- synchronized 需求唤醒问题
- 使用Lock定向唤醒线程
Java锁的基本用法
synchronized和lock
synchronized
synchronized是方法锁,可自动加锁和释放锁,因为其是方法级锁,也就是说将锁定整个方法的内容,所以对于业务逻辑较为复杂的方法,不建议用synchronized
需求:模拟三个人卖十张票,十张票代表不同的十个座位,正常情况下不能出现卖出两张一样的票
首先在没有加锁的情况下
测试结果
加锁的情况
直接在方法上加 synchronized
测试结果每张票都是唯一的,这里没有问题
Lock
需求:模拟三个人卖十张票,十张票代表不同的十个座位,正常情况下不能出现卖出两张一样的票
首先在没有加锁的情况下
package com.zhou.juc.sellingtickets;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author DELL
* @version 1.0
* @Description
* @date 2022/5/17 17:16
*/
public class LockTicket implements Runnable{
private int number=10;
//可重入锁
//final ReentrantLock rLock=new ReentrantLock();
//加锁的情况下
public void sellingTickets(){
//上锁
//rLock.lock();
if(number>0) {
System.out.println(Thread.currentThread().getName()+" 卖第"+(number--)+ "张票 当前剩于:" + number);
try {
//因为cpu处理效率较快,为了三个线程都能访问到这个方法,所以在此休息一下
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//解锁,这是必须的
//rLock.unlock();
}
@Override
public void run() {
for (int i=0;i<10;i++)
sellingTickets();
}
}
class LockSellTickets{
public static void main(String[]args){
LockTicket ticket=new LockTicket();
//同时开三个线程进行卖票
new Thread(ticket,"A").start();
new Thread(ticket,"B").start();
new Thread(ticket,"C").start();
}
}
测试结果出现同一张票,这是不正常的
加锁的情况下
非公平锁和公平锁
非公平锁就是谁拿到锁谁就用,这里会造成一种情况就是有可能一个线程会把所有的任务给做了,其它线程没事可做,但是这种方式效率高
公平锁就是尽可能的给各线程分配相同量的任务,因为需要进行公平处理,所以会牺牲一定效率
//参数为true为公平锁
//final ReentrantLock rLock=new ReentrantLock(true);
//默认无参为非公平锁
final ReentrantLock rLock=new ReentrantLock();
public class LockTicket implements Runnable{
private int number=10;
//参数为true为公平锁
//final ReentrantLock rLock=new ReentrantLock(true);
//默认无参为非公平锁
final ReentrantLock rLock=new ReentrantLock();
//加锁的情况下
public void sellingTickets(){
//上锁
rLock.lock();
try {
if (number > 0) { System.out.println(Thread.currentThread().getName() + " 卖第" + (number--)+ "张票 当前剩于:" + number);
try {
//因为cpu处理效率较快,为了三个线程都能访问到这个方法,所以在此休息一下
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
finally {
//解锁,这是必须的
rLock.unlock();
}
}
@Override
public void run() {
for (int i=0;i<10;i++)
sellingTickets();
}
}
class LockSellTickets{
public static void main(String[]args){
LockTicket ticket=new LockTicket();
//同时开三个线程进行卖票
new Thread(ticket,"A").start();
new Thread(ticket,"B").start();
new Thread(ticket,"C").start();
}
}
与没有加锁的区别在于这里加了可重入锁,为了避免出现异常而没有释放锁情况(死锁),需要把解锁放到finally中
测试结果是正常的情况
线程的通信
synchronized 通过wait和notifyAll进行通信
需求:一个生产者生产一个商品,两个消费者进行消费,并且生产者只能等消费者消费完之后才能生产,消费者只能等生产者生产完后才能消费
package com.zhou.juc.ThreadCommunication;
/**
* @author DELL
* @version 1.0
* @Description
* @date 2022/5/17 16:17
*/
public class SynTicket /*implements Runnable*/{
private int number=0;
//生产一个商品
public synchronized void incr() throws InterruptedException {
//判断商品是否被消费,如果没有就等待(这里一定要用while,用if会出现虚假唤醒的情况)
while (number!=0) {
//在这里进行等待,如果有多个线程调用incr,在此有可能被其它调用incr的线程进行唤醒
//因为已经进入了判断,所以此时number有可能已经大于0,所以上面必须用while,而不能用if
this.wait();
}
//生产一个商品
System.out.println(Thread.currentThread().getName()+"生产一个商品前:"+number+" 生产一个商品后:"+ (++number));
//通知其它线程可以干活了,这里是指所有线程都可以执行自己的任务了
this.notifyAll();
}
//消费一个商品
public synchronized void decr() throws InterruptedException {
//判断商品是否被生产,如果没有就等待(这里一定要用while,用if会出现虚假唤醒的情况)
while (number!=1) {
//在这里进行等待,这里有B、C两个线程调用,如果B在此等待,有可能将被C唤醒
//因为已经进入了判断,所以此时number有可能已经小于0,所以上面必须用while,而不能用if
this.wait();
}
//消费一个商品
System.out.println(Thread.currentThread().getName()+"消费一个商品前:"+number+" 消费一个商品后:"+ (--number));
//通知其它线程可以干活了,这里是指所有线程都可以执行自己的任务了
this.notifyAll();
}
//第一种方法
/*@Override
public void run() {
for (int i=0;i<10;i++) {
try {
if("A".equals(Thread.currentThread().getName()))
incr();
else
decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}*/
}
class SellTickets{
public static void main(String[]args){
SynTicket ticket=new SynTicket();
//同时开三个线程进行,A为生产者,B、C为消费者
//第一种方法
/*new Thread(ticket,"A").start();
new Thread(ticket,"B").start();
new Thread(ticket,"C").start();*/
//第二种方法
incrOrDecr("A",ticket);
incrOrDecr("B",ticket);
incrOrDecr("C",ticket);
}
private static void incrOrDecr(String name, SynTicket ticket){
new Thread(()->{
for (int i=0;i<10;i++) {
try {
if("A".equals(name))
ticket.incr();
else
ticket.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},name).start();
}
}
测试结果A生产一个,B、C才能消费一个
synchronized 需求唤醒问题
把while改成if
测试结果不符合生产一个消费一个的需求
使用Lock定向唤醒线程
需求:
1、A生产五件商品,B先消费三件,C再消费两件
2、只有等C消费完后,A再生产,整个流程循环五次
代码中用到了Condition接口,Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现使线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,阻塞队列实际上是使用了Condition来模拟线程间协作。
package com.zhou.juc.ThreadCommunication;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author DELL
* @version 1.0
* @Description
* @date 2022/5/18 10:41
*/
public class LockDirectonalAwaken {
//标志量1,默认A先执行
int flag=1;
//商品数量
int number=0;
//定义一把锁
ReentrantLock rLock=new ReentrantLock();
Condition conditionA=rLock.newCondition();
Condition conditionB=rLock.newCondition();
Condition conditionC=rLock.newCondition();
public void doA() throws InterruptedException {
rLock.lock();
try{
while(flag!=1){
//A进行等待
conditionA.await();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"生产一个商品前:"+number+" 生产一个商品后:"+ (++number));
}
//标志量改成B的
flag=2;
//定向唤醒B
conditionB.signal();
}finally{
rLock.unlock();
}
}
public void doB() throws InterruptedException {
rLock.lock();
try{
while(flag!=2){
//B进行等待
conditionB.await();
}
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+"消费一个商品前:"+number+" 消费一个商品后:"+ (--number));
}
//标志量改成C的
flag=3;
//定向唤醒C
conditionC.signal();
}finally{
rLock.unlock();
}
}
public void doC() throws InterruptedException {
rLock.lock();
try{
while(flag!=3){
//C进行等待
conditionC.await();
}
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"消费一个商品前:"+number+" 消费一个商品后:"+ (--number));
}
//标志量改成A的
flag=1;
//定向唤醒A
conditionA.signal();
}finally{
rLock.unlock();
}
}
}
class DirectonalAwaken{
public static void main(String[]args){
LockDirectonalAwaken ldAwaken=new LockDirectonalAwaken();
incrOrDecr("A",ldAwaken);
incrOrDecr("B",ldAwaken);
incrOrDecr("C",ldAwaken);
}
private static void incrOrDecr(String name, LockDirectonalAwaken ldAwaken){
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
switch (name){
case "A":
ldAwaken.doA();
break;
case "B":
ldAwaken.doB();
break;
case "C":
ldAwaken.doC();
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},name).start();
}
}
测试结果 A+5 B-3 C-2 A+5 B-3 C-2 符合顺序执行的业务逻辑