JUC多线程并发编程(二)

文章目录

  • JUC多线程并发编程(二)
    • 读写锁ReadWriteLock
    • 阻塞队列BlockingQueue
    • SychronousQueue同步队列
    • 线程池
    • Volatile关键字
    • 单例模式
    • CAS
    • 自旋锁
    • 死锁

读写锁ReadWriteLock

什么是读写锁?
读写锁包括独占锁(写锁)和共享锁(读锁)。
接口:

public interface ReadWriteLock {Lock readLock();Lock writeLock();
}

实现类:

public class ReentrantReadWriteLockimplements ReadWriteLock, java.io.Serializable

独占锁:表示只有一个线程可以持有这个锁
共享锁:表示可以多个线程可以持有这个锁,适用于读多写少

应用场景:在多线程的情况下,当对存储区(数组,map等等)进行添加操作,会出现一个线程正在添加中,另一个线程也会进行添加,此时需要添加独占锁来确保添加操作的原子性,而读操作可以多线程同时读取的,满足其并发量。也就是说,读写不共存,读读共存,写写不共存。

  static class MyCacheLock{private volatile Map<String,Object> map = new HashMap<>();//读写锁private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();public void put(String key, Object value){//写锁readWriteLock.writeLock().lock();try {System.out.println("Thread"+Thread.currentThread().getName()+"写入"+key);map.put(key,value);System.out.println("Thread"+Thread.currentThread().getName()+"写入成功!");}catch (Exception e){e.printStackTrace();}finally {readWriteLock.writeLock().unlock();}}public void get(String key){//读锁readWriteLock.readLock().lock();try{System.out.println("Thread"+Thread.currentThread().getName()+"读取"+key);Object result = map.get(key);System.out.println("Thread"+Thread.currentThread().getName()+"读取结果"+result);}catch (Exception e){e.printStackTrace();}finally {readWriteLock.readLock().unlock();}}}
}

阻塞队列BlockingQueue

什么是阻塞队列?
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。

阻塞队列有三个常用的实现类:

ArrayBlockingQueue:由数组结构组成的有界阻塞队列
LinkedBlockingQueue:由链表结构组成的有界阻塞队列
SynchronousQueue:不存储元素的阻塞队列,即单个元素队列

阻塞队列有四个处理方式以及对应的API:

方法\处理方式      抛出异常        返回特殊值       一直阻塞        超时退出
插入方法            add(e)      offer(e)        put(e)      offer(e,time,unit)
移除方法            remove()    poll()          take()      poll(time,unit)
检查方法            element()   peek()          不可用     不可用

抛出异常是指阻塞队列满或空,再去队列存储或获取,就会抛出异常
返回特殊值是指阻塞队列满或空,再去队列存储或获取,就会返回false或null
一直阻塞是指阻塞队列满或空,再去队列存储或获取,就会使当前操作一直阻塞等待
超时退出是指阻塞队列满或空,再去队列存储或获取,可以设置其超时时间,在时间之内会阻塞。

应用场景:常用于生产者和消费者的场景,生产者—》往队列里添加元素,消费者—》从队列里拿取元素。在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起。我们不用担心什么时候需要阻塞线程,什么时候需要唤醒线程。

SychronousQueue同步队列

什么是同步队列?
没有容量,一个生产线程,当它生产产品(put的时候),如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞。等待一个消费线程调用take操作,take操作会唤醒该生产线程,同时消费线程会获取生产线程的产品(即数据传递)。这样的一个过程称为一次配对过程(也可以先take,后put 原理相同)

public class SynchronousQueueDemo {public static void main(String[] args) {BlockingQueue<String> blockingQueue = new SynchronousQueue<>();new Thread(()->{try {System.out.println(Thread.currentThread().getName()+" put 1");blockingQueue.put("1");System.out.println(Thread.currentThread().getName()+" put 2");blockingQueue.put("2");System.out.println(Thread.currentThread().getName()+" put 3");blockingQueue.put("3");} catch (InterruptedException e) {e.printStackTrace();}},"T1").start();new Thread(()->{try {TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName()+"take"+blockingQueue.take());TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName()+"take"+blockingQueue.take());TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName()+"take"+blockingQueue.take());} catch (InterruptedException e) {e.printStackTrace();}},"T2").start();}
}
结果:
T1 put 1
T2take1
T1 put 2
T2take2
T1 put 3
T2take3

线程池

什么是线程池?
预先创建好多个线程,放在池中,这样可以在需要使用线程的时候直接获取,避免多次重复创建、销毁带来的开销。若是每次都是需要创建线程-》执行任务-》销毁线程,这样会大大的性能消耗。线程池就是把一些能复用的东西放到一起,而不是去销毁。

线程池的三大方法:

Executors.newSingleThreadExecutor()      单个线程
newFixedThreadPool(int nThreads)        创建一个固定的线程池大小
newCachedThreadPool()                   线程池的大小可以伸缩,遇强则强(要看cpu是几核的)

单个线程:

 public class Demo01 {9     public static void main(String[] args) {10
11         ExecutorService threadpool = Executors.newSingleThreadExecutor();     //单个线程
12
13         try {14             for (int i = 0; i < 10; i++) {15                 //使用了线程池之后,使用线程池来创建线程
16                 threadpool.execute(()->{17                     System.out.println(Thread.currentThread().getName()+" ok");
18                 });
19             }
20         } catch (Exception e) {21             e.printStackTrace();
22         } finally {23             //线程池用完,程序结束,关闭线程池
24             threadpool.shutdown();   //(为确保关闭,将关闭方法放入到finally中)
25         }
26     }
27 }

创建一个固定的线程池大小:

public class Demo01 {9     public static void main(String[] args) {10
11         //最多5个线程同时执行,从控制台中查看结果
12         ExecutorService threadpool = Executors.newFixedThreadPool(5);   //创建一个固定的线程池的大小,(5个线程)
13
14         try {15             for (int i = 0; i < 10; i++) {16                 //使用了线程池之后,使用线程池来创建线程
17                 threadpool.execute(()->{18                     System.out.println(Thread.currentThread().getName()+" ok");
19                 });
20             }
21         } catch (Exception e) {22             e.printStackTrace();
23         } finally {24             //线程池用完,程序结束,关闭线程池
25             threadpool.shutdown();   //(为确保关闭,将关闭方法放入到finally中)
26         }
27     }
28 }

遇强则强(要看cpu是几核的):

public class Demo01 {9     public static void main(String[] args) {10
11         ExecutorService threadpool = Executors.newCachedThreadPool();   //缓存池,可伸缩的, 遇强则强,遇弱则弱
12
13         try {14             for (int i = 0; i < 10; i++) {15                 //使用了线程池之后,使用线程池来创建线程
16                 threadpool.execute(()->{17                     System.out.println(Thread.currentThread().getName()+" ok");
18                 });
19             }
20         } catch (Exception e) {21             e.printStackTrace();
22         } finally {23             //线程池用完,程序结束,关闭线程池
24             threadpool.shutdown();   //(为确保关闭,将关闭方法放入到finally中)
25         }
26     }
27 }

线程池的七大参数:
看三大方法的源码:

public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}

可以看得出来三大方法都是通过ThreadPoolExecutor这个类来创建的,此时只需要看这个类的构造函数:

public ThreadPoolExecutor(int corePoolSize,  int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}

corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:存活时间
TimeUnit unit:时间单位
BlockingQueue workQueue:阻塞队列
Executors.defaultThreadFactory():工厂方法
defaultHandler:处理策略

假设有一个银行的场景,总共有五个窗口(线程),三张椅子(阻塞队列),此时只有两个窗口开着的,当有两个人进来办理业务(任务)时,直接办理,再来一个人,就会坐上椅子进行等待,陆陆续续再来时,椅子满了时,此时剩余的窗口就会开启处理任务,要是窗口和椅子都满了,这时要是再有人来银行,这个人会选择离开或则等待;

从这个场景中,开始的两个窗口代表是核心线程数,三张椅子代表是阻塞队列,五个窗口为最大线程数,而最后一个人的选择代表是处理策略;

存活时间是指当线程空闲的时间且数量大于核心线程数超过这个时间就会进行销毁。

阻塞队列的作用?
在银行的场景中,有两个窗口一直是开着的,代表着线程一直都是占用CPU资源的,而当没有任务,这个两个窗口也占用cpu资源,阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活、不至于一直占用cpu资源。

为什么是先添加队列而不是创建最大线程?
在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率。线程的创建与销毁很影响性能的,当任务量不多时,核心线程数能处理完当前任务,阻塞队列的任务再去处理,这样整体的效率提高,避免了线程的创建与销毁。

四种拒绝策略:
AbortPolicy(抛出异常)

public static void main(String[] args) throws Exception{int corePoolSize = 5;int maximumPoolSize = 10;long keepAliveTime = 5;BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(10);RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue, handler);for(int i=0; i<100; i++) {try {executor.execute(new Thread(() -> log.info(Thread.currentThread().getName() + " is running")));} catch (Exception e) {log.error(e.getMessage(),e);}}executor.shutdown();}

CallerRunsPolicy(从哪里来到哪里去)

RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();

控制台会打印“main is running”

DiscardPolicy(丢弃任务)

RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();

DiscardOldestPolicy(替换旧的)

RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();

Volatile关键字

在认识Volatile关键字之前,先认识JMM(内存模型)。
什么是JMM?
JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。

正是因为JMM的规定,在多线程的情况下,一个线程对内存的变量进行改动,另一个线程是不知道的,就会导致可见性问题

public class VolatileExample {public static void main(String[] args) {MyThread myThread = new MyThread();// 开启线程myThread.start();// 主线程执行for (; ; ) {if (myThread.isFlag()) {System.out.println("主线程访问到 flag 变量");}}}
}
class MyThread extends Thread {private boolean flag = false;@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 修改变量值flag = true;System.out.println("flag = " + flag);}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}
}

在上述程序中,main线程永远也访问不到flag变量。JMM 这样的规定可能会导致线程对共享变量的修改没有即时更新到主内存,或者线程没能够即时将共享变量的最新值同步到工作内存中,从而使得线程在使用共享变量的值时,该值并不是最新的。这样就导致了可见性问题.使用volatile关键字就会避免这个问题。使用 volatile 修饰共享变量后,每个线程要操作变量时会从主内存中将变量拷贝到本地内存作为副本,当线程操作变量副本并写回主内存后,会通过 CPU 总线嗅探机制告知其他线程该变量副本已经失效,需要重新从主内存中读取。

**嗅探机制工作原理:**每个处理器通过监听在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址修改,就会将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从主内存中把数据读到处理器缓存中。

class MyThread extends Thread {private volatile boolean flag = false;@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 修改变量值flag = true;System.out.println("flag = " + flag);}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}
}

Volatile不能保证原子性
原子性就是线程在执行的时候,不能打扰,要么成功,要么失败。

public class VDemo02 {private static volatile int number = 0;public static void add(){number++; //++ 不是一个原子性操作,是两个~3个操作}public static void main(String[] args) {//理论上number  === 20000for (int i = 1; i <= 20; i++) {new Thread(()->{for (int j = 1; j <= 1000 ; j++) {add();}}).start();}while (Thread.activeCount()>2){//main  gcThread.yield();}System.out.println(Thread.currentThread().getName()+",num="+number);}
}

此时输出的结果就是乱的。

使用synchronized和lock是可以解决这个问题的,但不是用锁我们该怎么解决这个问题呢?
使用原子类:

public class VDemo02 {private static volatile AtomicInteger number = new AtomicInteger();public static void add(){number.incrementAndGet();  //底层是CAS保证的原子性}public static void main(String[] args) {for (int i = 1; i <= 20; i++) {new Thread(()->{for (int j = 1; j <= 1000 ; j++) {add();}}).start();}while (Thread.activeCount()>2){//main  gcThread.yield();}System.out.println(Thread.currentThread().getName()+",num="+number);}
}

看incrementAndGet源码:

public final int incrementAndGet() {return U.getAndAddInt(this, VALUE, 1) + 1;}
public final int getAndAddInt(Object o, long offset, int delta) {int v;do {v = getIntVolatile(o, offset);} while (!weakCompareAndSetInt(o, offset, v, v + delta));return v;}

底层是CAS,是直接操作内存的。所以原子类能保证原子性

volatile禁止指令重排
什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的,源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性!

int x=1; //1
int y=2; //2
x=x+5;   //3
y=x*x;   //4
//我们期望的执行顺序是 1_2_3_4  可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的1234567

可能造成的影响结果:前提:a b x y这四个值 默认都是0
线程A 线程B
x=a y=b
b=1 a=2
正常的结果: x = 0; y =0;
线程A 线程B
b=1 a=2
x=a y=b
可能在线程A中会出现,先执行b=1,然后再执行x=a;

在B线程中可能会出现,先执行a=2,然后执行y=b;

那么就有可能结果如下:x=2; y=1.
volatile可以避免指令重排

单例模式

饿汉式

//饿汉式单例
public class Hungery {private Hungery(){}private final static Hungery hungery =new Hungery();public static Hungery getInstance(){return  hungery;}public static void main(String[] args) {System.out.println(Hungery.getInstance());System.out.println(Hungery.getInstance());}
}

懒汉式

public class Lazy {private Lazy(){}private   static  Lazy lazy;public static Lazy getInstance(){if (lazy==null){lazy=new Lazy();}return lazy;}public static void main(String[] args) {for(int i=1;i<=10;i++){new Thread(()->{System.out.println(Lazy.getInstance());}).start();}}
}

在多线程的情况下,这样的单例式不安全的,依然会有多个线程同时执行,创建不同的实例,此时我们需要加把锁

public class Lazy {private Lazy(){}private  static  Lazy lazy;public static Lazy getInstance(){if (lazy==null){synchronized (Lazy.class){if (lazy==null){lazy=new Lazy();}}}return lazy;}public static void main(String[] args) {for(int i=1;i<=10;i++){new Thread(()->{System.out.println(Lazy.getInstance());}).start();}}
}

此时还会有一个问题,就是new Lazy实例对象的时候,它不是原子性操作,首先会分配内存空间,再执行构造方法,初始化对象,然后把这个对象指向这个空间。多线程情况下,这样就会导致指令重排的问题,就是在上诉代码中,一个线程调用getInstance方法,另一个因为指令重排会直接跳过if判断语句,就会出现还没构造对象就返回的问题。解决这个问题就只需要在共享变量中加个volatile关键字,

public class Lazy {private Lazy(){}private  static volatile Lazy lazy;public static Lazy getInstance(){if (lazy==null){synchronized (Lazy.class){if (lazy==null){lazy=new Lazy();}}}return lazy;}public static void main(String[] args) {for(int i=1;i<=10;i++){new Thread(()->{System.out.println(Lazy.getInstance());}).start();}}
}

你以为这样就是最终的单例吗?其实反射也是可以破坏单例模式,通过getDeclaredConstructor()方法得到构造器对象,将其setAccessible()为false,这样类的构造器就拿到了,破坏了单例模式。解决方法为可以在构造器中抛出异常。
查看反射的源码,可以发现,枚举类是无法破坏的,会直接抛出一个异常.

public enum EnumSingle {Final;private EnumSingle(){}public EnumSingle getInstance(){return Final;}public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {EnumSingle instance1 = EnumSingle.Final;Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);declaredConstructor.setAccessible(true);EnumSingle instance2 = declaredConstructor.newInstance();System.out.println(instance1==instance2);}
}

此程序会直接抛出异常,IllegalArgumentException: Cannot reflectively create enum objects。这里需要注意的是在通过反射拿到构造器的是有参构造方法,而不是无参构造函数。

CAS

什么是CAS?
CAS : compareAndSet 比较并交换

public class casDemo {public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(2020);//boolean compareAndSet(int expect, int update)//期望值、更新值//如果实际值 和 我的期望值相同,那么就更新//如果实际值 和 我的期望值不同,那么就不更新System.out.println(atomicInteger.compareAndSet(2020, 2021));//返回trueSystem.out.println(atomicInteger.get());//返回2021//因为期望值是2020  实际值却变成了2021  所以会修改失败//CAS 是CPU的并发原语atomicInteger.getAndIncrement(); //++操作System.out.println(atomicInteger.compareAndSet(2020, 2021));//返回falseSystem.out.println(atomicInteger.get());//返回2020}
}

查看源码发现:

public class AtomicInteger extends Number implements java.io.Serializable {private static final long serialVersionUID = 6214790243416807050L;private static final Unsafe U = Unsafe.getUnsafe();private static final long VALUE= U.objectFieldOffset(AtomicInteger.class, "value");private volatile int value;

里面有一个Unsafe类:这个类是C++写的,里面都是native方法,可以直接操作内存,而Java是无法操作直接内存的,通过这个类间接性的操作内存。

CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。一次性只能保证一个变量的原子性。

CAS存在ABA问题:就是一个线程期望值为1,更新值为2,另一个线程期望值为1,更新值为3,另一个线程执行,更新值为1,此时另一个线程期望值为3,更新值为1,执行速度很快,就把值更新为1,这样就骗过了第一个线程。

解决ABA问题的思想就是:乐观锁,带有版本号的CAS

public class CASDemo {/**AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题* 正常在业务操作,这里面比较的都是一个个对象*/static AtomicStampedReference<Integer> atomicStampedReference = newAtomicStampedReference<>(1, 1);// CAS compareAndSet : 比较并交换!public static void main(String[] args) {new Thread(() -> {int stamp = atomicStampedReference.getStamp(); // 获得版本号System.out.println("a1=>" + stamp);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 修改操作时,版本号更新 + 1atomicStampedReference.compareAndSet(1, 2,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);System.out.println("a2=>" + atomicStampedReference.getStamp());// 重新把值改回去, 版本号更新 + 1System.out.println(atomicStampedReference.compareAndSet(2, 1,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1));System.out.println("a3=>" + atomicStampedReference.getStamp());}, "a").start();// 乐观锁的原理相同!new Thread(() -> {int stamp = atomicStampedReference.getStamp(); // 获得版本号System.out.println("b1=>" + stamp);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(atomicStampedReference.compareAndSet(1, 3,stamp, stamp + 1));System.out.println("b2=>" + atomicStampedReference.getStamp());}, "b").start();}
}

自旋锁

什么是自旋锁?
一个线程在获取到锁的时间里,就会循环自旋
通过CAS自定义自旋锁:

public class SpinlockDemo {// 默认// int 0//thread nullAtomicReference<Thread> atomicReference=new AtomicReference<>();//加锁public void myLock(){Thread thread = Thread.currentThread();System.out.println(thread.getName()+"===> mylock");//自旋锁while (!atomicReference.compareAndSet(null,thread)){System.out.println(Thread.currentThread().getName()+" ==> 自旋中~");}}//解锁public void myUnlock(){Thread thread=Thread.currentThread();System.out.println(thread.getName()+"===> myUnlock");atomicReference.compareAndSet(thread,null);}}
public class TestSpinLock {public static void main(String[] args) throws InterruptedException {ReentrantLock reentrantLock = new ReentrantLock();reentrantLock.lock();reentrantLock.unlock();//使用CAS实现自旋锁SpinlockDemo spinlockDemo=new SpinlockDemo();new Thread(()->{spinlockDemo.myLock();try {TimeUnit.SECONDS.sleep(3);} catch (Exception e) {e.printStackTrace();} finally {spinlockDemo.myUnlock();}},"t1").start();TimeUnit.SECONDS.sleep(1);new Thread(()->{spinlockDemo.myLock();try {TimeUnit.SECONDS.sleep(3);} catch (Exception e) {e.printStackTrace();} finally {spinlockDemo.myUnlock();}},"t2").start();}
}

t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待。

死锁

什么是死锁?
死锁是指A线程持有锁不放,B线程持有锁不放,而A线程却需要B线程的锁,B线程需要A线程的锁,这样就造成了死锁。

public class DeadLock {public static void main(String[] args) {String lockA= "lockA";String lockB= "lockB";new Thread(new MyThread(lockA,lockB),"t1").start();new Thread(new MyThread(lockB,lockA),"t2").start();}
}
class MyThread implements Runnable{private String lockA;private String lockB;public MyThread(String lockA, String lockB) {this.lockA = lockA;this.lockB = lockB;}@Overridepublic void run() {synchronized (lockA){System.out.println(Thread.currentThread().getName()+" lock"+lockA+"===>get"+lockB);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lockB){System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA);}}}
}

如何去找出死锁?

1、使用jps定位进程号,jdk的bin目录下: 有一个jps
命令:jps -l

2、使用jstack 进程进程号 找到死锁信息

over

本文借鉴狂神说Java并发编程视频:https://www.bilibili.com/video/BV1B7411L7tE?p=33加上了自己的理解做的笔记

最通俗易懂的JUC多线程并发编程相关推荐

  1. JUC多线程并发编程

    1.什么是JUC 源码+官方文档 JUC是 java util concurrent 面试高频问JUC~! java.util 是Java的一个工具包~ 业务:普通的线程代码 Thread Runna ...

  2. Java多线程并发编程--Java并发包(JUC)

    Java多线程并发–Java并发包(JUC) 前言 前一篇文章中,笔者已经介绍了Java多线程的一些基础知识,但是想要成为一名中高级Java程序员还必须懂得Java并发包(JUC)的知识点,而且JUC ...

  3. 【JUC高并发编程】—— 初见JUC

    一.JUC 概述 什么是JUC JUC 是 Java并发编程的缩写,指的是 Java.util.concurrent 即Java工具集下的并发编程库 [说白了就是处理线程的工具包] JUC提供了一套并 ...

  4. Java JUC高并发编程(三)-CallableJUC辅助类

    目录 一.Callable接口 二.JUC辅助类 1.减少计数CountDownLatch 2.循环栅栏CyclicBarrier 3.信号灯Semaphore 一.Callable接口 Callab ...

  5. Java JUC高并发编程(一)

    目录 一.概述 二.Lock接口 三.线程间的通信 解决虚假唤醒问题 Lock通信示例: 四.线程间定制化通信 一.概述 JUC就是java.util.concurrent工具包的简称,这是一个处理线 ...

  6. python 多线程并发编程(生产者、消费者模式),边读图像,边处理图像,处理完后保存图像实现提高处理效率

    文章目录 需求 实现 先导入本次需要用到的包 一些辅助函数 如下函数是得到指定后缀的文件 如下的函数一个是读图像,一个是把RGB转成BGR 下面是主要的几个处理函数 在上面几个函数构建对应的处理函数 ...

  7. Java 多线程 并发编程

    转载自  Java 多线程 并发编程 一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进 ...

  8. Java多线程并发编程

    一.线程池 1.1.什么是线程池 线程池是一种多线程的处理方式,利用已有线程对象继续服务新的任务(按照一定的执行策略),而不是频繁地创建销毁线程对象,由此提高服务的吞吐能力,减少CPU的闲置时间.具体 ...

  9. 【JUC高并发编程】—— 再见JUC

    一.读写锁 读写锁概述 1️⃣ 什么是读写锁? 读写锁是一种多线程同步机制,用于在多线程环境中保护共享资源的访问 与互斥锁不同的是,读写锁允许多个线程同时读取共享资源,但在有线程请求写操作时,必须将其 ...

  10. 网易云课堂微专业--Java高级开发工程师--多线程并发编程--学习笔记(二)

    文章目录 第一章 多线程并发编程 第二节 线程安全问题 1.2.1 线程安全之可见性问题 多线程中的问题 从内存结构到内存模型 工作内存缓存 指令重排序 内存模型的含义 Shared Variable ...

最新文章

  1. K3s初探:Rancher架构师带你尝鲜史上最轻量Kubernetes发行版
  2. 浅析新站SEO和老站优化推广有哪些区别?
  3. python怎么读文件夹下的文件夹-python如何获取当前文件夹下所有文件名详解
  4. 六数码问题(广搜_队列)
  5. Hadoop 2.2.0 集群搭建
  6. subprocess.Popen 运行windows命令出现“句柄无效”报错的解决方法
  7. 计算机网络-基本概念(1)【网络层】-ARP协议以及数据传输过程
  8. element-ui中用el-dialog+el-table+el-pagination实现文件默认选中且在分页的条件下有记忆功能...
  9. SonarQube结合IDEA实现代码检测
  10. ios web页面测试方法
  11. 04 | 函数与优化方法:模型的自我学习(上)
  12. 佩奇,是你吗?曝新款AirPods外观酷似吹风机
  13. 获取、导出微信所有表情
  14. 用 m3u8 下载网页视频直接保存为 MP4
  15. 企业OKR终极目标:让员工成功
  16. 修改tomcat 发布war大小限制
  17. 蓝桥杯 ALGO-7 逆序对
  18. CSDN 如何修改用户名(CSDN ID)?
  19. 组合导航(一):定位技术分类与介绍
  20. 正则环视 php,正则基础之 环视 Lookaround

热门文章

  1. 扫普通二维码打开小程序,可进入体验版
  2. android 色彩搭配,设计学堂:关于APP配色的一些常用色彩搭配技巧
  3. STM32+WIFI模块(EMW3080)使用MQTT协议链接阿里云服务器
  4. ps不更改原图比例,调整图片至任意尺寸
  5. 树莓派 能干啥_大神们都用树莓派做了哪些事
  6. 达梦DEM部署、agent配置与监控使用
  7. 将动态IP切换为静态
  8. Tomcat设置开机启动 - CentOS(结尾附视频)
  9. 十、cut ,sort,wc,unip,tee,tr,split 命令
  10. angular对象简单介绍