JUC并发编程(三)

常用辅助类

CountDownLatch

CountDownLatch,是一种减法计数器。
CountDownLatch主要有两个方法:

  • await()会阻塞线程,等待计时器归零。
  • countDown()会令计数器减1。

例如,创建6个线程,需要等待这6个线程执行完再在主线程中输出“main End”。代码如下:

package com.wunian.juc.help;

import java.util.concurrent.CountDownLatch;
/**
 * CountDownLatch 减法计数器
 * 创建六个线程,等待这六个线程跑完再执行主线程的End
 */
public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        //CountDownLatch默认的计数器初始值设置为6
        CountDownLatch countDownLatch =new CountDownLatch(6);

        for(int i=1;i<=6;i++){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" Start");
                countDownLatch.countDown();//计数器-1
            },String.valueOf(i)).start();
        }
        countDownLatch.await();//只要计数器没有归零,这里就会一直阻塞
        //等待上面六个线程跑完再执行主线程的End
        System.out.println(Thread.currentThread().getName()+" End");
    }
}

CyclicBarrier

CyclicBarrier,作用和CountDownLatch相反,是一种加法计数器。主要是通过判断线程数来控制线程的阻塞。
例如,模拟集齐7颗龙珠召唤神龙。代码如下:

package com.wunian.juc.help;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
 * 加法计数器
 * 集齐七颗龙珠召唤神龙
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        //CyclicBarrier 篱栅  7是最多放入的线程数
        CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功!");
        });
        for(int i=1;i<=7;i++){
            final int temp=i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集了第"+temp+"颗龙珠");
                //线程阻塞 1 2 3 4 5 6 7
                try {
                    cyclicBarrier.await();//阻塞
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

Semaphore

Semaphore,信号量,主要用于两种情况:

  • 多线程共享资源互斥。
  • 并发线程的控制。

主要方法

  • acquire()
    1.当一个线程调用acquire()操作时,就是获取到了一个信号量减1。
    2.如果当前信号量为0,就会一直等待。
  • release()
    信号量加1,唤醒等待的线程。

例如,模拟停车场有3个位置,现在有6辆车要进来的场景,代码如下:

package com.wunian.juc.help;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
 * Semaphore信号量
 * 主要用在两种地方:多线程共享资源互斥!并发线程的控制!
 * 模拟场景:停车场有3个位置,现在有6辆车要进来
 */
public class SemaphoreDemo {
    public static void main(String[] args) {
        //模拟3个车位
        Semaphore semaphore=new Semaphore(3);
        //模拟6辆车
        for(int i=1;i<=6;i++){
            new Thread(()->{
                try {
                    semaphore.acquire();//得到车位,如果信号量为0,就会一直等待
                    System.out.println(Thread.currentThread().getName()+"抢到了车位");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"离开了车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();//释放车位,唤醒等待的车
                }
            },String.valueOf(i)).start();
        }
    }
}

JMM

JMM,即Java内存模型(Java Memory Mode),是java虚拟机规范定义的,用来屏蔽掉java程序在各种不同的硬件和操作系统对内存的访问的差异,这样就可以实现java程序在各种不同的平台上都能达到内存访问的一致性。
线程的八大操作
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可再分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态。
  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中。
  • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令。
  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中。
  • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用。
  • write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。


    线程工作原理

JMM对这八种指令的使用,制定了如下规则:

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write。
  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存 (可见)。
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存。
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作。
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁。
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值。
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量。
  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存。

volatile

volatile 是一个类型修饰符。volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略。
volatile的特性

  • 保证可见性。
  • 不保证原子性。
  • 禁止指令重排。

volatile保证可见性的论证,代码如下:

package com.wunian.juc.jmm;

import java.util.concurrent.TimeUnit;
/**
 * volatile可见性论证
 */
public class VolatileDemo {

    //private  static int num=0;
    private volatile static int num=0;

    public static void main(String[] args) throws InterruptedException {//有3个线程 main、gc、我们定义的线程
        new Thread(()->{
            while(num==0){
                //没加volatile时,这个对象不可见,执行过程中会一直卡住
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        num=1;//没加volatile时,虽然main线程修改了这个值,但上面的线程并不知道
        System.out.println(num);
    }
}

volatile不保证原子性(不可分割)的论证,代码如下:

package com.wunian.juc.jmm;

/**
 *volatile不保证原子性(不可分割)的论证
 */
public class VolatileDemo2 {

    private volatile static int num = 0;

    // synchronized
    public static void add(){
        num++;
    }

    public static void main(String[] args) {
        // 期望 num 最终是 2 万
        for (int i = 1; i <=20 ; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {
                    add();
                }
            },String.valueOf(i)).start();
        }
        // 判断活着的线程
        while (Thread.activeCount()>2){ // mian  gc
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + " " + num);
    }
}

从运行结果可以知道,最终输出的结果并不总是20000,进入class文件目录,打开DOS窗口,输入命令javap -c VolatileDemo2.class可以查看该类的Java字节码。

java字节码

显然,num++并不是一个原子性操作。
如果不使用synchronized和lock,应该如何解决int类型变量自增的原子性问题呢?
这个时候就要使用AtomicInteger了。
AtomicInteger
AtomicInteger类提供了int类型变量运算的原子性操作,代码如下:

package com.wunian.juc.jmm;

import java.util.concurrent.atomic.AtomicInteger;
/**
 * volatile不保证原子性验证
 * AtomicInteger是原子性的
 * 保证变量原子性的方法:
 * 1.在方法中加synchronized关键字
 * 2.使用AtomicInteger
 */
public class AtomicIntegerDemo {

    //private volatile static int num=0;//int 不是原子性的
    /*public synchronized static void add(){
        num++;//num++不是原子性的操作
    }*/
    private volatile static AtomicInteger num=new AtomicInteger();

    public static void add(){
        num.getAndIncrement();//等价于num++,是原子性操作
        //Java不能直接操作内存! native c++=>操作内存
        //Unsafe类:后门,可以用它来直接操作内存!
    }

    public static void main(String[] args){
        //期望num的最终结果是20000,当使用volatile时,num的实际结果并不准确
        for(int i=1;i<=20;i++){
            new Thread(()->{
                for(int j=1;j<=1000;j++){
                    add();
                }
            },String.valueOf(i)).start();
        }
        //判断活着的线程
        while(Thread.activeCount()>2){//除了main、 gc还有其他线程运行时,先运行其他线程
            Thread.yield();//线程让步
        }
        System.out.println(Thread.currentThread().getName()+" "+num);
    }
}

禁止指令重排
指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序。
Java代码的执行过程:
源代码->编译器(优化重排)->指令并行重排-> 内存系统的重排-> 最终执行的结果
虽然单线程一定是安全的,但是也无法避免指令重排。
处理器在进行重排的时候会考虑指令之间的依赖性。
尝试理解多线程下的指令重排问题,如下所示:

int x,y,a,b = 0;

线程1                       线程2
x = a;                     y = b;
b = 1;                     a = 2;
理想的结果: x=0  y = 0

指令重排:
线程1                       线程2
b = 1;                     a = 2;
x = a;                     y = b;

重排后的结果: x=2  y = 1          

指令重排小结

  • volatile可以禁止指令重排。
  • 内存屏障(Memory Barrier)是CPU指令,它有两个作用:
    1.保证特定的执行顺序。
    2.保证某些变量的内存可见性(volatile就是用这个特性来实现的)。


    内存屏障原理

单例模式

单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例。
单例模式一般分为两种:饿汉式懒汉式
饿汉式,代码如下:

package com.wunian.juc.single;
/**
 * 单例模式:饿汉式
 * 单例思想:构造器私有
 */
public class Hungry {

    //浪费空间 不是我们需要的
    private byte[] data=new byte[10*1024*1024];

    private Hungry(){}

    private final static Hungry HUNGRY=new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }
}

懒汉式,基础版,代码如下:

package com.wunian.juc.single;

public class Lazy {

    private Lazy(){
        System.out.println(Thread.currentThread().getName() + " start");
    }

    private static Lazy lazy;

    public static Lazy getInstance() {
        if (lazy == null){
            lazy = new Lazy();
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        // 多线程下单例失效 
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                Lazy.getInstance();
            }).start();
        }
    }
}

DCL(双重校验锁)懒汉式,代码如下:

package com.wunian.juc.single;

public class Lazy {

    private Lazy(){
        System.out.println(Thread.currentThread().getName() + " start");
    }

    private volatile static Lazy lazy;

    public static Lazy getInstance() {
       if (lazy == null){
            synchronized (Lazy.class){
                if (lazy == null){
                    lazy = new Lazy(); // 请你谈谈这个操作!它不是原子性的
                    // java创建一个对象
                    // 1、分配内存空间
                    // 2、执行构造方法,创建对象
                    // 3、将对象指向空间

                    // A  先执行13,这个时候对象还没有完成初始化!
                    // B  发现对象为空,B线程拿到的对象就不是完成的
                }
            }
        }
        return lazy;
    }

    public static void main(String[] args) {
        // 多线程下单例失效 
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                Lazy.getInstance();
            }).start();
        }
    }
}

单例之所以安全,是因为构造器私有的。但是构造器私有也不安全,使用反射就可以绕过构造器直接创建对象,代码如下:

package com.wunian.juc.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/**
 * 单例模式:DCL(Double Check Lock 双重校验锁)懒汉式
 */
public class Lazy {

    private static boolean protectedCode=false;//标记参数,防止被反编译破坏

    private Lazy(){
        synchronized (Lazy.class){
            if(protectedCode==false){
                protectedCode=true;
            }else{
                //病毒代码、文件无限扩容
                throw  new RuntimeException("不要试图破坏我的单例模式");
            }
        }
    }

    private volatile static Lazy lazy;

    public static Lazy getInstance(){
        //双重校验锁
        if(lazy==null){
            synchronized (Lazy.class){
                if(lazy==null) {
                    lazy=new Lazy();//创建对象不是原子性的,还是存在不安全
                    //java创建一个对象
                    //1.分配内存空间
                    //2.执行构造方法,创建对象
                    //3.将对象指向空间
                    //如果A先执行1 3,这个时候对象还没完成初始化!B发现对象为空,B线程拿到的对象就不是完成的
                }
            }
        }
        return lazy;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {

        //反射安全吗?,官方推荐我们单例真的是DCL懒汉式吗?
        //Lazy lazy1=Lazy.getInstance();
        //得到无参构造器
//        Constructor declaredConstructors = Lazy.class.getDeclaredConstructor(null);
//        Lazy lazy2=declaredConstructors.newInstance();//创建对象
//        Lazy lazy3=declaredConstructors.newInstance();//创建对象
//        //hashcode不一样,所以还是不安全  反射根本不需要通过构造器
//        //System.out.println(lazy1.hashCode());
//        System.out.println(lazy2.hashCode());
//        System.out.println(lazy3.hashCode());

        //如何破坏在反编译过程中的保护参数
        Constructor declaredConstructors = Lazy.class.getDeclaredConstructor(null);
        declaredConstructors.setAccessible(true);
        Lazy lazy4=declaredConstructors.newInstance();//创建对象
        //获取参数对象,必须是在知道参数名称的情况下
        Field protectedCode=Lazy.class.getDeclaredField("protectedCode");
        //重新将参数值设置为false
        protectedCode.setAccessible(true);
        protectedCode.set(lazy4,false);
        Lazy lazy5=declaredConstructors.newInstance();//创建对象
        System.out.println(lazy4.hashCode());
        System.out.println(lazy5.hashCode());
    }
}

这个时候就要使用枚举类了。枚举是一个类,实现了枚举的接口,反射无法破坏枚举。代码如下:

package com.wunian.juc.single;

import com.sun.org.apache.bcel.internal.generic.INSTANCEOF;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 单例模式:使用枚举类
 * 枚举是一个类,实现了枚举的接口
 * 反射 无法破坏枚举
 */
public enum SingleEnum {

    INSTANCE;

    public SingleEnum getInstance(){
        return INSTANCE;
    }
}
//至少在做一个普通的jvm的时候,jdk源码没有被修改的时候,枚举就是安全的
//可以通过修改 jdk/jre/lib/rt.jar中java.lang.reflect.Constructor.class来破坏枚举(见Constructor类)
class Demo{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //通过jad.exe反编译可知,SingleEnum类只有一个有参构造器
        //Constructor declaredConstructor = SingleEnum.class.getDeclaredConstructor(null);
        Constructor declaredConstructor=SingleEnum.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        // throw new IllegalArgumentException("Cannot reflectively create enum objects");
        SingleEnum singleEnum1=declaredConstructor.newInstance();
        SingleEnum singleEnum2=declaredConstructor.newInstance();
        System.out.println(singleEnum1.hashCode());
        System.out.println(singleEnum2.hashCode());
        //这里面没有无参构造!JVM才是王道
        //java.lang.NoSuchMethodException: com.wunian.juc.single.SingleEnum.()
    }
}

运行代码会发现,报了一个异常:Cannot reflectively create enum objects,因此无法通过反射破坏枚举。但是如果修改jdk源码,枚举也可能变得不安全,但至少一般情况下枚举还是安全的。

CAS

CAS,CompareAndSet,比较并交换。CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
示例代码如下:

package com.wunian.juc.jmm;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * CAS:比较并交换
 */
public class CompareAndSetDemo {

    public static void main(String[] args) {
        //AtomicInteger默认为0
        AtomicInteger atomicInteger=new AtomicInteger(5);
        //compareAndSet CAS 比较并交换
        //如果这个值是期望的值,则更新为指定的值,交换成功返回true,否则返回false
        System.out.println(atomicInteger.compareAndSet(5,20));
        System.out.println(atomicInteger.get());//输出当前的值
        System.out.println(atomicInteger.compareAndSet(20, 5));
    }
}

分析AtomicInteger类的getAndIncrement方法
getAndIncrement方法实现了int++的原子性操作,它底层是如何实现的呢?来看看它的源码:

// unsafe可以直接操作内存
public final int getAndIncrement() {
    // this 调用的对象
    // valueOffset 当前这个对象的值的内存地址偏移值
    // 1
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5; // ? 
    do { // 自旋锁(就是一直判断!)
        // var5 = 获得当前对象的内存地址中的值!
        var5 = this.getIntVolatile(this, valueOffset); // 1000万
        // compareAndSwapInt 比较并交换
        // 比较当前的值 var1 对象的var2地址中的值是不是 var5,如果是则更新为 var5 + 1
        // 如果是期望的值,就交换,否则就不交换!
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

可以看出,getAndIncrement的底层是通过自旋锁和CAS算法来实现的。
CAS的缺点

  • 循环开销很大。
  • 内存操作,每次只能保证一个共享变量的原子性。
  • 可能出现ABA问题。

原子引用

什么是ABA问题?
简单来说就是狸猫换太子,例如有两个线程T1和T2,T1线程希望通过CAS算法将一个变量的值由100更新为1,结果在更新过程中睡眠了3秒,在这三秒中T2线程也通过CAS算法先将该变量值更新由100更新为1,然后又将该变量值由1再次更新为100,整个过程看起来似乎该变量的值没有改变,但是对于T1线程来说,数据已经改动了。
如何解决ABA问题?
可以使用原子类,通过增加一个版本号来解决,原理和乐观锁一样。例如,小明和小花同时更新一个数据,小明先睡了三秒,结果数据先被小花更新了,这时版本号发生变化,小明再去更新就会失败,代码如下:

package com.wunian.juc.jmm;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * ABA问题 1-100-1
 * 通过version字段加1来实现数据的原子性
 */
public class ABADemo {

    //version =1
    static AtomicStampedReference atomicStampedReference=new AtomicStampedReference<>(100,1);

    public static void main(String[] args) {

        //其他人员 小花 需要每次执行完毕+1
        new Thread(()->{
            int stamp=atomicStampedReference.getStamp();//获得版本号

            System.out.println("T1 stamp01=>"+stamp);

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100,101,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp()+1);

            System.out.println("T1 stamp02=>"+atomicStampedReference.getStamp());

            atomicStampedReference.compareAndSet(101,100,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp()+1);
            System.out.println("T1 stamp03=>"+atomicStampedReference.getStamp());

        },"T1").start();

        //乐观的小明,sleep过程中数据被小花改过,版本号发生变化,无法完成更新
        new Thread(()->{
            int stamp=atomicStampedReference.getStamp();//获得版本号
            System.out.println("T2 stamp01=>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result=atomicStampedReference.compareAndSet(100,1,stamp,stamp+1);
            System.out.println("T2是否修改成功:"+result);
            System.out.println("T2 stamp02=>"+atomicStampedReference.getStamp());
            System.out.println("T2 当前获取得最新的值=>"+atomicStampedReference.getReference());
        },"T2").start();
    }
}

自旋锁

自旋锁是为实现保护共享资源而提出一种锁机制。是为了解决对某项资源的互斥使用。在任何时刻,最多只能有一个执行单元获得锁。如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁。
Unsafe类源码中的getAndAddInt方法中就使用了自旋锁,源码如下:

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5; 
    do { // 自旋锁(就是一直判断!)
        // var5 = 获得当前对象的内存地址中的值!
        var5 = this.getIntVolatile(this, valueOffset); // 1000万
        // compareAndSwapInt 比较并交换
        // 比较当前的值 var1 对象的var2地址中的值是不是 var5,如果是则更新为 var5 + 1
        // 如果是期望的值,就交换,否则就不交换!
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}

我们可以使用自旋锁的方式定义一把锁,代码如下:

package com.wunian.juc.jmm;

import java.util.concurrent.atomic.AtomicReference;
/**
 * 自己定义一把自旋锁
 */
public class CodingLock {

    //AtomicInteger默认是0
    //AtomicReference默认是null
    //锁线程
    AtomicReference atomicReference=new AtomicReference<>();

    //加锁
    public void lock(){
        Thread thread=Thread.currentThread();
        System.out.println(thread.getName()+"==>lock");
        //上锁 自旋(不停的循环)
        while(!atomicReference.compareAndSet(null,thread)){

        }
    }

    //解锁
    public void unlock(){
        Thread thread=Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(thread.getName()+"==>unlock");
    }
}

然后编写测试类测试一下,代码如下:

package com.wunian.juc.jmm;

import java.util.concurrent.TimeUnit;
/**
 * 测试自旋锁
 */
public class CodingLockTest {

    public static void main(String[] args) {
        CodingLock lock=new CodingLock();

        //1 一定要先拿到锁,1解锁后2才可以拿到锁
        new Thread(()->{
            lock.lock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.unlock();
        },"T1").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2
        new Thread(()->{
            lock.lock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.unlock();
        },"T2").start();
    }
}

测试结果为T1线程先拿到锁,然后等T1释放锁之后T2线程才能拿到锁。这说明我们自己定义的锁实现了锁的功能。

死锁排查

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
产生死锁的四个条件
互斥条件、请求与保持条件、不可剥夺条件、循环等待条件。

死锁原理

模拟死锁,代码如下:

package com.wunian.juc.jmm;

import java.util.concurrent.TimeUnit;

/**
 * 死锁
 * 死锁条件:互斥条件、请求与保持条件、不可剥夺条件、循环等待条件
 * 死锁处理:查看堆栈信息:JVM知识
 * //1.获取当前运行的java进程号 jps -l
 * //2.查看进程信息 jstack 进程号
 * //3.jconsole查看对应的信息(可视化工具)
 */
public class DeadLockDemo {

    public static void main(String[] args) {

        String lockA="lockA";
        String lockB="lockB";

        new Thread(new MyLockThread(lockA,lockB),"T1").start();
        new Thread(new MyLockThread(lockB,lockA),"T2").start();
    }
}

class MyLockThread implements Runnable{

    private String lockA;
    private String lockB;

    public MyLockThread(String lockA,String lockB) {
        this.lockA=lockA;
        this.lockB=lockB;
    }

    @Override
    public 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.获取当前运行的java进程号,命令:jps -l
2.查看进程信息,命令:jstack 进程号
3.使用jconsole查看对应的信息(可视化工具)。
4.修改代码,破坏死锁产生的其中一个条件即可。

几个jdk命令

查看字节码:javap -c xxx.class
查看class源码:javap -p xxx.class
将class文件反编译成java文件(需先将jad.exe拷贝至jdk的bin目录下):jad -sjava xxx.class

你可能感兴趣的:(JUC并发编程(三))