JUC学习笔记(上篇)

目录

一、Future

1、FutureTask

1.1、相关架构

1.2、基本使用

1.3、优点和缺点

2、CompletableFuture

2.1、CompletionStage简介

3、completableFuture常用方法

3.1、获得结果和触发计算

3.2、对计算结果进行处理

3.3、对计算结果进行消费

3.4、completeFuture和线程池的说明

3.5、对计算速度进行选用

3.6、对计算对计算结果进行合并

二、多线程锁

1、乐观锁和悲观锁

1.2、乐观锁:

2、八锁案例

2.1、八锁案例描述

2.2、八锁案例详解

3、锁的字节码实现

3、为什么任何一个对象都能作为一把锁

4、公平锁和非公平锁

5、可重入锁

6、死锁及排查

7、加锁和加锁流程

 三、线程中断

1、简介

2、实现方法

3、线程等待和唤醒(LockSupport)

四、JMM

1、定义和作用

 2、三大特性

2.1、可见性

2.2、原子性

2.2、有序性

3、多线程下对变量的读写过程

4、happens-before

1、总原则

2、八大原则

3、总结 

 五、volatile

1、特点

2 、内存屏障

 2.1、分类

 3、使用内存屏障实现有序性(禁用重排)

 4、 使用内存屏障实现可见性

 5、volatile变量没有原子性

 6、volatile的使用场景

 7、总结

六、CAS

1、原理及简介

 2、unsafe

2.1、案例

2.2、底层汇编

 3、AtomicReference(借鉴CAS实现自旋锁)

 4、CAS缺点

 5、解决ABA问题

七、原子类(Atomic)

1、基本类型原子类 

2、数组类型原子类

3、引用类型原子类

4、对象的属性修改原子类

5、原子操作增强类深度解析


一、Future

Future接口的实现类,Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的业务。有三个特点 多线程/有返回/异步任务

1、FutureTask

1.1、相关架构

1.2、基本使用

 public void testm() throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(()->{
            System.out.println("futureTask running");
            return "hello";
        });

        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println(futureTask.get());
    }

1.3、优点和缺点

优点

future + 线程池异步多线程任务配合, 能显著的提高程序的执行效率

缺点

  1. 通过get方法求结果,如果该线程的执行时间比较长那么很容易造成程序的阻塞,且如果设置了超时时间会抛出异常,不够友好。
  2. 通过isDone方法,即轮询的方法获取执行进度,会耗费无谓的cpu资源,而且也不见得能及时的的到计算的结果。
  3. future对于结果的获取不是很友好,只能通过轮询或阻塞的方式得到任务的结果。

2、CompletableFuture

实现了future和CompletableStage接口,提供了一种观察者模式类似的机制,可以让任务执行完成完成后通知监听的一方

2.1、CompletionStage简介

2.2 CompletableFuture入门

基本api

 基本使用

public void test1() throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName());
        },executorService);

        CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            return "hello";
        }, executorService);

        System.out.println(stringCompletableFuture.get());

    }

通用使用方法

ExecutorService executorService = Executors.newFixedThreadPool(3);
        try {
            CompletableFuture.supplyAsync(()->{
                System.out.println(Thread.currentThread().getName());
                int i = ThreadLocalRandom.current().nextInt(10);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("一秒后出结果");
                return i;
            },executorService).whenComplete((v,e)->{
                //v为上一步的返回值,e为上一步出现的异常
                if (e == null){
                    System.out.println("上一步的结果为:"+v);
                }
            }).exceptionally(e->{
                e.printStackTrace();
                return null;
            });
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            executorService.shutdown();
        }
    }

3、completableFuture常用方法

函数式编程参数的相关含义

 3.1、获得结果和触发计算

  1. getNow()
//马上获取返回的值,如果异步任务没有执行完,则返回方法的参数,这里是5
future.getNow(5);

      2、complete()

//马上获取返回的值,如果异步任务没有执行完,则打断get或join,此时使用get或join获取的是complete中设置的值
        System.out.println(future.complete(5) + "\t" + future.get());

3.2、对计算结果进行处理

  1. thenApply()

计算结果存在依赖性,让两个线程串行化

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            return f+1;
        }).thenApply((f1)->{
            return f1+2;
        }).thenApply(f1->{
            return f1+2;
        }).whenComplete((v,e)->{
            System.out.println(v);
        }).exceptionally(e->{
            e.printStackTrace();
            return null;
        });

 2、handle()

与thenApply功能相同

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            return f+1;
        }).handle((f1,e)->{
            return f1+2;
        }).handle((f1,e)->{
            return f1+2;
        }).whenComplete((v,e)->{
            System.out.println(v);
        }).exceptionally(e->{
            e.printStackTrace();
            return null;
        });

3、两者异同

不同的是对异常的处理,thenApply()遇到异常时就会叫停,不会在执行下面的任务,而handle()则有异常也可以往下一步,根据带的异常参数可以进一步处理

3.3、对计算结果进行消费

1、thenAccept()

接收任务的处理结果,并进行消费处理,无返回结果

CompletableFuture.supplyAsync(() -> {
            return f+1;
        }).handle((f1,e)->{
            return f1+2;
        }).handle((f1,e)->{
            return f1+2;
        }).thenAccept(r->{
            System.out.println(r);
        }).exceptionally(e->{
            e.printStackTrace();
            return null;
        });

2、thenRun()

不用参数也没有返回的结果

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
            return f+1;
        }).handle((f1,e)->{
            return f1+2;
        }).handle((f1,e)->{
            return f1+2;
        }).thenRun(()->{
            System.out.println("AAAA");
        }).exceptionally(e->{
            e.printStackTrace();
            return null;
        });

3.4、completeFuture和线程池的说明

在completeFuture中几乎每一个对结果进行消费的方法都有一个Async版本,那这两个版本有什么区别呢,如thenRun和thenRunAsync的区别。

如果使用的是thenRun方法则这个线程使用的线程池与上一个使用的线程池是一样的,而如果使用的是thenRunAsync则与上一个使用的线程池有可能不同,且可以自己指定线程池,默认使用的是ForkJoin线程池。

有时候处理的太快,系统优化原则,会直接使用main线程进行处理

3.5、对计算速度进行选用

谁的速度快就用谁的返回结果

applyToEither()

public void test2() throws InterruptedException {
        CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "A";
        });
        CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "B";
        });
        CompletableFuture<String> future = futureA.applyToEither(futureB, (f) -> {
            return f + "\t" +"winner";
        });

        System.out.println(future.join());

3.6、对计算对计算结果进行合并

两个任务都完成后,最终将两个任务的结果一起交给thenCombine来处理,且先完成的先等待,等待另一个分支任务完成

CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
            return "A";
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            return "B";
        }),(x,y)->{
            return x + "\t" + y;
        });
        System.out.println(futureA.join());

二、多线程锁

1、乐观锁和悲观锁

1.1、悲观锁:认为自己在使用的时候一定会有别的线程来修改数据,因此在获取数据的时候回先加上锁,以确保数据不会被别的线程修改。常见的 实现方法有synchronized和lock

1.2、乐观锁:

2、八锁案例

2.1、八锁案例描述

class phone{

    public synchronized void sendEmail() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println("sendEmail");
    }

    public synchronized void sendSMS(){
        System.out.println("sendSMS");
    }
}

public class CompleteFutureDemo {
    public static void main(String[] args) {
        phone phone = new phone();

        new Thread(()->{
            try {
                phone.sendEmail();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"m1").start();

        new Thread(()->{
            phone.sendSMS();
        },"m2").start();
    }
}

2.2、八锁案例详解

1、1-2:

 当调用同一个对象的普通同步方法时,此时锁为new出来的的这个对象

2、3-4

当是两个对象时所用的锁都不一样了所以,当是两个对象时,则两把锁不相关

3、5-6

 当调用的是静态的同步方法则使用的锁是类锁,与对象的多少无关,都是同一把锁

4、7-8

当同时调用静态同步方法和普通同步方法时,前者使用的是类锁,后者使用的是对象锁,两者的锁都不一样所以不相关

3、锁的字节码实现

1、同步代码块的实现

 当在同步代码块中自己抛出了异常则只会有一个exit

2、对象锁的实现

 3、类锁的实现

3、为什么任何一个对象都能作为一把锁

在HotSpot中monitor通过ObjectMonitor实现,每一个对象都会带着一个对象监视器,每一个被锁住的对象都会和monitor相关联

 

ObjectMonitor的初始化

4、公平锁和非公平锁

使用的是ReentrantLock类

5、可重入锁

简介

实例

class Demoq{
    public synchronized void m1(){
        System.out.println("m1.start");
        m2();
        System.out.println("m1.end");
    }
    public synchronized void m2(){
        System.out.println("m2.start");
        m3();
    }
    public synchronized void m3(){
        System.out.println("m3.start");
    }

}
public class Demo1 {


    public static void main(String[] args) {
        Demoq demoq = new Demoq();
        demoq.m1();
    }
}

 隐式synchronlzed

 显式ReentrantLock类即当加了几次锁需要自己去解几把锁

6、死锁及排查

简介

排查

7、加锁和加锁流程

 

 三、线程中断

1、简介

 实现中断的api

 2、实现方法

public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("interrupted被设置为true,程序中断");
                    break;
                }
                System.out.println("hello");
            }
        });
        thread1.start();

        try {
            TimeUnit.MILLISECONDS.sleep(200);
        }catch (Exception e){
            e.printStackTrace();
        }
        //将标识位interrupt设置为true
        thread1.interrupt();
    }

interrupt注意事项

 interrupted()方法

3、线程等待和唤醒(LockSupport)

1.1、方法1:

使用Object中的wait方法让线程等待,使用Object中的notify()方法唤醒线程

public static void main(String[] args) {
        Object o = new Object();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t come in");
            synchronized (o){
                try {
                    o.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "\t 被唤醒了");
        },"t1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t come in");
            synchronized (o){
                o.notify();
            }
            System.out.println(Thread.currentThread().getName() + "\t 唤醒了t1");
        }, "t2").start();
    }

唤醒线程的方法必须要在挂起线程的方法之后执行,否则不起作用

必须在锁的代码块中

1.2、方法二

使用JUC包中的Condition的方法让线程等待,使用signai方法唤醒线程

public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t come in");
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + "\t 被唤醒了");
        }, "t1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t come in");
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t 唤醒了t1");
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }

注意事项与上面相同

1.3、方法三

LockSupport类

public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t come in");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t 被唤醒了");
        }, "t1");
        t1.start();

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

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t come in");
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + "\t 唤醒了t1");
        }, "t2").start();
    }

part方法获取凭证,如果没有获取到则会一直阻塞线程,直到使用unpark为线程发放凭证

四、JMM

1、定义和作用

 2、三大特性

2.1、可见性

 2.2、原子性

一个操作不可被打断,即多线程环境下,操作不能被其他线程干扰

2.2、有序性

 

3、多线程下对变量的读写过程

 

4、happens-before

在jmm中如果一个操作执行的结果需要对另一个操作可见性或者要单重排序,那么两个操作之间必须存在happens-before(先行发生)原则,为逻辑上的先后关系

案例描述

1、总原则

 2、八大原则

1、次序规则

一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作

2、锁定规则

3、volatile变量规则

4、传递规则

5、线程启动规则(Thread Start Rule)

6、线程中断规则(Thread Interruption Rule)

7、线程终止规则(Thread Termination Rule)

8、对象终结规则(Finalizer Rule)

 3、总结 

 五、volatile

1、特点

可见性和有序性

通过内存屏障(Memory Berrier)来实现

2 、内存屏障

重排序的定义

内存屏障定义

 2.1、分类

粗分为两种

读屏障:

写屏障

细分为四种

 3、使用内存屏障实现有序性(禁用重排)

 3.1、禁用规则

 使用volatile读操作时插入内存屏障后生成的指令序列示意图

 使用volatile写操作时插入内存屏障后生成的指令序列示意图

案例讲解

 

 

4、 使用内存屏障实现可见性

volatile变量的读写过程

 5、volatile变量没有原子性

 6、volatile的使用场景

1、

2、状态标志,判断业务是否结束

3、当读远多于写时

 4、双端锁案例

由于new SafeDoubleCheckSingleton(不能保证有序性)

导致在多线程情况下其他线程拿到的是一个没有完成初始化的对象

 7、总结

如何保证可见性

如何保证有序性

 

 字节码实现

内存屏障是什么

六、CAS

使用的是类似乐观锁的思想

1、原理及简介

 2、unsafe

2.1、案例

2.2、底层汇编

 3、AtomicReference(借鉴CAS实现自旋锁)

public class Demo6 {

    AtomicReference<Thread> threadAtomicReference = new AtomicReference<>();


    void lock(){
        Thread thread = Thread.currentThread();

        while (!threadAtomicReference.compareAndSet(null, thread)){

        }
    }

    void unLock(){
        Thread thread = Thread.currentThread();
        threadAtomicReference.compareAndSet(thread, null);

    }

    public static void main(String[] args) {
        Demo6 demo6 = new Demo6();

        AtomicInteger atomicInteger = new AtomicInteger();
        int andIncrement = atomicInteger.getAndIncrement();
        new Thread(()->{
            demo6.lock();
            System.out.println(Thread.currentThread().getName() + "\t come in");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            demo6.unLock();
            System.out.println(Thread.currentThread().getName() + "\t task over");
        },"t1").start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t come in");
            demo6.lock();

            demo6.unLock();
            System.out.println(Thread.currentThread().getName() + "\t task over");
        },"t2").start();
    }
}

4、CAS缺点

1、循环时间长的时候开销会比较大

2、ABA问题

 5、解决ABA问题

引入了版本号问题

Atomic实现类 AtomicStampedReference

public static void main(String[] args) {
        AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<Integer>(100,1);
        new Thread(()->{
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int stamp = stampedReference.getStamp();
            stampedReference.compareAndSet(100,101, stamp, stampedReference.getStamp()+1);
            System.out.println("第一次更改流水号为"+stampedReference.getStamp());
            int stamp1 = stampedReference.getStamp();
            stampedReference.compareAndSet(101,100, stamp1, stampedReference.getStamp()+1);
            System.out.println("第二次更改流水号为"+stampedReference.getStamp());

        }).start();
        new Thread(()->{
            int stamp = stampedReference.getStamp();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stampedReference.compareAndSet(100,2002, stamp, stampedReference.getStamp()+1);
            System.out.println(stampedReference.getReference() +"\t" + stampedReference.getStamp());
        }).start();

    }

  执行结果

七、原子类(Atomic)

 1、基本类型原子类 

 常用API

2、数组类型原子类

API与上面差不多,就只有在操作值的时候需要带上索引

3、引用类型原子类

 AtomicStampedReference

 AtomicMarkableReference

 4、对象的属性修改原子类

已一种线程安全的方式操作非线程安全对象类的某些字段

使用的要求

更新的对象属性必须使用public volatile 修饰符

因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并需要设置想要的更新的类和属性

class Account {
    private volatile int money;

    AtomicIntegerFieldUpdater<Account> fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Account.class, "money");

    public void add(Account account) {
        fieldUpdater.getAndIncrement(account);
    }

    public int getMoney() {
        return money;
    }
}

public class Demo8 {
    public static void main(String[] args) throws InterruptedException {
        Account account = new Account();
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i <= 9; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 10000; j++) {
                        account.add(account);
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();

        System.out.println(account.getMoney());
    }
}

5、原子操作增强类深度解析

 

 5.1、LongAdder为什么会这么快

 

 5.2、源码解析

 longAccumulate方法

 

 流程图

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值