并发编程 - 基础(20231017)

一、线程基础概念

1. 基础概念


1.1 进程和线程

进程: 进程是指运行中的程序。 比如我们使用钉钉、浏览器、等程序。操作系统会给程序分配内存资源。

线程: 线程是cpu调度的基本单位, 每个线程执行的都是某个进程的代码的某个片段。

区别;

  1. 进程像是线程的容器。一个进程至少包含一个线程
  2. 一个进程下的线程可以共享进程中的资源, 线程也有自己独立的存储空间。 而进程间的资源通常是独立的。而进程之间的通信是很麻烦的,需要借助内核才能实现,而线程之间的通信就方便很多
1.2 多线程

多线程是指: 进程中同时运行了多个线程。多线程的目的是为了提高cpu的利用率。避免一些网络io或者磁盘io的等待时间,让cpu去操作其他线程,这样可以提高程序的效率, 不用一直等待。

如: tomcat可以做并行处理, 提升处理的效率,而不是一个个排队等待请求完成才能处理下个请求。

线程的局限性: 并不是线程越多越好, 因为cpu在切换线程的时候会有上下文切换开销,如果是cpu密集型的程序设置了很多线程,那么执行效率可能还不如单线程。如果是io密集型,cpu大部分都在等待io操作的时间, 那么cpu 切换到到其他线程去执行,可以提到利用率

如: redis就是单线程的, 但是他的执行效率非常高

线程安全问题; 虽然多线程带来了一定的性能提升,但是做一些操作时,多线程如果操作临界资源时候会出现线程安全问题,如果使用锁不当, 还会出现死锁问题。

1.3 串行、并发、并行
  • 串行: 串行就是一个个排队,第一个完成, 第二个才能上
  • 并行: 并行就是同时一起执行。开两个窗口同时排两个队伍一起工作
  • 并发: 这里的并发并不是大量请求的的意思, 是指cpu在极短的时间类反复切换执行不同的线程, 在我们眼里感觉好像是一起执行, 但是在cpu的层级他是串行去执行的。并行囊括并发。

1.4 同步异步、 阻塞非阻塞

同步与异步: 执行某个功能后, 被调用者是否**主动反馈**信息。

阻塞和非阻塞: 执行某个功能后,调用者是否需要==一直等待结果==的反馈

2.线程的创建

2.1继承Thread类,重写润方法
public class Test {
    public static void main(String[] args) {
        Job job = new Job();
        job.start();
    }
}

class Job extends Thread {
    private int a;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Job:" + a++);
        }
    }
}
2.2实现Runnable接口, 重写run方法
public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(new Job());
        thread.start();
    }
}

class Job implements Runnable {
    private int a;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Job:" + a++);
        }
    }
}

2.3实现Callable重写call方法, 配合FutureTask
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("开始...."+ new Date());
        FutureTask<String> futureTask = new FutureTask<String>(new Job());
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println("等待任务执行完成"+ new Date());
        String s = futureTask.get();
        System.out.println("任务执行完成"+ new Date());
        System.out.println(s);

    }
}

class Job implements Callable<String> {
    private int a;


    @Override
    public String call() throws InterruptedException {
        Thread.sleep(5000);
        a = 1;
        System.out.println("Callable: ");
        return "a";
    }
}

3.线程的基本使用

3.1 线程的状态

传统的线程状态。

  1. 新建状态(new)>刚刚创建出来
  2. 就绪状态(ready)>调用start方法,就会变成就绪状态, 但是cpu还未调度
  3. 运行状态(run) > cpu 划到了时间片正在运行的状态
  4. 等待状态(waiting) > cpu时间片结束了但是任务还未结束的时候(cpu不会划分到时间片)。还有wait、sleep、join方法也会让cpu当前线程处于阻塞状态
  5. 结束状态(Terminated)> 线程生命周期到头了

在java中线程提供了6种状态

  1. 新建状态(new)>刚刚创建出来
  2. 运行/就绪状态(runnable)>调用start方法,就会变成就绪状态, 但是cpu还未调度
  3. 阻塞状态(blocked) > synchronized 没有拿到资源, 被放在EntryList中阻塞
  4. 等待状态(wanting) > 调用wait方法,让线程处于等待状态,需要手动唤醒
  5. 时间等待状态(timed_wating)> 调用sleep、join方法让线程处于时间等待状态, 会被自动唤醒无需手动唤醒
  6. 结束状态(Terminated)> 线程生命周期到头了

3.2 线程的常用方法
  1. 获取当前线程的方法(Thread.currentThread())
        Thread thread = Thread.currentThread();
        System.out.println(thread);
  1. 获取当前线程的名字( thread1.setName())
        Thread thread = new Thread(()->{
            System.out.println(Thread.currentThread().getName());
        },"业务模块 - 功能");
        thread.start();
  1. 线程优先级(thread1.setPriority())
//线程优先级就是cpu调度优先级
        Thread thread1 = new Thread(()->{
            for (int i = 0; i < 10; i++) {

                System.out.println(Thread.currentThread().getName());
            }
        },"业务模块 - 功能1");
        Thread thread2 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName());
            }
        },"业务模块 - 功能2");

        thread1.setPriority(1);
        thread1.start();

        thread2.setPriority(10);
        thread2.start();
  1. 线程的让步(Thread.yield())
        Thread thread1 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                if (i == 5) {
                    Thread.yield();
                }
                System.out.println("t1: " + i);
            }
        },"业务模块 - 功能1");
        Thread thread2 = new Thread(()->{
            for (int i = 0; i < 10; i++) {

                System.out.println("t2: " + i);
            }
        },"业务模块 - 功能2");
        thread1.start();

        thread2.start();
  1. 线程的休眠(Thread.sleep())
// 让线程从运行状态变为等待状态
        System.out.println(System.currentTimeMillis());
        Thread.sleep(1000);
        System.out.println(System.currentTimeMillis());

  1. 线程的强占, 那个线程中调用这个方法,那个线程就会等待该线程执行完成(t1.join())
    也饿可以传递等待时间参数(t1.join(1000))需要注意的是如果t1 线程提前执行完成那么调用该方法的对象就继续
    
    执行了不会等待1s才开始
    
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("t1: " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("t2: " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        t2.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main: " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (i ==1){
                // 这里会等待t1线程执行完成后继续执行
                t1.join();
            }
        }

  1. 守护线程( t1.setDaemon(true);)如果设置了这个线程为守护线程, 那么在程序中非守护线程执行完成后守护线程也会停止执行
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("t1: " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.setDaemon(true);
        t1.start();
    }
  1. 线程的等待和唤醒(wait() ,notify(),notifyAll() )

可以让获取synchronized锁资源的线程通过wait方法进入到锁的等待池,并且会释放锁资源

可以让获取synchronized锁资源的线程,通过notify或者通过notifyAll方法,将等待池中的线程唤醒,添加到锁池

notify随机唤醒等待池中的一个线程到锁池中

notifyAll 将等待池中的所有线程都唤醒,并且添加到锁池中

notify 和 wait 方法要写在获取锁的方法中或者同步块内部

 public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread t1 = new Thread(() -> {
            aaa();
        }, "t1");
        Thread t2 = new Thread(() -> {
            aaa();
        },"t2");
        t1.start();
        t2.start();
        Thread.sleep(1000);
        synchronized (Test.class){
//            Test.class.notify();
            Test.class.notifyAll();
        }
    }
    public static synchronized void aaa(){
        try {
            for (int i = 0; i < 10; i++) {
                if (i == 5) {
                    Test.class.wait();
                }
                System.out.println(Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

3.2 线程的结束方式
  1. 常用方式是return 或者 throw 抛出异常结束方法
  2. stop 方法, 这个方法现在已经被抛弃, 不推荐
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    System.out.println(i);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "t1");
        t1.start();
        Thread.sleep(3000);
        t1.stop();
    }
  1. 使用共享变量, 有些线程是使用死循环保证线程一直运转, 使用共享变量结束死循环
  2. interrupt 标记位的方式, 其实和共享变量的方式差不多
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程默认情况下, interrupt 标记位: false
        System.out.println(Thread.currentThread().isInterrupted());
        // 执行interrupt后, 标记位改为true
        Thread.currentThread().interrupt();
        // 查询一下标记位变为: true
        System.out.println(Thread.currentThread().isInterrupted());
        // 先返回当前线程interrupt标记位后, 并归位, 改为false
        System.out.println(Thread.interrupted());
        // 再次查询一下标记位, 已经归位变为: false
        System.out.println(Thread.currentThread().isInterrupted());
    }
  1. 通过打断WAITING或者TIMED_WAITING状态的线程, 从而抛出异常自行处理。这中停止打断线程的方式是最常用的,在框架和juc中也是最常见的。
    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("结束线程!");
                    return;
                }
            }
        });
        thread.start();
        Thread.sleep(500);
        thread.interrupt();
        Thread.sleep(5000);
    }

二、并发编程的三大特性

1. 原子性

1.1 什么是并发编程的原子性

JMM(Java Memory Model)。不同的硬件和不同的操作系统在内存上的操作有一定差异的。 Java为了解决相
同代码在不同操作系统上出现的各种问题,用 JMM屏蔽掉各种硬件和操作系统带来的差异。

让Java的并发编程可以做到跨平台,

JMM规定所以的变量都会存储在主内存中,在操作的时候, 需要从主内存中复制一份到线程内存(CPU内存),在线程内存做计算。 然后再写回主内存(不一定及时操作)

原子性的定义:原子性指一个操作是不可分割的, 不可中断的,一个线程在执行时,另一个线程不会影响到他。

原子性:多线程操作临界资源,预期结果与实际结果一致

1.2 如何保证并发编程的原子性
  1. synchhronized
  2. CAS
  3. Lock锁
  4. ThreadLocal
1. synchronized

正常的a++ 操作在字节码层面分为3步


    public static void main(String[] args) {
        a++;
    }

![在这里插入图片描述](https://img-blog.csdnimg.cn/c9e244d1c3114e9a82e8dfa6124e7d54.png)

添加了synchronized代码块后, 在字节码添加了 monitorenter 和monitorexit, 在线程执行到了monitorenter 后,会加锁, 后续其他线程执行到这个指令就会阻塞, 等待拿到锁的线程执行了monitorexit后锁释放, 其他线程去竞争锁

    public static void main(String[] args) {
        synchronized (test3.class) {
            a++;
        }
    }

在这里插入图片描述

2. CAS

什么是cas

compare and swap : 比较并交换, 他是一条cpu的并发原语

他在替换内存的某个位置的值时,首先查看内存中的值与预期的是否相同, 如果一致,执行替换操作。而整个操作是原子性操作。

java 中提供了Unsafe的类提供了对CAS的操作方法, jvm会帮助我们将方法实现CAS汇编指令

但是cas只是比较和交换, 但是在获取原值要自己来实现

static AtomicInteger atomicInteger = new AtomicInteger();

public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
atomicInteger.incrementAndGet();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
atomicInteger.incrementAndGet();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(atomicInteger);
}



**CAS的缺点: **CAS只能保证对一个变量的操作是原子性的, 无法实现对多行代码实现原子性。

CAS的问题:

  • ABA的问题:可以使用版本号的方式来解决ABA的问题。java 中提供了一个类在CAS时,针对各个版本追加版本号的操作。AtomicStampeReference
AtomicIntegerAtomicInteger
    static AtomicInteger atomicInteger = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                atomicInteger.incrementAndGet();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                atomicInteger.incrementAndGet();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(atomicInteger);
    }

Doug Lea 在CAS的基础上实现了一些原子类,其中就包括实例上用的AtomicInteger, 还有其他很多原子类。。。。


  • 自旋时间过长问题:
    • 可以指定CAS一共循环多少次, 如果超过这个次数,直接失败即可。(自旋锁,自适应锁)。
    • 在CAS一次失败后, 将这个操作暂存起来,后面需要获取结果时, 将暂存的操作全部执行, 再返回最后的结果。
3. Lock锁

Lock锁是在DK1.5由Doug Leat研发的,他的性能相比synchronized在JDK1.S的时期,性能好了很多,但是
在JDK1.6对synchronized优化之后,性能相差不大,但是如果涉及并发比较多时,推荐ReentrantLock锁,性能
会更好。


    static ReentrantLock lock = new ReentrantLock();

    private static int aInt;

    public static void increment() {
        lock.lock();
        try {
            aInt++;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                increment();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                increment();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(aInt);
    }

ReentrantLock 可以直接对比synchronized ,在功能上来说, 都是锁。 但是ReentrantLock 的功能性相比于synchronized 更加丰富。

ReentrantLock 底层是基于AQS实现的, 有一个基于CAS维护的state变量来实现锁的操作


4. ThreadLocal

ThreadLocal保证原子性的方式,是不让多线程去操作临界资源,让每个线程去操作属于自己的数据

    static ThreadLocal tl1 = new ThreadLocal();
    static ThreadLocal tl2 = new ThreadLocal();

    public static void main(String[] args) throws InterruptedException {
        tl1.set("111");
        tl2.set("222");

        Thread t1 = new Thread(() -> {
            System.out.println(tl1.get());
            System.out.println(tl2.get());
        });
        System.out.println(tl1.get());
        System.out.println(tl2.get());
        t1.start();
        t1.join();
    }

ThreadLocal实现原理:

  • 每个Thread中都存储着一个成员变量,ThreadLocalMap
  • ThreadLocalz本身不存储数据,像是一个工具类,基于ThreadLocal去操作ThreadLocalMap
  • ThreadLocalMap本身就是基于Entry[ ]实现的,因为一个线程可以绑定多个ThreadLocal,这样一来,可能需要存储多个数据,所以采用Entry[ ]的形式实现。
  • 每个线程都有自己独立的ThreadLocalMap, 再基于ThreadLocal对象本身作为kay,对value进行存取。
  • ThreadLocalMap的key是弱引用, 弱引的特点是,即便有弱引用,在GC时, 也必须被回收, 这里是为了在ThreadLocal对象在失去引用后, 如果key的引用是强引用, 会导致ThreadLocal对象无法被回收。

ThreadLocal内存泄漏问题:

  • 如果ThreadLocal引用丢失,key因为弱引用会被GC回收掉,如果同时线程还没有被回收,就会导致内存泄漏,内存中的value无法被回收, 同时也无法被获取。
  • 只需要在使用完毕ThreadLocal对象之后,及时的调用remove方法,移除Entry即可

2. 可见性

2.1 什么是可见性

可见性问题是基于CPU的位置出现的一个问题,CPU处理速度非常快,相对CPU来说,去主内存获取数据这个事情太慢了,CPU就提供了L1,L2,L3的三级缓存,每次去主内存拿完数据后,就会存储到CPU的三级缓存,每次去三级缓存拿数据,效率肯定会提升。

这就带来了问题,现在CPU都是多核,每个线程的工作内存(CPU三级缓存)都是独立的,会告知每个线程中做修改时,只改自己的工作内存,没有及时的同步到主内存,导致数据不一致问题。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可见性代码问题:

    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (flag) {

            }
        }).start();
        Thread.sleep(1000);
        flag = false;
        System.out.println("主线程完成");
    }
2.2 解决可见性的方式
2.2.1 volatile

volatile 是一个关键字, 用来修饰成员变量。

如果属性被volatile修饰, 相当于会告诉cpu,当前属性的操作,不允许使用CPU的缓存, 必须去和主内存操作

volatile的内存含义:

  • volatile属性被写:当写一个volatile变量,JMM会将当前线程对应的CPU缓存及时的刷新到主内存中
  • volatile属性被读:当读一个volatile变量,JMM会将对应的CPU缓存中的内存设置为无效,必须去主内存中重新读取共享变量

其实加了volatile就是告知CPU,对当前属性的读写操作,不允许使用CPU缓存,加了volatile修饰的属性,会在转为汇编之后后,追加一个lock的前缀,cpu在执行这个指令时,如果带有lock前缀会做两件事:

  • 将当前处理器缓存行的数据写回到主内存。
  • 这个写回的数据,在其他的CPU内核的缓存中,直接无效。

总结:volatile就是让CPU每次操作这个数据时,必须立即同步到庄内存,以及从主内存读取数据。

    private volatile static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (flag) {

            }
            System.out.println("线程结束");
        }).start();
        Thread.sleep(1000);
        flag = false;
        System.out.println("主线程完成");
    }
2.2.2 synchronized

其实在上面synchronized 原子性的实例代码中也有可见性问题,但是最终加了synchronized后最终结果正确。

synchronized 能实现可见性是基于synchronized的内存语义,如果涉及到了synchronized的同步代码块或者是同步方法,获取锁资源之后,将内部涉及到的变量从CPU缓存中移除**(只有在获取锁的时候会释放缓存)**,

        new Thread(() -> {
            // 锁加载while外面不生效
            synchronized (test2.class) {
                while (flag) {

                }
                System.out.println("t1线程结束");
            }
        }, "t1").start();

        new Thread(() -> {
            
            while (flag) {
                // 锁加载while内部, 在获取锁的时候清除cpu缓存,达到可见性的方式
                synchronized (test3.class) {
                }
            }
            System.out.println("t2线程结束");
        }, "t2").start();

        Thread.sleep(1000);
        flag = false;
        System.out.println("主线程完成");
2.2.3 Lock

Lock锁保证可见性的方式和synchronized完全不同,synchronized基于他的内存语义,在获取锁和释放锁时,对CPU缓存做一个同步到主内存的操作。

Lock锁是基于volatile实现的。Lock锁内部再进行加锁和释放锁时,会对一个由volatile修饰的state属性进行加减操作。

如果对volatile修饰的属性进行写操作,CPU会执行带有lock前缀的指令,CPU会将修改的数据,从CPU缓存立即同步到主内存,同时也会将其他的属性也立即同步到主内存中。还会将其他CPU缓存行中的这个数据设置为无效,必须重新从主内存中拉取。


    private static boolean flag = true;
    private static Lock lock = new ReentrantLock();
    private volatile static boolean a = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (flag) {
                //使用lock操作保证可见性
//                lock.lock();
//                try {
//                } finally {
//                    lock.unlock();
//                }
                // 在循环内部如果对修饰了volatile的变量进行写操作,就会结束循环
                a = false;
            }
            System.out.println("t2线程结束");
        }, "t2").start();

        Thread.sleep(1000);
        flag = false;
        System.out.println("主线程完成");
    }

3. 有序性


3.1 什么是有序性

在java 中, java 文件中的内容会被编译, 在执行前需要转化为CPU可以识别的指令, CPU在执行这些指令时, 为了提升执行效率, 在不影响最终结果的前提下(满足一些要求),会对指令进行重排。而指令乱序执行的原因是为了提高cpu的使用率

以下java 中的程序是乱序执行的。


    static int a, b, x, y;


    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            a = 0;
            b = 0;
            x = 0;
            y = 0;
            Thread t1 = new Thread(() -> {
                a = 1;
                x = b;
            }, "t1");
            Thread t2 = new Thread(() -> {
                b = 1;
                y = a;
            }, "t2");
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            if (x == 0 && y == 0) {
                System.out.println("发生了指令重排: " + i);
            }
        }

    }

单例模式如果不加volatile 可能会发生的指令重排问题:


    private static volatile Test3 test3;
    private Test3() {
    }

    public static Test3 getTest3() {
        if (test3 == null) {
            synchronized (Test3.class) {
                if (test3 == null) {
                    //java 中new 一个对象 会分为三步
                    // 开辟空间, 初始化, test指针地址,。这三步如果没有加 volatile是乱序执行的
                    // 如果没有加volatile 那指令可以是 开辟空间,test指针地址,初始化
                    //有可能 a线程拿到锁后已经执行完成 开辟空间和 test指针地址了那b 线程在判断test3 == null 时候就会判断为false
                    //那么后续线程会拿着为初始化对象。操作了
                    test3 = new Test3();
                }
            }
        }
        return test3;
    }

3.2 as - if -serial( cpu 级别指令重排 )

as-if-seriali语义:

  • 不论指定如何重排序,需要保证单线程的程序执行结果是不变的。
  • 而且如果存在依赖的关系,那么也不可以做指令重排。

3.3 happen-before( JVM 级别指令重排 )

具体规则:

  1. 单线程happen-beforel原则:在同一个线程中,书写在前面的操作happen-before后面的操作,
  2. 锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。
  3. volatile的happen-before原则:对一个volatile?变量的写操作happen-before对此变量的任意操作。
  4. happen-before的传递性原则:如果A操作happen-before B操作,B操作happen-before C操作,那么A操作nappen-before C操作,
  5. 线程启动的happen-before)原则:同一个线程的start方法happen-beforel此线程的其它方法。
  6. 线程中断的happen-before原侧:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
  7. 线程终结的happen-before)原则:线程中的所有操作都nappen-before线程的终止检测。
  8. 对象创建的nappen-before原则:一个对象的初始化完成先于他的finalize方法调用。

JMM 只有在不出现上述8 中的情况时, 才不会触发指令重排效果

3.3 volatile

如果需要让程序对某一个属性的操作不出现指令重排,除了满足nappens-before原则之外,还可以基于volatile修饰属性,从而对这个属性的操作,就不会出现指令重排的问题了。

volatile如何实现的禁止指令重排?

volatile 是通过内存屏障的概念去实现的, 可以将内存屏障单做一条指令。

他会在可能重拍的操作之间,添加一条指令,这个指令就可以避免上下执行的其他指令进行重排序。

四、阻塞队列



五、线程池



六、并发集合



七、JUC并发工具



八、异步编程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值