JUC基础-C4-集合的线程安全&多线程锁

1 集合的线程安全

1.1 集合线程不安全示例

部分集合是线程不安全的,如ArrayList
在多个线程对同一个ArrayList进行读写操作时:
在这里插入图片描述

由于ArrayListadd()方法并没有同步过
所以多个线程进行写时,会出现ConcurrentModificationException,即并发修改异常

1.2 集合线程不安全的解决方案

1,使用线程安全的Vector
Vector内的方法几乎都被synchronized修饰过
2,使用Collections工具类同步集合

List<String> list = Collections.synchronizedList(new ArrayList<>());
3,使用CopyOnWriteArrayList 写时复制技术

List<String> list = new CopyOnWriteArrayList<>();
当用于并发写时,将原先集合的内容复制一份到新集合 将数据写入新集合
写操作结束后,将新集合覆盖原先集合,避免了并发修改异常

2 多线程锁

2.1 公平锁&非公平锁

在使用ReentrantLock时,其无参构造默认使用非公平锁
在这里插入图片描述

类比排队的场景,非公平锁的情况下可能出现插队的情况,后面的顾客一直买不了东西
公平锁则不允许插队,按照队伍顺序进行服务,保证每个顾客都能买到东西

公平锁:多个线程按照申请锁的顺序获得锁,队列的第一个线程才能拿到锁
吞吐量下降,但能保证所有线程都能得到资源

非公平锁:多个线程会直接尝试去获取锁,获取不到进入阻塞队列
执行效率高,但可能造成线程饿死

2.2 可重入锁

synchronizedLock都是可重入锁
但syn是隐式可重入锁 
Lock是显式(手动lock/unlock)可重入锁

在这里插入图片描述
在这里插入图片描述

2.3 死锁

在这里插入图片描述
在这里插入图片描述

造成死锁的四个条件:

1,互斥
2,占有并等待
3,非抢占
4,循环等待

死锁的预防:

1,一次性申请所有资源
2,如果线程申请不到自己想要的资源,可以先释放自己占有的资源
3,按某一顺序来申请资源,并逆序释放资源

死锁的验证:

Terminal中输入
jps -l 找到目前尚未停止的进程
jstack 目标PID 查看进程状态

D:\java\JUC\About_JUC\Test1>jps -l
8644 sun.tools.jps.Jps
11528 org.jetbrains.jps.cmdline.Launcher
14280
2844 com.coisini.test4.Main

D:\java\JUC\About_JUC\Test1>jstack 2844

在这里插入图片描述

2.4 乐观锁&悲观锁

乐观锁:
总是认为事情向好的方向发展,乐观锁适用于多读的场景,可以提高吞吐量

每次去拿数据时都认为别人不会去修改,所以不会上锁
但是更新数据时,采用版本号+CAS来判断数据有没有被修改过

悲观锁:
总是认为事情向坏的方向发展,悲观锁适用于多写的场景,保护共享资源
每次去拿数据时都会认为别人会修改,所以每次都上锁
这样想拿这个数据的线程就会阻塞

在关系型数据库中的行级锁,表级锁,读锁,写锁
都是在操作前先上锁,JavasynchronizedReentrantLock等就是悲观锁思想的实现

2.5 读写锁

读锁: 共享锁,可能发生死锁
当线程1和线程2同时对1条记录进行读操作时
线程1读完后准备进行修改,线程2读完后也准备进行修改,此时形成了死锁
线程1等待线程2读完后进行修改,线程2等待线程1读完后进行修改

写锁: 独占锁,可能发生死锁
当线程1和线程2同时对1条记录进行写操作时
线程1写完后想进行修改,线程2写完后也想进行修改,此时形成了死锁
线程1等待线程2写完后进行修改,线程2等待线程1写完后进行修改

并发编程时对集合不加锁将导致数据混乱的情况:
在这里插入图片描述
为此可以使用ReentrantReadWriteLock保证读写正确

ReentrantReadWriteLock读写锁,一个资源可以被多个读线程访问
或者被一个写线程访问,但是不能和同时存在读线程和写线程,读写互斥,读读共享

public class Test {


    private volatile HashMap<String, Object> cache = new HashMap();

    private ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {
        // 写操作开始 加上写锁
        rwLock.writeLock().lock();

        try {
            System.out.println(Thread.currentThread().getName() + " 进行写操作 " + key);
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 写操作结束 打开写锁
            rwLock.writeLock().unlock();
        }

        cache.put(key, value);
        System.out.println(Thread.currentThread().getName() + " 完成写操作");
    }

    public Object get(String key)  {
        rwLock.readLock().lock();

        try {
            System.out.println(Thread.currentThread().getName() + " 进行读操作 " + key);
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rwLock.readLock().unlock();
        }

        Object res = cache.get(key);
        System.out.println(Thread.currentThread().getName() + " 完成读操作");

        return res;
    }

}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值