28.3 Java进阶之线程信号量,避免死锁

1.信号量

1.1 什么是信号量?

信号量指的是对共同资源进行访问控制的对象。
在访问资源之前,线程必须从信号量获取许可,访问完之后,将许可归还给信号量。

流程如图:
在这里插入图片描述

这样有助于资源的正确的有效利用,也能保证资源数据的安全性。

1.2 创建信号量

创建信号量必须确定许可数量,可以选用公平策略。
Java中信号量是用Semaphore类声明的对象。
类图如下:
在这里插入图片描述

1.2.1 使用实例

public class SemaphoreStudy {

    public static void main(String[] args) {
        Account account = new Account();
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for(int j=0;j<200;j++){
            executorService.execute(()->{
                for(int i = 0;i<100;i++)
                    account.deposit(100);
            });
        }
        executorService.execute(()->{
            while (true)
                account.getBalance();
        });

    }


    private static class Account {

        //创建写信号量
        private static Semaphore writeSemaphore = new Semaphore(1);

        //创建读信号量
        private static Semaphore readSemaphore = new Semaphore(5);

        private int balance = 0;//余额

        public int getBalance() {
            int res = 0;
            try {
                readSemaphore.acquire();
                res = balance;
                System.out.println("获取余额:"+res);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                readSemaphore.release();
            }
            return res;
        }

        public void deposit(int amount) {
            try{
                writeSemaphore.acquire();//获得许可
                int newBalance = balance+amount;//访问余额
                balance = newBalance;//更新余额
                //Thread.sleep(1000);
            }catch(InterruptedException ex) {

            } finally {
                writeSemaphore.release();//归还信号量
            }
        }

    }
}

在这里插入图片描述

2.避免死锁

2.1 发生死锁

死锁发生的条件是某个线程一直不释放自己的锁而导致其他很多线程一直在等待阻塞状态。
简单来说,就是锁资源无法被其他线程获取,就是这个锁死了,俗称死锁。

2.1.1 死锁程序再现

//完整代码地址在文章底部
public void write(int value) {
    lock.lock();//上锁
    try{
        while(queue.size()==CAPACITY){
            System.out.println("缓冲区满了,等待读取");
            //释放锁,线程进入等待状态
            notFull.await();
        }
        queue.offer(value);//添加value到queue
        notEmpty.signal();//唤醒read
        
        //在这里添加循环,让线程一直不释放锁
        while (true){
        }
        
    }catch(InterruptedException ex) {
        ex.printStackTrace();
    } finally{
        lock.unlock();//解锁
    }
}

在这里插入图片描述

可以发现,读取数据线程因为没获取到锁,所以没办法进行数据读取。

死锁产生的经典场景是相互等待:
在这里插入图片描述

如果出现上面的情况,就可能出现线程1在等线程2释放锁,而线程2又在等线程1释放锁,故而出现死锁。

2.2 如何避免死锁

我们可以采用正确的资源排序来避免死锁,或者引入合理的中断机制,让一些线程在无法长时间停留在获取锁或释放锁的阻塞状态。

3.同步集合

3.1 什么是同步集合?

同步集合就是线程安全的集合,我们可以将同步集合使用在多线程编程上,无需对其进行同步编程(无需考虑锁的问题)
前面学习Java合集框架的类并不一定是线程安全的,就是说如果它们同时被多个线程访问和更新可能会破坏其数据。
那么怎么保证集合中数据的安全性呢?
这时候我们可以通过Collections来保护合集中的数据。
所以Collections提供6个静态方法来将合集建成同步版本,使用这些方法创建的合集称为同步集合类

我们来看下类图:(将集合转换成同步版本的方法)
在这里插入图片描述

3.2 Collections类

//Collection参数封装为一个安全的集合并返回,这是一个比较通用的方法
public static <T> Collection<T> synchronizedCollection(Collection<T> c) {
    return new SynchronizedCollection<>(c);
}
//将List对象封装为线程安全的List并返回
public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list) :
            new SynchronizedList<>(list));
}
//将Map对象封装为线程安全的Map并返回
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
    return new SynchronizedMap<>(m);
}
//将传入的Set对象封装为线程安全的Set并返回
public static <T> Set<T> synchronizedSet(Set<T> s) {
    return new SynchronizedSet<>(s);
}
//将传入的SortedMap封装为线程安全的并返回
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m) {
    return new SynchronizedSortedMap<>(m);
}
//将传入的SortSet对象封装为线程安全的SortedSet并返回
public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s) {
    return new SynchronizedSortedSet<>(s);
}

想要了解更多大家可以看看源码,看看它是怎么把普通集合变为同步集合的。
其实就是让对于的Synchronized…实现和实现类相同的接口,然后进行重写,在重写的方法中调用传入对象的方法的时候加入同步操作。
例如:

public E set(int index, E element) {
    synchronized (mutex) {return list.set(index, element);}
}

学到这里,应该算是入门多线程了,如果想要深入学习多线程,还需要另外花时间去研究和学习。

4 代码地址

Java基础学习/src/main/java/Progress/exa28_3 · 严家豆/Study - 码云 - 开源中国 (gitee.com)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小牧之

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值