JUC Day01
文章总结自B站狂神说Java
JUC 是 java.util.concurrent以及下面的atomic和locks包的简称。称为并发编程,面试高频问。
1. 概念
1.1 进程与线程
进程是一个程序运行的集合,一个进程可以包含多个线程,至少包含一个线程,线程是进程最小的运行单位。在Java中最少包含两个线程:main、GC
线程。
Java并不是真的可以开启线程,只能调用本地底层的C++方法,Java无法直接操作硬件设备。
1.2 并发与并行
并发为多个线程操作同一个资源,并行为多个线程同时执行(必须为多核CPU)。并发编程的本质是充分利用CPU的资源。
1.3 多线程状态
- NEW : 新生状态
- RUNABLE :运行状态
- BLOCKED:阻塞状态
- WATING :等待状态,死死的等待
- TIMED_WATING :超时等待
- TERMINATED :终止状态
1.3 wait 和 sleep
wait来自Object类;sleep来自Thread类;
wait会释放锁;sleep不会释放锁;
wait必须在同步代码块中使用;Sleep可以在任何地方使用;
wait不需要捕获异常;sleep必须捕获异常。
2.LOCK 锁
线程是一个单独的资源类,没有任何的附属操作
创建Lock对象:
在创建对象时可以传递参数来指定是否为公平锁:不穿参数默认为false:非公平锁:根据CPU来决定运行顺序;传递参数为true:公平锁:先来后到,等先来的线程运行完毕之后再运行后面的线程。
Lock lock = new ReentrantLock(false);
加锁:
把需要家锁的代码放在try/catch代码块中,使用finally释放锁。
public void sale(){
// 家锁
lock.lock();
try {
// 加锁之后的业务代码
if (number > 0){
System.out.println(Thread.currentThread().getName()+"卖出了"+number--+"------------->还剩"+number);
}
}catch (Exception e){
e.printStackTrace();
}finally {
// 释放锁
lock.unlock();
}
}
2.1传统的Synchronized
与Lock
的区别:
Synchornnized
是内置的Java关键字,Lock
是一个关键字;Synchorniezd
无法判断锁的状态,Lock
可以判断锁的状态;Synchornized
会自动的释放锁,Lock
必须要手动释放锁,如果不释放有可能会出现死锁问题。Synchornized
如果线程A阻塞线程B就会一直等待线程A释放锁;Lock
可以使用tryLock
方法去尝试获取锁。Synchornized
是可重入锁,不可以中断的,非公平的;Lcok
可重入锁,判断锁状态,自己设置公平或者非公平。Synchornized
适合锁少量的代码同步问题,Lock
适合大量的锁同步代码。
2.2 LOCK锁的消费者与生产者问题
lock
相较于synchornized
的notifyAll
方法变成了Condition
对象的singalAll
方法;wait
变成了await
方法。除去上述的区别外Condition
还新增了对线程执行顺序控制的方法,对线程的精准通知唤醒。
juc解决生产者消费者问题
/**
* @date 2020/7/26 13:28
* juc 解决生产者消费者问题
*/
public class ProductAndCustomerJUC {
public static void main(String[] args) {
DataA dataA = new DataA();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
dataA.inc();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
dataA.dec();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
dataA.inc();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
dataA.dec();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class DataA{
private int num = 0;
// 锁对象
Lock lock = new ReentrantLock();
// 获取condition对象用来执行 await 和 signalAll 方法
Condition condition = lock.newCondition();
/**
* 加 1 方法
* @throws InterruptedException
*/
public void inc() throws InterruptedException {
try {
lock.lock();
while (num != 0){
// 相当于 synchronized 的 wait 方法。
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
// 通知其它线程 相当于 synchronized 的 notifyAll 方法
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
/**
* 减 1 方法
* @throws InterruptedException
*/
public void dec() throws InterruptedException {
try {
lock.lock();
while (num == 0){
// 相当于 synchronized 的 wait 方法。
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
// 通知其它线程 相当于 synchronized 的 notifyAll 方法
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
Condition 对线程的精准通知唤醒
/**
* @date 2020/7/26 13:28
* condition 对象精确唤醒线程
*/
public class ProductAndCustomerJUC02 {
public static void main(String[] args) {
DataB dataB = new DataB();
new Thread(()->{
for (int i = 0; i < 10; i++) {
dataB.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
dataB.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
dataB.printC();
}
},"C").start();
}
}
class DataB{
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private int num = 1;
public void printA(){
lock.lock();
try {
// 业务代码
while (num != 1){
condition.await();
}
System.out.println(Thread.currentThread().getName()+"-------------->printA");
num = 2;
// 精准唤醒
condition1.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
// 业务代码
while (num != 2){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"----------------->printB");
num = 3;
// 精准唤醒
condition2.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
// 业务代码
while (num != 3){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"-------------->printC");
num = 1;
// 精准唤醒
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
2.2 锁的问题
- 没有锁的线程的运行顺序与调用顺序无关;
- 有锁的与休眠时间无关;
synchornized
锁的是调用方法的对象,也就是说如果一个对象调用两个方法,先拿到锁(先被调用就先拿到锁)的就先执行。- 没有同步的方法不受锁的影响。
- 两个不同的对象调用同步的方法,不受对方锁的影响。
- 加了static标识符的方法锁的是全局唯一的class对象,即使是两个对象调用方法也是同把锁。
- 如果同时存在静态同步方法和普通同步方法,这个两个方法的锁的对象是不同的,所以不需要等待对方释放锁。
3. 不安全类
3.1 在并发下list并不安全
如果使用多个线程去往list中添加数据会java.util.ConcurrentModificationException
异常,这个错误为并发修改异常。
public class ListDemo01 {
public static void main(String[] args) {
List<String> lists = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
lists.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(lists);
},String.valueOf(i)).start();
}
}
}
解决方案:
List<String> lists = new Vector<>();
List<String> lists = Collections.synchronizedList(new ArrayList<>());
List<String> lists = new CopyOnWriteArrayList<>();
推荐使用第三个,实现原理是在读写分离,在写入时避免覆盖而造成的数据问题;相较于Vector
而言它的add
方法并不是使用的synchornized
锁同步的方法,效率高。
3.2 在并发下set也不安全
在多线程共同像set添加内容时,set也会出现与list一样的异常List<String> lists = new CopyOnWriteArrayList<>();
解决方案:
Set <String > strings = Collections.synchronizedSet(new HashSet<>());
Set<String > set = new CopyOnWriteArraySet<>();
HashSet
的底层就是HashMap。
3.3 Map也不是安全的
出现的问题与上面的set和list的问题一样,推荐的解决方案是:
Map<String,Sting> map = new ConncurrnetHashMap<>();