【Android开发--新手必看篇】锁

Android笔记

​ ——锁
若对该知识点有更多想了解的,欢迎私信博主~~

锁:Lock
一:用途
  1. 互斥访问

    ​ 锁可以确保在任意时刻只有一个线程可以获得锁,从而保证了对共享资源的互斥访问。当一个线程获得锁后,其他线程必须等待锁被释放才能继续执行。

  2. 数据一致性

    ​ 锁可以防止多个线程同时对共享资源进行写操作,从而保证数据的一致性。只有获得锁的线程才能对资源进行写操作,其他线程必须等待。

  3. 可见性

    ​ 锁可以确保在释放锁之前,对共享资源的修改对其他线程可见。这防止了线程在不同的 CPU 缓存中看到不一致的数据。

二:分类
  1. 内置锁(synchronized)

    ​ 用于在多个线程之间同步对共享资源的访问

    public class SynchronizedExample {
        private int count = 0;
    
        public synchronized void increment() {
            count++;
        }
    }
    

    ​ 内置锁是最常见的方式,这个案例是指当有多个线程访问increment()时,只会有一个线程访问该实例方法,其他会等待

    ​ 此处介绍3种使用方式:

    ​ 1. **实例方法同步 :**确保只有一个线程访问实例方法

    public synchronized void synchronizedMethod() {
        // 同步代码块
    }
    

    ​ 2. **静态方法同步:**确保只有一个线程访问静态方法

    public static synchronized void synchronizedStaticMethod() {
        // 同步代码块
    }
    

    ​ 3. **代码块同步:**确保只有一个线程访问该对象,其他线程继续执行下面的代码

    public void someMethod() {
        synchronized (lockObject) {
            // 同步代码块
        }
    }
    
  2. 重入锁(Reentrant Lock)

    ​ 拥有显示的锁定和解锁机制

    ​ 特性和优点:

    ​ 1. 可重入性

    ​ 同一个线程可以多次获得同一个锁

    ​ 2. 公平性

    ​ 可按照请求锁的先后顺序分配锁,避免线程饥饿

    ​ 3. 可中断

    ​ 可响应中断信号

    ​ 4. 超时获取

    ​ 允许指定时间内获取锁,否则视为放弃

    ​ 5. 可实现多个条件变量

    ​ 可创建多个Condition条件对象,用于不同条件下等待和唤醒线程(例:生产者-消费者问题)

    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLockExample {
        private static ReentrantLock lock = new ReentrantLock(); // 创建一个重入锁
        private static int count = 0;
    
        public static void increment() {
            lock.lock(); // 获取锁
            try {
                for (int i = 0; i < 10000; i++) {
                    count++;
                }
            } finally {
                lock.unlock(); // 释放锁
            }
        }
    }
    

    ​ 该示例中,在操作count前后分别显示的加锁和解锁

  3. 读写锁(Read-Write Lock)

    ​ 允许多个线程同时读取共享资源,但有写操作时,所有读写操作都会被阻塞

    ​ 特性和优点:

    ​ 1. 读锁

    ​ 读锁可以并发读取资源,只要没有线程持有写锁

    ​ 2. 写锁

    ​ 当有一个线程持有写锁,其他线程将无法同时持有写锁和读锁

    ​ 3. 公平性

    ​ 可按照请求锁的先后顺序分配锁,避免线程饥饿

    ​ 4. 可中断

    ​ 可响应中断信号

    ​ 5. 锁升级和降级

    ​ 可先获取读锁再升级为写锁,也可先获取写锁再降级为读锁

    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class ReadWriteLockExample {
        private int value = 0;
        private ReadWriteLock rwLock = new ReentrantReadWriteLock();
    
        public int getValue() {
            rwLock.readLock().lock();
            try {
                return value;
            } finally {
                rwLock.readLock().unlock();
            }
        }
    
        public void setValue(int newValue) {
            rwLock.writeLock().lock();
            try {
                value = newValue;
            } finally {
                rwLock.writeLock().unlock();
            }
        }
    }
    

    ​ 该示例中,getValue()会加读锁,setValue()会加写锁,在操作数值前后分别加锁解锁

  4. 条件锁(Condition Lock)

    ​ 通过Condition条件对象实现,通常与重入锁一起使用

    ​ 特性和优点:

    ​ 1. 等待和通知机制

    ​ 不满足时等待await(),满足时唤醒signal()、signalAll()

    ​ 2. 与重入锁结合

    ​ 可与重入锁一起使用

    ​ 3. 公平性

    ​ 可按照请求锁的先后顺序分配锁,避免线程饥饿

    ​ 4. 可中断

    ​ 可响应中断信号

    ​ 5. 可实现多个条件变量

    ​ 可创建多个Condition条件对象,用于不同条件下等待和唤醒线程(例:生产者-消费者问题)

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ConditionLockExample {
        private int count = 0;
        private ReentrantLock lock = new ReentrantLock();
        private Condition condition = lock.newCondition();
    
        public void increment() {
            lock.lock();
            try {
                count++;
                condition.signalAll();
            } finally {
                lock.unlock();
            }
        }
    
        public void await() throws InterruptedException {
            lock.lock();
            try {
                while (count < 10) {
                    condition.await();
                }
            } finally {
                lock.unlock();
            }
        }
    }
    

    ​ 该示例中,increment()只要有线程操作count就会唤醒其他线程,await()只要count小于10就会一直等待被唤醒,大于或等于10后才会继续执行后续的操作

  5. 信号量(Semaphore)

    ​ 可控制共享资源的访问数量,避免过度竞争或资源耗尽,可用于解决生产者-消费者问题或连接池问题

    ​ 特性和优点:

    ​ 1. 许可数量

    ​ 获取资源时必须先获取数量许可,数量为1时则变成互斥锁

    ​ 2. 获取和释放许可

    ​ 通过acquire()获取许可,通过release()释放许可

    ​ 3. 公平性

    ​ 可按照请求锁的先后顺序分配锁,避免线程饥饿

    ​ 4. 可中断

    ​ 可响应中断信号

    import java.util.concurrent.Semaphore;
    
    public class SemaphoreExample {
        private Semaphore semaphore = new Semaphore(3); // 允许同时访问的线程数量为3
    
        public void accessResource() throws InterruptedException {
            semaphore.acquire();
            try {
                // 访问共享资源
            } finally {
                semaphore.release();
            }
        }
    }
    

    ​ 该示例中,semaphore的许可数量为3,accessResource()在操作资源时会先后获取和释放许可

  6. 倒计时门闩(CountDownLatch)

    ​ 等待一个或多个任务完成之后才会继续执行其他任务

    ​ 基本用法:

    ​ 1. 初始化计数

    ​ 需要一个初始计数值,需要等待的线程数量

    ​ 2. 等待操作

    ​ await()等待,直到计数减少到0或中断

    ​ 3. 减小计数

    ​ 用countDown()来减小计数

    import java.util.concurrent.CountDownLatch;
    
    public class CountDownLatchExample {
        private CountDownLatch latch = new CountDownLatch(3); // 设置计数器为3
    
        public void doTask() {
            // 执行任务
            latch.countDown(); // 任务完成,计数减少
        }
    
        public void awaitCompletion() throws InterruptedException {
            latch.await(); // 等待计数减少到0
        }
    }
    

    ​ 该示例中,latch的计数为3,doTask()会不断减少计数值,等到0后,awaitCompletion()里就会继续执行

  7. 循环屏障(CyclicBarrier)

    ​ 允许一组线程等待彼此达到一个共同的屏障点,然后再一起继续执行

    ​ 基本用法:

    ​ 1. 初始化计数

    ​ 需要一个初始计数值,需要等待的线程数量

    ​ 2. 等待操作

    ​ 利用await()达到屏障点,当足够线程都达到屏障点,则会释放,继续执行

    ​ 特性和优点:

    ​ 1. 可重用

    ​ 可多次利用,每次达到节点后会自动重置,继续等待下一轮

    ​ 2. 回调操作

    ​ 所有线程达到循环屏障后,可执行一个可选的回调操作

    import java.util.concurrent.CyclicBarrier;
    
    public class CyclicBarrierExample {
        private CyclicBarrier barrier = new CyclicBarrier(3); // 等待三个线程
    
        public void performTask() throws Exception {
            // 执行任务
            barrier.await(); // 等待所有线程到达屏障
            // 继续执行
        }
    }
    

    ​ 该示例中,barrier需要等待的线程数为3,当performTask()被执行三次,才会继续执行下面的程序

  8. 交换器(Exchanger)

    ​ 用于两个线程到达同步点后互相交换数据并继续执行

    ​ 基本用法:

    ​ 1. 数据交换

    ​ 调用exchange()交换数据,调用处即同步点

    ​ 特性和优点:

    ​ 1. 可重用

    ​ 可多次利用,每次达到节点后会自动重置,继续等待下一轮

    ​ 2. 可中断

    ​ 可响应中断信号

    import java.util.concurrent.Exchanger;
    
    public class ExchangerExample {
        private Exchanger<String> exchanger = new Exchanger<>();
    
        public void exchangeData(String data) throws InterruptedException {
            String receivedData = exchanger.exchange(data);
            // 使用 receivedData 进行处理
        }
    }
    

    ​ 该示例中,当有两个线程调用exchangeData()这个方法时,会交换数据,然后继续执行

  9. 并发集合(Concurrent Collections)

    ​ 提供的一组并发的数据结构

    ​ 内容:

    ​ 1. ConcurrentHashMap:并发读取和部分并发写入的哈希表

    ​ 2. ConcurrentLinkedQueue:高并发入队和出队的队列

    ​ 3. ConcurrentSkipListMap、ConcurrentSkipListSet:有序并发访问的键值表、集合

    ​ 4. CopyOnWriteArrayList、CopyOnWriteArraySet:通过复制整个集合确保线程安全,读不需要锁

    ​ 5. BlockingQueue:接口,支持阻塞操作的队列

    import java.util.concurrent.ConcurrentHashMap;
    
    public class ConcurrentHashMapExample {
        private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    
        public void addToMap(String key, int value) {
            map.put(key, value);
        }
    }
    

    ​ 该示例中,map里的键值是可并发访问的

  10. StampedLock

    ​ 除了支持读和取,还支持乐观读

    ​ 基本用法:

    ​ 1. 三种模式

    ​ 读模式、写模式(同读写锁)、乐观读模式,并发读取时不会阻塞写操作,而是验证是否正确

    ​ 2. 获取戳记

    ​ 即stamp,lock.validate(stamp)来验证乐观读锁是否有效

    ​ 特性和优点:

    ​ 1. 适应性自旋

    ​ 会根据系统的负载和竞争情况进行适应性调整

    ​ 2. 可中断

    ​ 可响应中断信号

    import java.util.concurrent.locks.StampedLock;
    
    public class StampedLockExample {
        private int value = 0;
        private StampedLock lock = new StampedLock();
    
        public void updateValue(int newValue) {
            long stamp = lock.writeLock(); // 获取写锁
            try {
                value = newValue;
            } finally {
                lock.unlockWrite(stamp); // 释放写锁
            }
        }
    
        public int getValue() {
            long stamp = lock.tryOptimisticRead(); // 获取乐观读锁
            int currentValue = value;
            if (!lock.validate(stamp)) { // 验证乐观读锁是否有效
                stamp = lock.readLock(); // 获取悲观读锁
                try {
                    currentValue = value;
                } finally {
                    lock.unlockRead(stamp); // 释放悲观读锁
                }
            }
            return currentValue;
        }
    }
    

    ​ 该示例中,updateValue()用于写操作,getValue()用于乐观读操作,会先获取乐观读锁的状态,然后赋值,验证乐观读锁状态是否正常,如果不正常,则获取悲观读锁,然后阻塞其他线程的读写操作,然后重新赋正确的值,确保数据的唯一性

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值