JUC编程

1.1 CPU底层知识


多线程

什么是进程和线程
一个程序进入内存,被称为进程?一个QQ可以运行多分吗?
同一个进程内部,有多个任务并发执行的需求(比如,一边计算,一边接受网络,一边刷新界面)
能不能使用多进程?可以但是毛病多,最严重的毛病是,我可以轻易搞死别的进程
线程的概念横空出世:共享空间,不共享计算
​
进程是静态的概念:程序进入内存分配对应资源:内存空间,程序进入内存,同时产生一个主线程
进程就是资源
线程是动态的概念:是可以执行的计算单元 ,线程共享进程空间
线程就是一条条指令
​
  • 一个ALU(逻辑计算单元)同一时间只能执行一个线程

  • 同一个代码可以被多个线程执行

线程的切换
保存上下文,保存现场
​
问题:是不是线程线程数量越多,执行效率越高?
肯定不是
问题:单核cpu多线程执行有没有意义
有
问题:对于一个程序,设置多少个线程合适?(线程池设定多少核心线程)
cup应该有多少线程=cpu*cpu利用率*(1+wait/c)
wait/等待时间
c/计算时间
  • 内存到cpu的缓存

缓存一致性协议
CPU的速度和内存速度(100:1)
现在工业实践中,多用三级缓存架构
缓存行:一次性读取的数据块
程序的局部性原来:空间局部性 时间局部性
如果缓存行大:命中率搞,但是读取效率低,命中率低,但读取效率高
工业实践的妥协结果,目前(2021)的计算机多采用64bytis(64*8bit)为一行
由于缓存行的存在,我们必须有一种机制,来保证缓存数据的一致性,这种机制被称为缓存一致性协议。
MESI MOSI 等一些缓存一致性协议
  • Disruptor消息队列

Disruptor是一种环形的数据结构,只需要一个指针
而其他的消息队列是数组结构,需要两个指针
  • 什么是缓存行

    cpu寄存器从内存中读取数据或指令。一但出现多线程运行的模式,这写线程会抢夺cpu时间片,当A线程抢夺到时间片,B线程就会等待,那么B线程抢夺到时间片,A线程就会等待,如何A线程执行到了一般时间片就被B线程抢走了,那么当A线程重新抢到时间会继续执行尚未完成的,还是会重新执行。答案当然是会继续执行,因为当A线程的时间片被抢走之后,线程执行完成的部分会放到缓存中(内存区域),工业实践妥协的结果,计算机采用三级缓存机制。   
    在三级缓存机制中,我们是如何读取数据,如何一个一个读,虽然读取很快,但是返回结果慢。一次读取很多,效率会高的很多,但是在读取的过程中会很慢。工业实践的妥协结果,目前(2021)的计算机多采用64bytis(64*8bit)为一行   
  • 缓存行的代码的演示

这是没开启缓存行式

public class Demo01 {
    private static long COUNT=10_0000_0000L;
    private static class T{
//        private long l1,l2,l4,l5,l6,l7;
        public long x=0L;
//        private long p1,p2,p3,p4,p5,p6,p7,p8;
    }
    public static T[] arr=new T[2];
    static {
        arr[0]=new T();
        arr[1]=new T();
    }
    public static void main(String[] args)throws Exception {
        CountDownLatch latch=new CountDownLatch(2);
        Thread t1=new Thread(()->{
            for (long i=0;i<COUNT;i++){
                arr[0].x=i;
            }
            latch.countDown();
        });
        Thread t2=new Thread(()->{
            for (long i=0;i<COUNT;i++){
                arr[1].x=i;
            }
            latch.countDown();
        });
        final long l = System.nanoTime();
        t1.start();
        t2.start();
        latch.await();
        System.out.println(System.nanoTime()-l);
    }
}
​

开始缓存行之后

public class Demo01 {
    private static long COUNT=10_0000_0000L;
    private static class T{
        private long l1,l2,l4,l5,l6,l7;
        public long x=0L;
        private long p1,p2,p3,p4,p5,p6,p7,p8;
    }
    public static T[] arr=new T[2];
    static {
        arr[0]=new T();
        arr[1]=new T();
    }
    public static void main(String[] args)throws Exception {
        CountDownLatch latch=new CountDownLatch(2);
        Thread t1=new Thread(()->{
            for (long i=0;i<COUNT;i++){
                arr[0].x=i;
            }
            latch.countDown();
        });
        Thread t2=new Thread(()->{
            for (long i=0;i<COUNT;i++){
                arr[1].x=i;
            }
            latch.countDown();
        });
        final long l = System.nanoTime();
        t1.start();
        t2.start();
        latch.await();
        System.out.println(System.nanoTime()-l);
    }
​

可以看出来在我们开启缓存行时,代码的执行效率提高了一倍以上

  • 多线程的单例模式

饿汉单例

​
public class Singleton01 {
    private static final Singleton01 SINGLETON_01=new Singleton01();
    private Singleton01(){}
    public static Singleton01 getSingleton01(){
        return SINGLETON_01;
    }
​
    public static void main(String[] args) {
        Singleton01 a1 = Singleton01.getSingleton01();
        Singleton01 a2 = Singleton01.getSingleton01();
        System.out.println(a1==a2);
    }
}

懒汉单例

public class Singleton02 {
    private static  Singleton02 SINGLETON_02;
    private Singleton02(){}
    public  static Singleton02 getInstance(){
​
        if(SINGLETON_02==null){
            synchronized (Singleton02.class){
                if(SINGLETON_02==null){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    SINGLETON_02=new Singleton02();
                }
            }
​
        }
        return SINGLETON_02;
    }
    public static void main(String[] args) {
        for (int i=0;i<1000;i++){
            new Thread(()->{
                System.out.println(Singleton02.getInstance().hashCode());
            }).start();
        }
​
    }
}

懒汉单例模式在多线程环境下会出现许多错误,所以我们会加锁,保证程序的原子性

DCl单例有没有bug

//当然时有的,在创建了一半对象,这时候对象已经有地址了,当另一个线程过来这就会检测出对象不为空,就会返回一个对象,但是这个对象只有初始值,因为发生了指令重排的问题,才会导致这个bug的出现
​
以下8中情况下时不准换顺序
hanppens-before原则(JVM规定重排序必须遵守的原则 )
  • CPU乱序执行的概念

CPU乱序执行:
    cpu的运行速度时内存的100倍
    有一条指令把数据读入寄存器,还有一条数据把是把这个数据加1,但是因为CPU的速度比较快,当在等待内存响应数据时,会有一部分空挡时间,这时会执行下一条指令,这就是CPU的乱序执行
为什么会乱序,主要提高效率    
    
  • CPU乱序执行的证明

public class DemoTest01 {
    private static int x=0,y=0;
    private static int a=0,b=0;
​
    public static void main(String[] args) throws InterruptedException {
        for(long i=0;i<Long.MAX_VALUE;i++){
            x=0;
            y=0;
            a=0;
            b=0;
            CountDownLatch latch=new CountDownLatch(2);
            Thread one =new Thread(()->{
               a=1;
               x=b;
               latch.countDown();
            });
            Thread other =new Thread(()->{
                b=1;
                y=a;
​
                latch.countDown();
            });
            one.start();
            other.start();
            latch.await();
            String result="第"+i+"次("+x+","+y+")";
            if(x==0&&y==0){
                System.err.println(result);
                break;
            }
        }
    }
}

  • 程序真的时顺序按顺序运行吗?

线程的as-if-serial:
    单线程,两条语句,未必是俺顺序运行
    单线程的重排序,必须保证最终一致性
    as-if-serial:开上去像是序列化(单线程)
会产生的后果
     多线程会产生意向不到的结果   
  • 禁止编译器乱序

  • 内存屏障防止乱序执行

内存屏障是特殊指令,看到这种指令,前面必须执行完,后面才能执行
​
  • JVM的内存屏障

所以实现JVm规范的虚拟机,必须实现四个屏障
    LoadLoadBarrier LoadStore SL SS
LoadLoad屏障
LoadStore屏障
CPU 如何完成?
    hotsport:的volatile细节
  • volatile的底层实现

/*本质两大作用:
    保证可见性,一个CPU改变的内容,另一个CPU马上可见:一个线程改变内容,则另一个就必须马上可见
    禁止重排序:禁止指令重排
    保证可见性,但是不保证原子性(多线程操作进行的读写,不保证线程安全);而synchronized是一种排它(互斥)的机制
在多线程的环境下还是要使用锁机制 
 */   
    public class CounThread  implements Runnable{
    private volatile int count=0;
    private static final Object o=new Object();
    @Override
    public void run() {
​
        for(int i=0;i<100;i++) {
            synchronized (o) {
                count++;
                System.out.println("count =========>>>" + count);
            }
        }
    }
}
/*
    变量使用了volatile关键字,可以保证多线程下数据的可见性,但是不保证原子性(在多线程环境下volatile修饰的变量也是线程不安全的)。在多线程环境下还是要使用锁机制
*/
  • volatile的使用场景

    • 开关控制

    利用可见性地特点,控制某一段代码的执行和关闭()

    • 多个线程操作共享变量,但是只用一个线程对其进行写操作,其他线程都是读

  • 解决问题

使用锁机制,给count++加上一把锁,那么count++就是一个临界区,临界区只有一个线程进行操作,这样Count++就变成原子性的操作了。

   public class CounThread  implements Runnable{
        private volatile int count=0;
        private static final Object o=new Object();
        @Override
        public void run() {
​
            for(int i=0;i<100;i++) {
                synchronized (o) {
                    count++;
                    System.out.println("count =========>>>" + count);
                }
            }
        }
    }

LOCk用于在多线程中执行指令时对共享内存的独占用

它的作用时能够将当前处理对应缓存的内容刷新,并使用器处理器对应的缓存失效

另外还提供了有序的指令无法越过这个内存屏障的作用

LOCk并非只是锁总线

  • CPU的并发控制:关中断,缓存一致性

JUC编程入门

1.2 线程状态


线程由生到死的完整过程

当线程被创建时,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态?在API中java.lang.Thread.State这个枚举中给出了六中状态:

线程状态导致状态发生条件
NEW(新建)线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread只有线程对象,没有线程特征。
Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。调用了t.start()方法 :就绪(经典教法)
Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

什么是并行并发

A学生(线程)和B学生(线程)在进入两个门(CPU),不管先后性,这就可以被乘坐是并行。
如果两个学生不管以方式进入,在路人甲眼里,他两个都在办公室,这就是并发
并发:
    并发是一种现象,同时运行多个程序或多个被程序处理的对象
并行:
    可以通过多进程/多线程的方式取得多任务,并以多进程/多线程的方式执行这些任务
​
并发与并行的区别
所以,并发和并行的区别就很明显了。它们虽然都说是"多个进程同时运行",但是它们的"同时"不是一个概念。并行的"同时"是同一时刻可以多个进程在运行(处于running),并发的"同时"是经过上下文快速切换,使得看上去多个进程同时都在运行的现象,是一种OS欺骗用户的现象。

1.3 原子类


概述:java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。

对原有的代码进行改进

public class CounThread02 implements Runnable{
    //原子类,定义一个int类型的变量
        private AtomicInteger atomicInteger=new AtomicInteger();
        @Override
        public void run() {
​
            for(int i=0;i<100;i++) {
                //以原子方式将当前值加1,注意,这里返回的是自增前的值。
                int j = atomicInteger.getAndIncrement();
                System.out.println("count =========>>>" +j);
​
            }
        }
    }

1.4 原子类 CAS机制介绍


CAS原理:比较在交换:

线程A在读取到变量a,并器增强1,这时候线程B也读取到变量B,此时变量B为0,在要把数据写会时,要读取重新读取一下变量a,这时候在进行复制,这就是CAS的比较在交换。

乐观锁和悲观锁

  • 悲观锁(synchronized)

synchronized总是从悲观的角度的出发的,每次拿数据都会认为别人会修改,所以在每次拿数据时都会进行上锁,这样别的线程就会阻塞,知道拿到锁。

共享资源只给一个线程使用,其他线程阻塞,用完再把资源转让给其他线程,因此称为synchronized为悲观锁

  • 乐观锁(CAS锁)

CAS是从乐观的情况下出发的:

总是假设最好的情况,每次拿数据搜认为别人不会修改,所以不会上锁,但是在更新的时候回去判断一下在此期间有没有人去更新这个数据。

CAS这种机制我们称为乐观锁。综合性能好

1.5 并发包ConcurrentHashMap


并发包的来历:
    在实际开发中如果不需要考虑线程安全问题,大家不需要线程安全,因为如果做了反而性能会降低,
    但是开发中有很多业务是需要考虑线程安全问题,此时就考虑。否则业务出现问题。
    java为很多业务场景提供了性能优异,且线程安全的并发包,程序员可以选择
    (原子类)
​
Map集合中的经典集合:HashMap他是线程不安全的,到那时性能好
    -- 如果在要求线程安全的业务情况下就不能使用Map集合,否则业务会崩溃
为了保证线程安全,可以使用HashTable,注意,线程中加入了计时
为了保证线程安全,在看看ConcurrentHashMap(不止线程安全,而且效率高,但是性能好,最新最好用的)
    --ConcurrentHashMap保证了线程安全,综合性能好
  • HashMap

public class Conset {
    public static ConcurrentHashMap<String,String> hashMap=new ConcurrentHashMap <>();
}
  @Override
    public void run() {
​
        for(int i=0;i<50000;i++){
            Conset.hashMap.put(this.getName()+(i+1),this.getName()+i+1);
        }
        System.out.println(this.getName()+"结束");
    }

进行测试

public static void main(String[] args) throws InterruptedException {
        Thread t1=new MyThread();
        Thread t2=new MyThread();
        t1.setName("线程一");
        t2.setName("线程二");
        t1.start();
        t2.start();
        Thread.sleep(1000*5);
        System.out.println("Map的大小"+Conset.hashMap.size());
    }

HashMap线程不安全

Hash是一个Hash表,两个线程都往一个数组里存储数据,但是最后只能留下一个,所以线程是不安全的

HashTable线程安全

HashTable的每个方法都加了synchronized锁了

ConcurrentHashMap 实例

新方法

t1.jion //让t1先跑完
t2.jion //让t2跑完
    //主线程能抢其他的CPU

小结

HashMap 它是线程不安全,但是性能好
HashTable 锁整个集合线程安全,性能较差!!!淘汰了
ConcurrentHashMap 线程安全,性能得到了极大的提升了!!分段锁!只锁自己操作的元素位置!综合性能最好

1.6 并发包CountDownLath


CountDownLath 允许一个或多个线程等待其他线程完成操作,在执行自己
例如:线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后
    要线程2打印B之后才能打印c,所以:线程1在打印A后
    必须等待2打印B之后才能继续执行
需求:
    提供A线程,打印A,C
    提供B线程,打印B
public countDownLath(int count) //初始化唤醒需要几步
    

代码演示

​
public class MyTest {
    public static void main(String[] args) {
        CountDownLatch latch=new CountDownLatch(1);
        new MyThread1(latch).start();
        new MThread2(latch).start();
    }
}
class MyThread1 extends Thread{
    private CountDownLatch c;
​
    public MyThread1(CountDownLatch c) {
        this.c = c;
    }
​
    @Override
    public void run() {
​
        System.out.println("A");
        //等待自己
        try {
            c.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("C");
    }
}
class MThread2 extends Thread{
    private CountDownLatch c;
​
    public MThread2(CountDownLatch c) {
        this.c = c;
    }
    @Override
    public void run() {
​
        System.out.println("B");
        c.countDown();
    }
}

1.7 并发包 CycliBarrier


CycliBarrier (循环屏障)作用
    某个线程任务必须等待其他线程执行完毕后才能触发执行。
例如:公司召集5名员工开会,等5名员工都到了,会议开始
    使用CycliBarrier线程保证5名员工全部执行后,在执行开会线程
    
  • 小结

可以实现多线程中,某个任务等待其他线程执行完毕以后触发

循环屏障可以实现到达一组屏障,就触发一个任务!

代码演示:

public class MTest02 {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier=new CyclicBarrier(5,new Messing());
        for(int i=1;i<=5;i++){
            new EmployeeThread("员工"+i,cyclicBarrier).start();
        }
    }
}
class Messing implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始会议");
    }
}
​
class EmployeeThread extends Thread{
    private CyclicBarrier cyclicBarrier;
    public  EmployeeThread(String name,CyclicBarrier cyclicBarrier){
        super(name);
        this.cyclicBarrier=cyclicBarrier;
    }
    @Override
    public void run() {
​
        System.out.println(Thread.currentThread().getName()+"进入会议");
        try {
            cyclicBarrier.await();//每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程放回到线程池里
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}
​
构造方法 public  CyclicBarrier(5,new Messing());
第一个参数:是会议人数
第二个参数:实现了Ruanble接口,执行完毕会议之后,在进行这个任务
CyclicBarrier 的底层使用的线程池实现的
public void await()方法不是阻塞,只是把线程放回线程池里。

代码演示

1.8 并发包 Semaphore


Semaphore(发信号)的主要作用就是控制线程的并发数量。

synchronize可以起到“锁的”作用,但是在某个时间内,只允许一个线程执行

Semaphore可以设置同时允许几个线程执行。

Semaphore字面意思是信号量的意思,他的作用是控制特地资源的线程数目

Semaphore构造方法

public Semaphore(int permoits)
public semaphor (int permits,boolean fair) fair 表示公平性,    
​
  

代码演示

servie:
public class Servie {
    private Semaphore semaphore=new Semaphore(2);
    public void testMode(){
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName()+"进入 时间="+System.currentTimeMillis());
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+"结束 时间="+System.currentTimeMillis());
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
    }
}
Thread:
public class MyThread extends Thread{
    private Servie servie;
    public MyThread(Servie servie){
        super();
        this.servie=servie;
    }
    @Override
    public void run() {
        servie.testMode();
​
    }
}
DemoTest:
public class DemoTest {
    public static void main(String[] args) {
        Servie servie=new Servie();
        for(int i=0;i<5;i++){
            Thread t1=new MyThread(servie);
            t1.setName("线程"+i);
            t1.start();
        }
​
    }
​
}
​

允许一个线程执行:

允许两个线程执行

这个并发包可以用在秒杀环节上,设置多长时间进入的线程的个数

1.9 并发包 Exchanger(交换者)


作用:
    Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。
    这两个线程通过Exchanger 方法交换数据,如果第一个线程先执行exchanger()方法,她会一直等待第二个线程也会执行Exchanger方法,当两个线程都到达同步点时,这两个线程就交换数据,将本线程生产出来的数据 传递给对方
    

代码演示

public class Test01 {
    public static void main(String[] args) {
        Exchanger<String> exchanger=new Exchanger<>();
        new Boy(exchanger).start();
        new Griy(exchanger).start();
    }
}
class Boy extends Thread{
    private Exchanger<String> exchanger;
    public Boy(Exchanger<String> exchanger){
        this.exchanger=exchanger;
    }
    @Override
    public void run() {
        System.out.println("男孩正在做🔒");
        String rs= null;
        try {
            rs = exchanger.exchange("同心锁🔒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("男孩收到礼物:"+rs);
    }
}
class Griy extends Thread{
    private Exchanger<String> exchanger;
    public Griy(Exchanger<String> exchanger){
        this.exchanger=exchanger;
    }
    @Override
    public void run() {
        System.out.println("女孩正在做🔑");
        try {
            String rs = exchanger.exchange("钥匙🔑");
            System.out.println("女孩收到礼物:"+rs);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
    }
}
public class Test01 {
    public static void main(String[] args) {
        Exchanger<String> exchanger=new Exchanger<>();
        new Boy(exchanger).start();
        new Griy(exchanger).start();
    }
}
class Boy extends Thread{
    private Exchanger<String> exchanger;
    public Boy(Exchanger<String> exchanger){
        this.exchanger=exchanger;
    }
    @Override
    public void run() {
        System.out.println("男孩正在做🔒");
        String rs= null;
        try {
            rs = exchanger.exchange("同心锁🔒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("男孩收到礼物:"+rs);
    }
}
class Griy extends Thread{
    private Exchanger<String> exchanger;
    public Griy(Exchanger<String> exchanger){
        this.exchanger=exchanger;
    }
    @Override
    public void run() {
        System.out.println("女孩正在做🔑");
        try {
            String rs = exchanger.exchange("钥匙🔑");
            System.out.println("女孩收到礼物:"+rs);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
    }
}

Exchanger交换者的主要用途,是用于银行的报表的进行校对是使用的

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值