JUC学习

文章目录

  • :sunny: 基础概念
        • :moon: 用户线程代码演示和总结
  • :sunny: `CompletableFuture`
          • :star: `Future` 接口
          • :star: `FutureTask`
          • :star: `CompletableFuture`
  • :sunny: 线程锁相关知识
        • :star: 悲观锁和乐观锁
        • :star: 公平锁与非公平锁
  • :sunny: 中断机制
        • :star: `LockSupport`
  • :sunny: `jMM` 模型
  • :sunny: `volatile`
  • :sunny: 原子类
        • :star: 基本原子类型
        • :star: 对象属性修改的原子类

☀️ 基础概念

 

并发(concurrent): 在同一台处理器上,处理多个任务。

并行(parallel): 在多台处理器上同时处理多个任务。

进程: 系统中运行的应用程序就是一个进程,每一个进程都有他自己的内存空间和系统资源。

线程: 又称为 轻量级进程,在同一个进程内会有一个或者多个的线程,是大多数操作系统进行时序调度的基本单元。

管程: Monitor(监视器) ,即我们平时所说的锁。他是一个同步机制,在同一时间内,只有一个线程可以访问被保护的数据和代码。

用户线程: user thread,系统的工作线程,它会完成这个程序要完成的业务操作。

守护线程: daemon thread,是一种特殊的线程,为其他线程进行服务的,在后台默认的完成一系列的系统任务。( gc 回收机制)

 

判断一个线程是否为守护线程的方法:


    /**
     * Tests if this thread is a daemon thread.
     *
     * @return  true if this thread is a daemon thread;
     *          false otherwise.
     * @see     #setDaemon(boolean)
     */
    public final boolean isDaemon() {
        return daemon;
    }

用户线程代码演示和总结

默认情况下创建的线程,都是用户线程!

栗子:

// 用户线程
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"\t当前线程开始运行\t"+(Thread.currentThread().isDaemon()?"守护":"用户"));
                while (true){
                }
            }
        },"t1").start();

        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName()+"\t当前线程开始运行 ---- end 主线程");
    }

// 守护线程

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "\t当前线程开始运行\t" + (Thread.currentThread().isDaemon() ? "守护" : "用户"));
                while (true) {
                }
            }
        }, "t1");
        thread.setDaemon(true);
        thread.start();

        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName()+"\t当前线程开始运行 ---- end 主线程");
    }

SUM: 如果用户线程全部结束,那么程序需要完成的业务操作已经结束了。守护线程随着 JVM 一同结束工作。


☀️ CompletableFuture


⭐️ Future 接口

Future 接口(FutureTask 实现类),定义了操作异步任务执行的方法,如获取异步任务的执行结果,取消任务的执行,判断任务是否被取消,判断任务是否执行完毕。也就是 Future 接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂业务。

作用:
Future 接口是 java5 中新增的接口,它提供了一种 异步并行计算 的功能。如果主线程需要执行一个很耗时的计算任务,我们可以通过 Future 把这个任务放到异步线程中执行。主线程会继续处理其他任务或先行结束,在通过 Future 获取计算结果。


⭐️ FutureTask

异步多线程任务执行且返回结果有三个特点:

  • 多线程
  • 又返回
  • 异步任务

Runnable 接口 VS Callable 接口

  • 重写方法名称不一样, Callable —— callRunnable —— run
  • Runnable 没有返回值没有抛出异常,Callable 有返回值,并且会抛出异常。

使用示例 :

public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task = new FutureTask<>(new MyThread2());
        Thread thread = new Thread(task, "t1");
        thread.start();
        // 获取任务的返回结果
        System.out.println(task.get());
    }
}

class MyThread2 implements Callable<String> {

    @Override
    public String call() throws Exception {
        return null;
    }
}

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

示例:

    public static void main(String[] args) throws ExecutionException, InterruptedException {
//         3 个任务,目前开启多个异步任务来处理,请问耗时多少
        ExecutorService pool = Executors.newFixedThreadPool(3);
        long start = System.currentTimeMillis();
        FutureTask<String> task1 = new FutureTask<String>(()->{
            try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
            return "task1 over";
        });
        pool.submit(task1);

        FutureTask<String> task2 = new FutureTask<String>(()->{
            try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
            return "task2 over";
        });
        pool.submit(task2);
        task1.get();
        task2.get();
        try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        long end = System.currentTimeMillis();
        pool.shutdown();
        System.out.println("耗时时间为: " + (end-start));
        System.out.println(Thread.currentThread().getName()+"-------- end");
    }

    public static void m1(){
        // 3 个任务,目前只有一个线程 main 来处理,请问耗时多少
        long start = System.currentTimeMillis();
        try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
        try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        long end = System.currentTimeMillis();
        System.out.println("耗时时间为: " + (end-start));
        System.out.println(Thread.currentThread().getName()+"-------- end");
    }

缺点:

  • get() 方法,容易导致程序阻塞,一般建议放在程序的后面,get(long, TimeUnit) 方法,超过等待多少秒之后就不再继续阻塞。

   public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        FutureTask<String> task = new FutureTask<String>(()->{
            System.out.println(Thread.currentThread().getName()+"---- come in");
            TimeUnit.SECONDS.sleep(5);
            return "task over";
        });
        new Thread(task,"t1").start();
//        System.out.println(task.get());'
        System.out.println(task.get(6,TimeUnit.SECONDS));
        System.out.println(Thread.currentThread().getName()+"----- 去做其他事情");
    }
  • isDown() 轮询,会耗费无畏的 cpu 资源,也不见得能及时的获得结果。如果想要异步的获取结果,通常会以轮询的方式获取结果,尽量不要阻塞。
 public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        FutureTask<String> task = new FutureTask<String>(() -> {
            System.out.println(Thread.currentThread().getName() + "---- come in");
            TimeUnit.SECONDS.sleep(5);
            return "task over";
        });
        new Thread(task, "t1").start();
//        System.out.println(task.get(6,TimeUnit.SECONDS));
        System.out.println(Thread.currentThread().getName() + "----- 去做其他事情");
        while (true) {
            if (task.isDone()) {
                System.out.println(task.get());
                break;
            }else {
                System.out.println("四月打弟弟,四月欺负弟弟!!!");
            }
        }
    }

SUM: Future 对于结果的获取很不友好,只能通过阻塞或者轮询的方式去获取结果。


⭐️ CompletableFuture

提供了一种观察者模式相类似的机制,可以让任务执行完成之后去通知监听的一方。


JUC学习_第1张图片

CompletionStage 接口:CompletionStage 代表异步计算过程中的某一个阶段,一个阶段完成以后可以出发另一个阶段。一个阶段可以被单个阶段触发,也有可能被多个阶段触发。


创建 CompletableFuture 四种静态方法:

 

runAsync(Runnable) 静态方法,无返回值,使用默认的线程池 ForkJoinPool

runAsync(Runnable ,Executor ) 静态方法,无返回值,传入线程池。

supplyAsync(Supplier) 静态方法,又返回值,使用默认的线程池 ForkJoinPool


public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(3);
//        CompletableFuture future = CompletableFuture.runAsync(() -> {
//        CompletableFuture future = CompletableFuture.runAsync(() -> {
//        CompletableFuture future = CompletableFuture.supplyAsync(() -> {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            // 暂停几分钟
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            return "hello world";
        },pool);
//        });
        System.out.println(future.get());
    }

代码演示:

    public static void main(String[] args) {
            ExecutorService pool = Executors.newFixedThreadPool(3);
        try {
            CompletableFuture.supplyAsync(() -> {
                System.out.println(Thread.currentThread().getName() + "---- come in");
                Integer result = ThreadLocalRandom.current().nextInt(10);
                if (result>5){
                    Integer num =result/0;
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return result;
            }, pool).whenComplete((v, e) -> {
                if (Objects.isNull(e)) {
                    System.out.println("----- 计算完成,更新系统 updateValue: " + v);
                }
            }).exceptionally((e) -> {
                e.printStackTrace();
                System.out.println("异常情况:" + e.getCause() + "\t" + e.getMessage());
                return null;
            });
            System.out.println(Thread.currentThread().getName() + "线程先去忙其他的任务去了!");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            pool.shutdown();
        }
    }
  • 异步任务结束之后,会自动回调某个对象的方法。
  • 主线程设置好了回调,不在关心异步线程的执行,异步任务之间可以顺序执行。
  • 异步任务出现错误的时候,会自动回调某个对象的方法。

JUC学习_第2张图片

示列:

public class CompletableFutureMallDemo {

    static List<NetMall> list  = Arrays.asList(
            new NetMall("jd"),
            new NetMall("dangdang"),
            new NetMall("pdd")
    );

    /**
     * setup by setup
     * @param list
     * @param name
     * @return
     */
    static List<String> getPrice(List<NetMall> list,String name){
       return list.stream().map(netMall ->
            String.format(name+" in %s price is %.2f",netMall.getName(),netMall.calcPrice(name))
        ).collect(Collectors.toList());
    }

    /**
     *  List ==== List> ==== List
     *
     * @param list
     * @param name
     * @return
     */
    static List<String> getPriceByCompletableFuture(List<NetMall> list,String name){

      return   list.stream().map(netMall -> CompletableFuture.supplyAsync(()->
            String.format(name+" in %s price is %.2f",netMall.getName(),netMall.calcPrice(name))
        )).collect(Collectors.toList()).stream().map(s->s.join())
                .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        List<String> mysql = getPrice(list, "mysql");
        mysql.forEach(System.out::println);
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+(end-start));

        System.out.println("========================");

        long start2 = System.currentTimeMillis();
        List<String> mysql2 = getPriceByCompletableFuture(list, "mysql");
        mysql2.forEach(System.out::println);
        long end2 = System.currentTimeMillis();
        System.out.println("耗时:"+(end2-start2));
    }

}

class NetMall{

    private String  name;

    public String getName() {
        return name;
    }
    public NetMall(String name){
        this.name=name;
    }

    public    double calcPrice(String name){
        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        return ThreadLocalRandom.current().nextDouble()*2+name.charAt(0);
    }
}

常用方法:

  1. 获得结果和触发预算

获取结果:

  • get()
  • get(long,TImeUtin)
  • join()
  • getNow(T),如果计算完成则返回计算完成之后的结果,如果没有计算完成则返回默认的结果。
    触发预算:
  • complete(T),是否打断 get() 方法立即返回括号里面的值。
  1. 对计算结果进行处理
  • thenApply,计算结果存在依赖,两个线程串行化,如果当前步骤有异常则不会继续向下云行。
  • handle,该方法正常情况下和上面的方法执行方式相同,如果出现异常可以继续向下执行。
  1. 计算结果进行消费
  • thenRun ,执行完 A 任务,执行 B 任务,且 B 任务不需要 A 任务的结果。

  • thenAcceptB 任务需要 A 任务执行完成的结果,但无返回值。

  • thenApplyB 任务需要 A 任务执行完成的结果,但有返回值。

thenRun VS thenRunAsync

  1. 没有传入自定义线程池,则使用默认线程池。
  2. 如果传入自定义的线程池,thenRun 方法执行第二个任务和第一个任务使用的线程池相同。thenRunAsync 执行第二个任务时候,如果传入的第一个任务的线程池是自定义的,则第二个任务则使用默认的线程池 ForkJoinPool
  3. 系统处理太快,系统优化原则,则使用 main 线程。


  1. 对速度计算进行选用

applyToEither 进行比较。

  1. 对计算结果进行合并

thenCombine 把两个结果进行处理,先完成的要等待,等待其他任务完成。


☀️ 线程锁相关知识


⭐️ 悲观锁和乐观锁

悲观锁:
synchronized 关键字和 lock 的实现都是悲观锁。认为自己在使用数据的时候,一定有别的线程来修改数据,因此会在获取数据的时候先加锁,确保数据不会被别的线程修改。

使用场景,先加锁可以保证写操作时,数据正确。

乐观锁:
认为自己在使用数据时,不会有别的线程来修改数据和资源,所以不会添加锁。在 java 中通过使用无锁编程来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。

判断规则:

  1. 版本号机制,version
  2. 最长采用的是 CAS 算法,java 中原子类的递增就是通过 CAS 来实现的。

适合读操作较多的场景,不加锁的特点能够使其读操作性能大幅度的提升。

 

synchronized 关键字解读:

  1. 一个对象里面如果有多个 synchronized 的方法,在某一时刻内,只要线程去调用其中一个 synchronized 方法, 其他的线程都只能等待,换句话来说,在某一时刻,只能有唯一的一个线程去访问这些 synchronized 方法。 锁的是当前对象 this,被锁定之后,其他线程都不能进入到当前对象的其他 synchronized 方法。

  2. 加一个普通方法后发现和同步锁无关,换成两个对象后,不是同一把锁了,情况立刻发生了变化

  3. 都换成静态同步方法,三种 synchronized 的锁内容有一些差别对于普通方法,锁的是当前实例对象,通常指 this 对于静态方法,锁的是当前类的 class 对象,对于同步方法块,锁的是 synchronized 括号内的对象。

  4. 当一个线程试图访问同步代码块时,他首先必须得到锁,正常退出或者抛出异常时必须释放锁。
    所有普通同步方法用的都是同一把锁 —— 实例对象本身,就是 new 出来的实例对象本身,本类 this 也就是说如果一个实列对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放之后才能获取锁。 所有的静态同步方法用的也是同一把锁 —— 类对象本身, 具体实例对象 this 和唯一模板 class, 这两把锁对应不同的对象,所以静态同步方法和普通的同步方法之间是不会产生竞争条件的, 但是一旦静态方法获取锁之后,其他的静态方法都必须等待该方法释放锁之后才能获取锁。


作用:

  • 当前实例加锁,进入同步代码前要获得当前实例的锁。
  • 对括号里配置的对象锁。
  • 当前类加锁,进入同步代码前要获得当前类的锁。

⭐️ 公平锁与非公平锁

非公平锁: 非公平锁更充分的利用了 cpu 的时间片段。尽量减少 cpu 的空闲时间。减少了线程的开销。

可重入锁: 也叫递归锁,一个线程的多个流程可以获取同一把锁,持有这把锁可以再次进入。自己可以获取自己的内部锁。指可重复可递归调用的锁,在外层使用锁之后,内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。

简单来说,在一个 synchronized 修饰的方法或者代码块中内部调用本类的其他 synchronized 修饰的方法或者代码块时,永远可以得到锁。

隐式锁 —— synchronized 关键字

显示锁 —— Lock 也有 ReentrantLock 这样的可重入锁


死锁命令排查:

  • jsp -l 查看进程号
  • jstack 进程号 排查问题
  • jconsole 图形化工具

JUC学习_第3张图片


☀️ 中断机制


一般来说一个线程不应该由其他线程来强制中断或者停止,而是应该由线程自己来进行停止。


⭐️ LockSupport

是一个线程阻塞的工具类,所有方法都是静态方法。其中 park() 方法和 unpark() 方法,分别是阻塞线程和解除线程的。每个使用他的线程都有一个许可证,最多只有一个且不会累计。


☀️ jMM 模型

java 规范中尝试定义一种内存模型(Java Memory Model),来屏蔽各种硬件与操作系统之间内存访问差异,已实现 java 在各个平台下都能达到一致的访问效果。

JMM 模型,本身是一种抽象的概念,并不真实存在,他仅仅描述的是一组约定或规范。通过这组规范,定义了程序中(尤其是多线程),各个变量的读写和访问方式。并且决定了一个线程对共享变量的写入何时以及如何编程对另一个线程的可见,关键的技术点都是围绕多线程的原子性可见性以及有序性展开的。

总结:

我们定义的变量都存储在物理内存中,每个线程都有自己独立的工作内存。里面保存该线程使用到的变量的副本。


☀️ volatile

使用 volatile 修饰共享变量,每次都会读取主内存中的数据,然后复制到工作内存中,


☀️ 原子类

⭐️ 基本原子类型

案例:

class MyNumber {

 AtomicInteger integer   =new AtomicInteger();

 public void add(){
     integer.getAndIncrement();
 }
}

public class AtomicIntegerDemo {

    public static final int SIZE = 50;

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

        MyNumber number = new MyNumber();
        CountDownLatch latch = new CountDownLatch(SIZE);

        for (int i = 0; i < SIZE; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 1000; j++) {
                        number.add();
                    }
                }finally {
                    latch.countDown();
                }
            }).start();
        }
        // 等待上面的五十个线程全部计算完成。再去获得最终结果的的值

//        Thread.sleep(2);
        latch.await();
        System.out.println(Thread.currentThread().getName()+"\t"+"result: "+number.integer.get());
    }
}


⭐️ 对象属性修改的原子类

案例:

class MyVar {
    public volatile Boolean isInit = Boolean.FALSE;

    AtomicReferenceFieldUpdater<MyVar, Boolean> updater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");

    public void init(MyVar myVar) {
        if (updater.compareAndSet(myVar, Boolean.FALSE, Boolean.TRUE)) {
            System.out.println(Thread.currentThread().getName() + "\t" + "------ start");
            try {
                Thread.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "------ over");
        } else {
            System.out.println(Thread.currentThread().getName() + "\t" + "------ 已经有线程进行初始化操作!");
        }
    }
}

/**
 * @author: yueLQ
 * @date: 2022-06-30 11:57
 */
public class AtomicReferenceFieldUpdateDemo {

    public static void main(String[] args) {
        MyVar myVar = new MyVar();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                myVar.init(myVar);
            }, String.valueOf(i)).start();

        }
    }
}

你可能感兴趣的:(java,java)