前言:线程的基础知识。请查看我上一篇文章
0. 环境:
电脑:Windows10
Android Studio: 2024.3.2
编程语言: Java
Gradle version:8.11.1
Compile Sdk Version:35
Java 版本:Java11
1. 锁:类锁、对象锁、显示锁
类锁
常用的锁:synchronized
JDK内置锁,常用于单例模式
代码示例:
//方式一,比较消耗资源。每一次线程调用get方法时,都要判断锁
public static synchronized GpsEngine getGpsEngine() {
if (gpsEngine == null) {
gpsEngine = new GpsEngine();
}
return gpsEngine;
}
//方式二,懒加载方式,比较推荐
public static GpsEngine getGpsEngine() {
if (gpsEngine == null) {
synchronized (GpsEngine.class) {
if (gpsEngine == null) {
gpsEngine = new GpsEngine();
}
}
}
return gpsEngine;
}
对象锁
示例代码:
package com.liosen.lib;
public class CountTest {
private int count = 0;
//自增函数1,不带锁
public void increaseCount1() {
count++;
}
// 自增函数2,带对象锁
public synchronized void increaseCount2() {
count++;
}
// 自增函数3,带对象锁。与自增函数2一样
public void increaseCount3() {
synchronized (CountTest.this) {
count++;
}
}
public static void main(String[] args) throws InterruptedException {
CountTest ct = new CountTest();
CountThread thread1 = new CountThread(ct);
CountThread thread2 = new CountThread(ct);
thread1.start(); // count 理论上自增到10000
thread2.start(); // count 理论上自增到20000
Thread.sleep(50);// 不加这行,会导致result为0. 打印的行为,在自增行为之前
/**
* 如果执行了increaseCount1(),会发现打印出来的count不确定,理论上为10000~20000之间浮动。(我浮动在13000~15000之间)
* 如果执行了increaseCount2(),打印出来的count确定为20000
* 如果执行了increaseCount3(),打印出来的count确定为20000
*/
System.out.println("count result : " + ct.count);
}
private static class CountThread extends Thread {
private CountTest ct;
public CountThread(CountTest ct) {
this.ct = ct;
}
@Override
public void run() {
super.run();
// 自增10000次
for (int i = 0; i < 10000; i++) {
ct.increaseCount1();
// ct.increaseCount2();
// ct.increaseCount3();
}
}
}
}
显示锁
package com.liosen.lib;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
private int count = 0;
//自增函数1,不带锁
public void increaseCount1() {
count++;
}
private Lock lock = new ReentrantLock(); // ReentrantLock可重入锁。可重入锁:递归时可以重新进入
public void increaseCount2() {
lock.lock();
try {
count++;// 模拟逻辑代码
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); // 保证解锁一定能执行。避免逻辑代码报错后,increaseCount2锁死
}
}
public static void main(String[] args) throws InterruptedException {
LockDemo ld = new LockDemo();
CountThread thread1 = new CountThread(ld);
CountThread thread2 = new CountThread(ld);
thread1.start(); // count 理论上自增到10000
thread2.start(); // count 理论上自增到20000
Thread.sleep(50);// 不加这行,会导致result为0.
/**
* 如果执行了increaseCount1(),会发现打印出来的count不确定,理论上为10000~20000之间浮动。(我浮动在13000~15000之间)
* 如果执行了increaseCount2(),打印出来的count确定为20000
*/
System.out.println("count result : " + ld.count);
}
private static class CountThread extends Thread {
private LockDemo ld;
public CountThread(LockDemo ld) {
this.ld = ld;
}
@Override
public void run() {
super.run();
// 自增10000次
for (int i = 0; i < 10000; i++) {
// ld.increaseCount1();
ld.increaseCount2();
}
}
}
}
以上就表示完三种锁。如果看过我上一篇文章,这部分代码应该很好理解。
2. 锁的案例演示(等待、唤醒 机制)
上篇文章中,我们实现了 线程A执行完后,再执行线程B,使用到join()函数。
现在有个新需求,线程A和线程B依次交替执行。
模拟情景:生产一件商品后,立即消费售卖一件商品。
我们可以使用 wait()和notify()函数来实现。
实现代码如下:
package com.liosen.lib;
/**
* 该demo为了做到,生产一件商品后,消费一件商品。
* 思路,执行生产商品的线程后,唤醒消费的线程;执行消费商品的线程后,唤醒生产的线程。
* 即 生产的线程 和 消费的线程 之间切换。
*/
class Res { // 商品资源属性
public int count; // 商品数量
public int produceCount; // 已生产的数量
public int consumeCount; // 已消费的数量
private boolean flag; // 用于标记运行,先生产 --> 后消费。可以理解成,当前是否有商品
// 生产一件商品
public synchronized void put() {
if (!flag) {
count += 1;
produceCount++;
System.out.println("produce one, total is: "+ count + "; produceCount: " + produceCount + "<-------------");
flag = true;
}
/**
* notify()的目的是,唤醒另一个wait()。如果没有wait()的线程,默认不处理。
*/
notify(); // 必须在锁内执行
try {
/**
* 等待消费线程执行,所以此时需要wait等待
*/
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//取出商品来售卖
public synchronized void getAndSell() {
if (flag) {
count -= 1;
consumeCount++;
System.out.println("------------->consume one, total is :" + count + "; consumeCount: " + consumeCount + "\n");
flag = false;
}
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 与上面put()中 一样的意思 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
notify();
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}
}
class ProduceRunnable implements Runnable {
private Res res;
public ProduceRunnable(Res res) {
this.res = res;
}
@Override
public void run() {
// 假设生产20个商品
for (int i = 0; i < 20; i++) {
res.put();
}
}
}
class ConsumeRunnable implements Runnable {
private Res res;
public ConsumeRunnable(Res res) {
this.res = res;
}
@Override
public void run() {
// 假设消费20个商品
for (int i = 0; i < 20; i++) {
res.getAndSell();
}
}
}
public class ThreadCommunicationDemo {
public static void main(String[] args) {
Res res = new Res();
// 创建生产任务
ProduceRunnable produceRunnable = new ProduceRunnable(res);
// 创建消费任务
ConsumeRunnable consumeRunnable = new ConsumeRunnable(res);
Thread produceThread = new Thread(produceRunnable);
Thread cusumeThread = new Thread(consumeRunnable);
produceThread.start();
cusumeThread.start();
}
}
注:wait()函数会给当前锁解锁。所以线程A和线程B交替执行时,即使加锁了,另外一个线程也可以进入,就是因为wait()解锁了。
注2:notify()唤醒存在不确定性。如果线程超过2,可能会唤醒其他wait()。所以需要看你的逻辑代码如何实现的。
注3:如果需要唤醒所有wait(),可以使用notifyAll()
3. 写在最后
至此,我们就新学会了两个线程交替执行。