十五、异步回调

1.什么是异步回调?

JNI 异步线程 c回调静态java java多线程异步回调_单例模式

同步回调和异步回调, 主要体现在其是否需要等待.
同步调用,:如果C处理一个问题需要花很长时间, 我们需要等待这个问题处理完,再继续执行其他任务。
异步调用:如果C处理这个需要等待的问题时不需要等待得到结果, 而是扔给S去处理,C然后接着,去做其他事情。

2.CompletableFuture-异步回调

JNI 异步线程 c回调静态java java多线程异步回调_单例模式_02


CompletableFuture在Java里面被用于异步编程,异步通常意味着非阻塞,可以使得我们的任务单独运行在与主线程分离的其他线程中,并且通过回调可以在主线程中得到异步任务的执行状态,是否完成,和是否异常等信息。CompletableFuture实现了Future, CompletionStage接口.

没有返回值的异步回调

public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //runAsync();异步执行
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
            //异步任务发起时不占用程序的时间
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":runAsync-->Void");
        });
        //因为有异步调用,所以主线程会继续执行以下代码,等阻塞时间过后,才会得到以上结果
        System.out.println("主线程正在执行!!!!");
        completableFuture.get();//获取执行结果
    }
}

有返回值的异步回调

/**
 * 有返回值的异步回调
 */
public class Demo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //runAsync();异步执行
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName() + ":runAsync-->Integer");
            return 2021;
        });

        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t-->" + t);//t为正常的返回结果,u为null
            System.out.println("u-->" + u);//错误的返回结果,t为null,u为错误信息
        }).exceptionally((e) -> {
            //失败返回值
            System.out.println(e.getMessage());//可以获取到错误的返回结果
            return 99999;
        }).get());
    }
}

十六、JMM

1.什么是JMM?

JMM:Java内存模型,不存在的东西,概念!约定!

JMM主要是为了规定了线程和内存之间的一些关系。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有实例变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量。

2.关于JMM的一些同步的约定

1、线程解锁前,必须把共享变量立刻刷回主存。(先将主存数据复制到该线程的私有工作内存,再修改后给到主存)
2、线程加锁前,必须读取主存中的最新值到工作内存中!
3、加锁和解锁是同一把锁

3.内存交互8个操作

JNI 异步线程 c回调静态java java多线程异步回调_volatile_03

lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定

read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中

use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中

store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
write  (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

4.JMM对这八种指令的使用,制定的规则

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

5.程序不知道主内存的值已经发生了变化

JNI 异步线程 c回调静态java java多线程异步回调_JNI 异步线程 c回调静态java_04


不加Volatile,次线程不知道主线程已经修改了num的值,所以程序会死循环

public class JMMTest {
//不加Volatile程序会死循环
    private static int num = 0;
    public static void main(String[] args){
        new Thread(()->{
            while (num==0){

            }
        },"次线程").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num = 1;
        System.out.println("main线程"+num);

    }
}

十七、Volatile

1.请你谈谈你对Volatile的理解

Volatile是Java虛拟机提供轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排(有序)

2.保证可见性

加Volatile可以保证可见性,即次线程知道主线程修改了num的值

//加Volatile可以保证可见性,即次线程知道主线程修改了num的值
    private volatile static int num = 0;

3.不保证原子性

原子性:不可分割.
线程A在执行任务的时候,不能被打扰的。也不能被分割。要么同时成功。要么同时失败(回滚)。
测试:

public class VolatileTest2 {
    private volatile  static int num =0;
    public  static void add(){
        num++;
    }
    public static void main(String[] args){
        //20个线程
        for (int i = 1; i <=20 ; i++) {
            new Thread(()->{
                //每个线程执行1000次
                for (int j = 0; j <1000 ; j++) {
                    add();
                }
            },"线程"+i).start();
        }
        while (Thread.activeCount()>2){
            //还有除过主线程和gc线程的其他线程
            //yield()方法的作用是放弃当前线程获取CPU的执行权,将让其它的线程去获取
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+" "+num);
    }

}

JNI 异步线程 c回调静态java java多线程异步回调_JNI 异步线程 c回调静态java_05


由结果可以看出volatile,不能保证原子性,但是方法加了synchronized或者lock就可以保证原子性

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

JNI 异步线程 c回调静态java java多线程异步回调_volatile_06


但是不加lock和synchronized,如何保证原子性

使用原子类的包装类

JNI 异步线程 c回调静态java java多线程异步回调_System_07

public class AtomicTest3 {
    private volatile  static AtomicInteger num =new AtomicInteger();

    public  static void add(){
        //num++;
        num.getAndIncrement();//AtomicInteger+1 CAS
    }


    public static void main(String[] args){
        //20个线程
        for (int i = 1; i <=20 ; i++) {
            new Thread(()->{
                //每个线程执行1000次
                for (int j = 0; j <1000 ; j++) {
                    add();
                }
            },"线程"+i).start();
        }
        while (Thread.activeCount()>2){
            //还有除过主线程和gc线程的其他线程
            //yield()方法的作用是放弃当前线程获取CPU的执行权,将让其它的线程去获取
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+" "+num);
    }
}

4.禁止指令重排

什么是指令重排:你写的程序,计算机并不是按照你写的那样去执行的。

源代码–>编译器优化的重排–>指令并行也可能会重排–>内存系统也会重排–>执行

处理器在进行指令重排的时候.考虑:数据之间的依赖性!

intx=1;//1
inty=2;//2
x=x+5;//3
y=x*x;//4

我们所期望的执行顺序: 1234 但是 可能执行的时候回变成2134 1324
上述的过程就可以当做是指令的重排,即内部执行顺序,和我们的代码顺序不一样

但是指令重排也是有限制的,即不会出现下面的顺序

4 3 2 1
因为处理器在进行重排时候,必须考虑到指令之间的数据依赖性

因为步骤 4:需要依赖于 y的申明,以及x的申明,故因为存在数据依赖,无法首先执行

例子:int a,b,x,y=0

JNI 异步线程 c回调静态java java多线程异步回调_JNI 异步线程 c回调静态java_08


上面的代码,不存在数据的依赖性,因此编译器可能对数据进行重排

JNI 异步线程 c回调静态java java多线程异步回调_JNI 异步线程 c回调静态java_09


重排后和预期结果不同.

volatile可以避免指令重排
内存屏障。CPU指令。
作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性( 利用这些特性volatile实现了可见性)

JNI 异步线程 c回调静态java java多线程异步回调_System_10

Volatile是可以保持可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!

十八、单例模式

1.单例设计模式特点:

①一个类只允许产生一个实例化对象。
②类的构造器的访问权限设置为private
③类内部创建该类的对象,并且对象也必须声明为static
④提供公共的static’方法,返回类的对象

单例模式又分为:饿汉式和懒汉式

2.饿汉式单例

饿汉式:在声明的同时初始化该对象,在类创建的同时就已经创建好一个静态的对象,以后不在改变。线程是安全的

public class SingletonDemo1 {
    public static void main(String[] args){
        Book book1 = Book.getInstance();
        Book book2 = Book.getInstance();
        System.out.println("创建的是否为同一个book对象:"+(book1==book2));//true
    }
}

/**
 * 饿汉式单例:
 */
class Book{
    //1.构造器私有
    private Book() {

    }

    //2.内部创建对象
    //3.此对象必须为静态,否则获得对象实例的getInstance方法无法获取
    //饿汉式,声明的同时初始化该对象
    private static Book book = new Book();

    //4.提供公共的static方法,返回类的对象
    public static Book getInstance(){
        return book;
    }
}

3.懒汉式单例

懒汉式:延时加载,它是在需要的时候才创建对象,在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的。

public class SingletonDemo2 {
    public static void main(String[] args){
        Dog dog1 = Dog.getInstance();
        Dog dog2 = Dog.getInstance();
        System.out.println("创建的是否为同一个book对象:"+(dog1==dog2));//true

    }
}

/**
 * 饿汉式单例:
 */
class Dog{
    //1.构造器私有
    private Dog() {

    }

    //2.内部创建对象
    //3.对象声明为static
    //懒汉式:对象声明但是没有初始化
    private static Dog dog = null;

    //4.提供公共的static方法,返回类的对象,延迟加载
    public static Dog getInstance(){
        if (dog == null){
            dog = new Dog();
        }
        return dog;
    }

饿汉式和懒汉式比较

饿汉式:
坏处:对象加载时间过长。
好处:饿汉式是线程安全的

懶汉式:
好处:延迟对象的创建。
写法坏处:线程不安全。

4.DCL单例(双重检测模式)

不加锁,线程不安全

public  class Animals{

    private Animals() {
        System.out.println(Thread.currentThread().getName()+"已启动");
    }

    private static Animals animal = null;

    public static Animals getInstance(){
        if (animal == null){
            animal = new Animals();
        }
        return animal;
    }

    //多线程并发
    public static void main(String[] args){
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                Animals.getInstance();
            }).start();
        }
    }
}

JNI 异步线程 c回调静态java java多线程异步回调_volatile_11


双重检测锁加原子性判断,从而避免指令重排

/**
 * 饿汉式单例:加锁synchronized
 */
public  class Animals{

    private Animals() {
        System.out.println(Thread.currentThread().getName()+"已启动");
    }
	
    private volatile static Animals animal = null;

    public static Animals getInstance(){
        if (animal == null){
            synchronized (Animals.class){
                if (animal == null){
                    animal = new Animals();
                }
            }
        }
        return animal;
    }

    //多线程并发
    public static void main(String[] args){
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                Animals.getInstance();
            }).start();
        }
    }
}

JNI 异步线程 c回调静态java java多线程异步回调_JNI 异步线程 c回调静态java_12


为什么需要双重检测的原因?

第一个if语句,用来确认调用getInstance()时animal是否为空,如果不为空即已经创建,则直接返回,如果为空,那么就需要创建实例,于是进入synchronized同步块。

synchronized加类锁,确保同时只有一个线程能进入,进入以后进行第二次判断,是因为,对于首个拿锁者,它的时段animal肯定为null,那么进入new Animals()对象创建,而在首个拿锁者的创建对象期间,可能有其他线程同步调用getInstance(),那么它们也会通过if进入到同步块试图拿锁,因为有第一个线程已经拿到锁,所以该线程会等待第一个线程释放锁,从而阻塞。

这样的话,当首个拿锁者完成了对象创建,之后的线程都不会通过第一个if了,而这期间阻塞的线程开始唤醒,它们则需要靠第二个if语句来避免再次创建对象。

为什么对象要加volatile修饰
因为animal=new Animals();不是一个原子性操作
步骤

  • 1、分配内存空间
  • 2、执行构造方法,初始化对象
  • 3、把这个对象指向这个空间

正常期望顺序是123,而如果指令重排按照132 执行时,A线程还没有完成构造,但B线程会发现已经有对象指向一片内存空间,即发现有对象,所以会返回。所以为了避免指针重排对象的声明还需要加volatile

5. 静态内部类单例

public class Holder {
    private Holder(){

    }
    //内部类
    private static class InnerClass(){
        private static final Holder HOLDER = new Holder();
    }

    private static Holder getInstance(){
        return InnerClass.HOLDER;
    }
}

第一次加载Holder类时,并不会实例化HOLDER ,只有第一次调用getInstance方法时,Java虚拟机才会去加载Holder类,继而延时实例化HOLDER

十九、CAS

1.什么是CAS

CAS–>compareAndSet:比较并交换

JNI 异步线程 c回调静态java java多线程异步回调_volatile_13


CAS :比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!

缺点:

1、 循环会耗时

2、一次性只能保证一个共享变量的原子性

3、ABA问题

public class CASDemo {
    public static void main(String[] args){
        //AtomicInteger原子性Integer 设置初始值
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //public final boolean compareAndSet(int expect, int update)   期望,更新
        //是否达到期望,达到更新,要么不更新,CAS是CPU的并发原语
        System.out.println(atomicInteger.compareAndSet(2020, 2021));//2021
        System.out.println(atomicInteger.get());

        //此时期望修改为2021,我们的初始值是2020,没有达到期望,所以不更新期望还是2021
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

JNI 异步线程 c回调静态java java多线程异步回调_System_14

2.unsafe类

JNI 异步线程 c回调静态java java多线程异步回调_JNI 异步线程 c回调静态java_15


JNI 异步线程 c回调静态java java多线程异步回调_双重加锁_16


JNI 异步线程 c回调静态java java多线程异步回调_volatile_17

3.ABA问题

JNI 异步线程 c回调静态java java多线程异步回调_双重加锁_18

public class CASDemo {
    public static void main(String[] args){
        //AtomicInteger原子性Integer 设置初始值
        AtomicInteger atomicInteger = new AtomicInteger(1);
        //public final boolean compareAndSet(int expect, int update)   期望,更新

        //   ABA  线程1不知道线程2修改过资源
        //========================线程2================================
        System.out.println(atomicInteger.compareAndSet(1, 3));
        System.out.println(atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(3, 1));
        System.out.println(atomicInteger.get());

        //========================线程1================================
        System.out.println(atomicInteger.compareAndSet(1, 2));
        System.out.println(atomicInteger.get());
    }
}

JNI 异步线程 c回调静态java java多线程异步回调_System_19

二十、原子引用

解决ABA问题,引入原子引用—>增加版本号

带版本号的原子操作!

JNI 异步线程 c回调静态java java多线程异步回调_volatile_20


AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题

public class CASDemo2 {
    public static void main(String[] args){
        //AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
        //正常在业务操作,这里面比较的都是一个个对象
        AtomicStampedReference<Integer> ar = new AtomicStampedReference<>(1,1);

        new Thread(()->{
            int stamp = ar.getStamp();//获得当前版本号  1
            System.out.println("A1---->"+stamp);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //1改为3
            System.out.println("A是否修改数据:"+ar.compareAndSet(1, 3,
                    ar.getStamp(), ar.getStamp() + 1));
            System.out.println("A2---->"+ar.getStamp());//修改后的版本号  2

            //3修改回去1
            System.out.println("A是否修改数据:"+ar.compareAndSet(3, 1,
                    ar.getStamp(), ar.getStamp() + 1));
            System.out.println("A3---->"+ar.getStamp());//修改后的版本号  1


        },"A").start();

        //乐观锁的原理相同
        new Thread(()->{
            int stamp = ar.getStamp();//获得版本号
            System.out.println("B1---->"+stamp);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("B是否修改数据:"+ar.compareAndSet(1, 6,
                    ar.getStamp(), ar.getStamp() + 1));
            System.out.println("B2---->"+ar.getStamp());
        },"B").start();

    }
}

A1修改了初始值,又重新改回去,但B不知道(ABA)

JNI 异步线程 c回调静态java java多线程异步回调_volatile_21

注意:Integer使用了对象缓存机制,默认范围是-128 ~ 127 ,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new 一定会创建新的对象分配新的内存空间

二十一、各种锁

1.公平锁、非公平锁

JNI 异步线程 c回调静态java java多线程异步回调_System_22


公平锁:非常公平,不能够插队,必须先来后到!

Lock lock = new ReentrantLock(true);

非公平锁:非常不公平,可以插队( 默认都是非公平)

Lock lock = new ReentrantLock();

2.可重入锁

也叫递归锁,拿到了外面的锁之后,就可以拿到里面的锁,自动获得

JNI 异步线程 c回调静态java java多线程异步回调_JNI 异步线程 c回调静态java_23


synchronized 可重入锁:获得外层的锁,自动获取到里面的锁

/**
*synchronized 可重入锁
**/
public class Demo {
    public static void main(String[] args){
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMs();
        },"线程A").start();

        new Thread(()->{
            phone.sendMs();
        },"线程B").start();
    }

}
class Phone{
    public synchronized static void sendMs() {
        System.out.println(Thread.currentThread().getName()+"发消息");
        call();
    }

    public synchronized static void call() {
        System.out.println(Thread.currentThread().getName()+"打电话");
    }
}

JNI 异步线程 c回调静态java java多线程异步回调_volatile_24


lock锁:需要拿两把锁

public class Demo2 {
    public static void main(String[] args){
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sendMs();
        },"线程A").start();

        new Thread(()->{
            phone.sendMs();
        },"线程B").start();
    }

}
class Phone2{
    Lock lock = new ReentrantLock();
    public  void sendMs() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"发消息");
            call();
        } finally {
            lock.unlock();
        }
    }

    public  void call() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"打电话");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

3.自旋锁(spinLock)

JNI 异步线程 c回调静态java java多线程异步回调_volatile_25


3.1什么是自旋锁

自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

3.2自定义实现自旋锁
利用原子引用类(AtomicReference)自定义实现的自旋锁 new 一个原子引用类,对象类型为Thread

public class SpinLockDemo3 {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    //加锁
    public void Lock(){
        //获取当前线程
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+"准备上锁");

        //利用CAS自旋锁
        //compareAndSet(null,current),如果需要读写的值不是null(null即无锁状态)
        // 那么返回false;thread(当前线程)获取锁。
        while(!atomicReference.compareAndSet(null,thread)){
            //Do nothing
        }
    }

    //解锁
    public void unLock() {
        //获取当前线程
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "已解锁");

        //自旋锁
        atomicReference.compareAndSet(thread, null);
    }
}

测试

public class SpinLockTest {
    public static void main(String[] args) throws InterruptedException {

        //创建自旋锁(CAS)
        SpinLockDemo3 lock = new SpinLockDemo3();

        new Thread(() -> {
            lock.Lock();
            try {
                //阻塞5s
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unLock();
            }

        }, "线程1").start();

        //阻塞是为了先让线程1先拿到锁
        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            lock.Lock();
            try {
                //阻塞2s
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unLock();
            }
        }, "线程2").start();
    }
}

lock()方法利用的CAS,当第一个线程1获取锁的时候,能够成功获取到,不会进入while循环,如果此时线程1没有释放锁,另一个线程2又来获取锁,此时由于不满足CAS,所以就会进入while循环,不断判断是否满足CAS,直到1线程调用unlock方法释放了该锁。

JNI 异步线程 c回调静态java java多线程异步回调_System_26

4.死锁

4.1 线程死锁是指:多个线程同时被阻塞,他们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此此程序不可能正常终止。

如下图所示,有两个线程A,B,两个资源A锁,B锁,线程A持有的资源A锁,线程B持有B锁,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。

JNI 异步线程 c回调静态java java多线程异步回调_JNI 异步线程 c回调静态java_27


测试代码

public class DeadLockDemo {
    public static void main(String[] args){
        String lockA = "lockA";
        String lockB = "lockB";

        new Thread(new MyThread(lockA,lockB),"线程A").start();

        new Thread(new MyThread(lockB,lockA),"线程B").start();

    }
}

class MyThread implements Runnable{
    //创建两个资源
    private String lockA;
    private String lockB;

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

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName()+"拥有->"+lockA+" want to get "+lockB);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+"拥有->"+lockB+" want to get "+lockA);
            }
        }
    }
}

JNI 异步线程 c回调静态java java多线程异步回调_单例模式_28

4.2产生死锁的四个条件:

(1).互斥条件:该资源任何一个时刻只由一个线程占有。

(2).请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3).不剥夺条件:线程已获得的资源在未使用之前不能被其他线程强行剥夺,只能自己使用完后才释放资源。

(4).循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

自私,不让,不准抢,必须轮流

4.3如何避免死锁?
上面说了死锁的四个必要条件,为了避免死锁,我们只要破坏产生死锁的四个条件中的其中一个就可以了。现在我们来挨个分析一下:

(1).破坏互斥条件:这个条件无法破坏,因为我们用锁本来就是让他们单独拥有

(2).破坏请求与保持条件:一次性把所有进程需要的资源全部拿走。这样就不会在运行的途中进行再去申请资源了。

(3).破坏不剥夺条件:以退为进。当某个线程申请不到资源的时候,把自己拥有的资源都释放

(4).破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。