1 集合的线程安全
1.1 集合线程不安全示例
部分集合是线程不安全的,如ArrayList
在多个线程对同一个ArrayList进行读写操作时:
由于ArrayList的add()方法并没有同步过
所以多个线程进行写时,会出现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 可重入锁
synchronized和Lock都是可重入锁
但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来判断数据有没有被修改过
悲观锁:
总是认为事情向坏的方向发展,悲观锁适用于多写的场景,保护共享资源
每次去拿数据时都会认为别人会修改,所以每次都上锁
这样想拿这个数据的线程就会阻塞
在关系型数据库中的行级锁,表级锁,读锁,写锁
都是在操作前先上锁,Java中synchronized和ReentrantLock等就是悲观锁思想的实现
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;
}
}