JUC并发编程
1. 线程间定制化通信
- 指定每个线程的执行顺序
- 为每个线程都设置一个标志位,只有当是当前线程的标志位时才让该线程执行,且执行后修改标志位,并通知下一个线程执行,只通知指定进程就行
- 为了实现定制化通知,故必须为每个线程创建一个Condition,此时通知时只需要使用对应的Condition进行通知即可
- 为了防止虚假唤醒,必须要在while()循环判断,且在内部进行进行等待,此时唤醒后仍会进行判断,从而避免了虚假唤醒问题
- 使用Lock实现线程间通信
- 创建资源类,在资源类中定义属性和操作方法
- 在操作方法中实现 判断、干活、通知
- 创建多个线程,调用操作方法
- 在Lock中存在一个Condition对象,通过newCondition()创建一个Condition对象并返回,Condition对象就相当于一个锁,可以实现进程间的通信
- 通过Condition对象的await()和signal()方法可以实现进程间的通信
- 为了防止虚假唤醒问题,必须在while()循环中进行wait(),必须在while中进行wait(),防止虚假唤醒,此时唤醒后仍会进行判断,从而防止虚假唤醒
- Lock中的Condition对象可以实现进程间的通信,通过await()和signal()来睡眠和唤醒进程
- Lock接口中通过Condition.await()方法进行等待,通过Condition.signal()方法进行唤醒
- 在哪里睡就在哪里醒,故必须在while里面睡,以防止虚假唤醒问题
2. 线程不安全问题
- List集合线程不安全演示
- List集合的方法没有Synchronized关键字标识,故多个线程同时操作时可能出现线程不安全问题,如存在并发修改问题
- 在主线程中创建的子线程可以直接调用主线程中的对象,主线程中创建的子线程可以直接调用主线程的对象
- 解决方案-Vector
- Vector集合中的方法加了Synchronized关键字,此时就可以避免出现线程不安全问题
- Vector集合中的方法都加了Synchronized关键字,故可以解决并发修改的问题,确保每次只有一个对象可以访问Synchronized作用范围的代码
- 解决方案-Collections
- 也可以通过Collections集合类中的synchronizedList()方法,传入一个List对象,此时就会返回一个线程安全的List对象
- 解决方案-CopyOnWriteArrayList
- 通过JUC提供的CopyOnWriteArrayList类来解决集合的线程不安全问题
- 即写时复制技术:读集合时可以并发读,写集合时先复制一份集合,然后写入新复制的集合,与旧集合合并,以后从新集合中读取内容
- 即实现并发读,也实现了独立写操作
- 写操作时加锁,读操作时不加锁,且写操作是先复制一份再写入,然后覆盖原来的集合
- HashSet集合线程不安全问题
- HashSet集合的方法也没有使用Synchronized关键字实现线程安全,故此时多线程操作时可能出现并发修改问题,多个线程同时修改
- 使用JUC的CopyOnWriteArraySet来解决
- 还是利用写时复制技术,实现并发读和独立写
- 写操作时先复制一份,然后对新的写入,最后覆盖原来的,从而实现独立写
- 且读操作不加锁,写操作加锁
- HashMap线程不安全演示
- HashMap中的方法也是线程不安全的,没有使用Synchronized关键字,故当多个线程时就会出现线程不安全问题,可能会并发修改问题
- 使用 ConcurrentHashMap类 来解决HashMap的线程不安全问题
3. 多线程锁
- Synchronized锁的8种情况
-
每一个对象都可以看做一个锁,当执行Synchronized同步代码块时,相当于用该对象进行加锁,此时该对象就会被占用,就无法再执行别的同步代码块,但可以执行非同步代码,因为非同步代码不需要加锁
-
每个对象只可以同时执行一个Synchronized关键字作用范围的代码,如果要执行多个,则必须解锁后才可以执行下一个同步方法,但可以同时执行多个非同步方法
-
多个对象执行同步方法时,会先抢锁,先得到的先执行,未抢到锁则会等待
-
静态方法中的this是类的Class对象,是唯一的,故不可以同时执行一个类中多个静态同步方法,因为一个对象只可以同时执行一个同步方法,被加锁了就无法再执行别的同步方法
-
Java中的每一个对象都可以作为锁,每一个对象同一时刻只可以执行一个同步方法
-
Synchronized标注普通同步方法时,此时锁的对象是调用该方法的this对象
-
标注静态方法时,此时锁的对象是类的Class对象
-
标注代码块时,此时锁的对象是代码块括号内配置的对象
-
执行同步方法的同时可以执行非同步方法
-
- 公平锁和非公平锁
- 非公平锁可能造成线程被饿死的情况,但执行的效率高,直接执行,不会先看队列是否为空
- 公平锁的效率相对低,先看队列是否为空,为空时执行,不为空时加入队列排队
- 公平锁是加入队列进行排队,非公平锁是直接执行,不会排队
- 公平锁效率低,非公平锁效率高,但会线程饿死
- 可重入锁
- 指当一个线程已经占有一个可重入锁时,在锁中再次申请该锁时会直接获得而不会阻塞
- 非可重入锁时,此时线程再次申请锁时就会造成死锁
- Synchronized(隐式,自动加锁解锁) 和 Lock(显式,手动加锁解锁) 都是可重入锁;使用Synchronized时不需要手动加锁解锁,但使用Lock时必须手动加锁解锁,且一定要保证加锁和解锁的次数对应
- 一个对象同一时间只可以执行一个同步操作,因为此时会被加锁,但可以执行多个非同步操作,因为非同步操作不会加锁
- 每一个对象都可以看作一个锁,同步方法时会对当前对象加锁,此时该对象就无法再执行别的同步方法了,一个对象同一时刻最多只可执行一个同步方法
- 死锁
- 定义:两个或者两个以上线程因为争夺资源而造成的相互等待现象,如果没有外力干涉,将无法继续执行下去,无外力干涉时,将无法继续执行
- 产生死锁的原因
- 系统资源不足
- 进程运行推进顺序不合适
- 资源分配不当
- 死锁案例:两个线程分别占有一个锁,此时分别去获取另一个线程的锁时就会出现死锁现象,此时会因为竞争资源而造成相互等待现象
- 验证是否发生了死锁
- jps :类型于linux ps -ef,查看运行中的进程
- jstack:JVM自带的堆栈跟踪工具,指定进程号来判断某个进程是否发生了死锁