【安卓笔记】线程基本使用:锁、锁案例

发布于:2025-07-15 ⋅ 阅读:(14) ⋅ 点赞:(0)

前言:线程的基础知识。请查看我上一篇文章

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. 写在最后

至此,我们就新学会了两个线程交替执行。


网站公告

今日签到

点亮在社区的每一天
去签到