【安卓笔记】线程基本使用:锁、锁案例
前言:线程的基础知识。请查看我上一篇文章
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 理论上自增到10000thread2.start(); // count 理论上自增到20000Thread.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;}@Overridepublic 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 理论上自增到10000thread2.start(); // count 理论上自增到20000Thread.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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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. 写在最后
至此,我们就新学会了两个线程交替执行。