JavaのJUC并发编程

JavaのJUC并发编程(上)

  • 1. 基础知识
    • 1.1. start开启线程
    • 1.2. 多线程相关概念
  • 2. CompletableFuture
    • 2.1. FutureTask
    • 2.2. CompletableFuture
      • a. 四大静态方法
      • b. 通用演示以及优点分析
      • c. CompletableFuture在电商项目中的使用
      • d. CompletableFuture的常用方法
  • 3. Java中的锁
    • 3.1. 悲观锁和乐观锁
    • 3.2. 线程8锁
    • 3.3. synchronized
    • 3.3. 公平锁和非公平锁
    • 3.4. 可重入锁(递归锁)
    • 3.5. 死锁

1. 基础知识

多线程的优点:

  • 提高程序性能,高并发系统

  • 提高程序吞吐量,异步+回调登生产需求

1.1. start开启线程

openJDK下载地址:jdk8/jdk8/jdk: 687fd7c7986d / (java.net)

java线程通过start的方法启动,调用底层native的start0方法。主要的三个底层C++代码为thread.cpp, jvm.cpp和Thread.c

Thread.java对应的是Thread.c,start0其实就是JVM_startThread,jvm.cpp中有实现
JavaのJUC并发编程_第1张图片

1.2. 多线程相关概念

  • 并发concurrent:一台处理器同时处理多个任务,同一时刻只有一个任务在执行

  • 并行parallel:多台处理器同时处理多个任务,同一时刻处理器在执行相互独立的任务

  • 线程

  • 进程

  • 管程monitor:就是锁,一种同步机制。保证同一时刻只能有一个线程访问临界资源。Monitor对象与java对象一同创建和销毁

  • 用户线程:系统的工作线程

  • 守护线程:为其他线程提供服务的,比如GC线程。可以使用**thread.isDaemon()**方法判断某一个线程是否是守护线程

2. CompletableFuture

2.1. FutureTask

Future接口(FutureTask实现类)定义**操作异步任务执行的一些方法**

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {}

Future接口是Java5中引入的,提供一种并行计算的能力。

如果主线程需要执行很耗时的任务,可通过Future接口将任务放入到异步线程中去执行。主线程处理完其他任务结束后,可通过Future获取计算结果。异步多线程任务执行的特定:多线程,有返回值,异步任务

Runnable Callable Future和FutureTask实现类

通过Callable接口和FutureTask

创建Callable接口的实现类 ,并实现Call方法。创建Callable实现类的实现,使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的Call方法的返回值**,使用FutureTask对象作为Thread对象的target创建并启动线程,调用FutureTask对象的get()来获取子线程执行结束的返回值**。通过Callable接口来创建线程的细节:

  • 实现callable接口的线程可以防止线程阻塞,但是注意该线程中task的get方法需要在放在最后,防止主线程被阻塞
  • 在创建线程时,如果传入的futureTask对象是同一个,只会执行一次线程中的方法

由于Thread的构造方法中没有以Callable接口为参数的,而Callable接口中存在一个子接口RunnableFuture,该子接口的实现类有一个FutureTask,并且在该类的构造方法中存在FutureTask(Callable callable)。因此可以使用该类去包装Callable接口才能作为Thread的target来创建线程:

Thread(Runnable target, String name) ->
Thread(RunnableFuture target, String name) ->
Thread(FutureTask(Callable) target, String name)

存在的弊端

  • get方法返回结果,一旦任务没完成会进入到阻塞状态,程序无法继续执行
  • 使用get方法设置超时时间,虽然会中断阻塞状态,但是会抛出TimeException
  • 使用isDone()方法进行while(true)轮询,会消费无谓的CPU资源,也不能及时得到结果

2.2. CompletableFuture

将多个异步任务的结果组合起来,后一个异步任务的结果依赖前一个异步任务的结果,这几个异步任务计算相互独立,同时后面又依赖前一个处理的结果

对于2.1.中的问题,希望可以通过传入回调函数,在Future结束时自动调用该回调函数,不再需要一直等待结果。CompletableFuture提供一种类似于观察者模式的机制,可以让任务执行完成后通知监听的一方

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {}
  • CompletionStage代表异步计算中的某一个阶段,一个阶段完成后可能会触发另一个阶段

  • 一个阶段的计算执行可以是一个Function,Consumer或者是Runnable

  • 一个阶段的执行可能是被单个节点的完成所触发的,也可能是由多个阶段一起触发(类似于Linux中的管道分隔符)

  • CompletableFuture提供函数式编程能力,可通过回调的方式处理计算结果,也提供了转换和组合的方法

a. 四大静态方法

runAsync 无返回值

以下没有指定Executor方法,则使用默认的ForkJoinPool.commonPool()作为他的线程池执行异步代码

如果指定了Executor方法,则使用自定义或者特别指定的线程池执行异步代码

  1. 只有Runnable作为输入参数
public static CompletableFuture<Void> runAsync(Runnable runnable) {
    return asyncRunStage(asyncPool, runnable);
}
  1. Runnable runnable,Executor executor
    public static CompletableFuture<Void> runAsync(Runnable runnable,
                                                   Executor executor) {
        return asyncRunStage(screenExecutor(executor), runnable);
    }

supplyAsync 有返回值

    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
        return asyncSupplyStage(asyncPool, supplier);
    }
    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
                                                       Executor executor) {
        return asyncSupplyStage(screenExecutor(executor), supplier);
    }

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

        // 1. runAsync(Runnable) 无返回值,使用默认的线程池ForkJoinPool.commonPool
        CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 2. runAsync(Runnable) 无返回值,使用自定义线程池
        ExecutorService threadPool1 = Executors.newFixedThreadPool(3);
        CompletableFuture<Void> completableFuture2 = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, threadPool1);
        threadPool1.shutdown();
        // 3.supplyAsync 有返回值,使用默认线程池
        CompletableFuture<String> completableFuture3 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello, completableFuture1...";
        });
        // 4. 3.supplyAsync 有返回值,使用自定义线程池
        ExecutorService threadPool2 = Executors.newFixedThreadPool(3);
        CompletableFuture<String> completableFuture4 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello, completableFuture2...";
        }, threadPool2);
        threadPool2.shutdown();

        System.out.println(completableFuture1.get());
        System.out.println(completableFuture2.get());
        System.out.println(completableFuture3.get());
        System.out.println(completableFuture4.get());

    }

b. 通用演示以及优点分析

CompletableFuture是Future的增强版,减少阻塞和轮询可以传入回调函数,当异步任务完成或者发生异常时,自动调用回调对象的回调方法

public static void main(String[] args) throws ExecutionException, InterruptedException {
        userFuture();
    }

    public static void userFuture() throws ExecutionException, InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " come in...");
            int res = ThreadLocalRandom.current().nextInt(10);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("2s之后出结果: " + res);
            if(res >= 5){ // 演示异常时的回调
                int i=1/0;
            }
            return res;
            // 回调函数使用whenComplete(Consumer(当前任务的结果,异常))
        }, threadPool).whenComplete( (v,e) ->{
            if(e == null){
                System.out.println("------------任务完成,返回结果: " + v);
            }
            // 发生异常时需要调用的回调函数
        }).exceptionally(e ->{
            e.printStackTrace();
            System.out.println("出现异常: " + e.getMessage());
            return null;
        });
        threadPool.shutdown();
        System.out.println(Thread.currentThread().getName() + " 处理其他任务...");
//        TimeUnit.SECONDS.sleep(5);
        System.out.println("task is over...");
    }

JavaのJUC并发编程_第2张图片

  • 异步任务结束或者发生异常时,会自动回调某个对象的方法
  • 主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以按顺序执行

c. CompletableFuture在电商项目中的使用

Lambda表达式+Stream流式调用+Chain链式调用+Java8函数式编程

需求:同一款产品,同时搜索出在各个平台的售价;同一款产品,同时搜索在同一个电商平台下,每个商家的售价

import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 1.同一款产品,同时搜索出在各个平台的售价
 * 2.同一款产品,同时搜索在同一个电商平台下,每个商家的售价
 */
public class CompletableFutureForMallProject {
    // 电商平台集合
    public static List<Mall> mallList = Arrays.asList(new Mall("jd"),
             new Mall("Taobao"),
             new Mall("Dangdang"),
            new Mall("PDD"),
            new Mall("Xiaohongshu"));
    // 根据电商平台的集合以及商品名查询到在每个平台的售价 返回一个价格的集合
    public static List<String> getPrices(List<Mall> list, String productName){
        return list.
                stream().
                map((mall) -> {
                    return String.format(productName + " in %s price is %.2f",
                      mall.getMallName(),
                      mall.getProductPrice(productName));
        }).collect(Collectors.toList());
    }
    // 使用CompletableFuture 并行处理每一个集合中的元素 处理完后收集每一个CompletableFuture任务的结果
    // List -> List> -> List
    public static List<String> getPriceByCompletableFuture(List<Mall> list, String productName){
        List<String> res = list.stream().map((mall) -> { return CompletableFuture.supplyAsync( () ->
                        String.format(productName + " in %s price is %.2f",
                        mall.getMallName(),
                        mall.getProductPrice(productName)));})
                .collect(Collectors.toList())
                .stream()
                .map((cf) -> {return cf.join();})
                .collect(Collectors.toList());
        return res;
    }

    public static void main(String[] args) {
        Long begin = System.currentTimeMillis();
        List<String> prices = getPrices(mallList, "JVM虚拟机");
        for(String price : prices){
            System.out.println(price);
        }
        long end = System.currentTimeMillis();
        System.out.println("----step by step process------cost time: " + (end - begin) + "ms");
        System.out.println("==============================================");

        Long begin2 = System.currentTimeMillis();
        List<String> prices2 = getPriceByCompletableFuture(mallList, "JVM虚拟机");
        for(String price : prices2){
            System.out.println(price);
        }
        long end2 = System.currentTimeMillis();
        System.out.println("----use CompletableFuture process------cost time: " + (end2 - begin2) + "ms");

    }
}

@Data
@AllArgsConstructor
class Mall{

    private String mallName;
    // 查询具体某一个电商平台中productName的售价
    public double getProductPrice(String productName){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return ThreadLocalRandom.current().nextDouble()*2 + productName.charAt(0);
    }
}

d. CompletableFuture的常用方法

  1. 获得结果和触发计算
// 获得结果的方法
public T get(long timeout, TimeUnit unit)
public T join()
// 如果当前任务未完成则输出getNow中指定的值    
public T getNow(T valueIfAbsent)
//当调用get方法发生阻塞时,complete方法结束当前阻塞,返回true以及complete中设置的值; 否则只返回false    
public boolean complete(T value)
  1. 对计算结果进行处理
 // thenApply: 出现异常时立即停止
 // handle:与thenApply的区别在于发生异常时,可以根据带的参数进一步处理
 // 计算结果存在依赖关系 -> 使得线程串行化
public <U> CompletableFuture<U> thenApply(
    Function<? super T,? extends U> fn)
    
public <U> CompletableFuture<U> handle(
    BiFunction<? super T, Throwable, ? extends U> fn)    
  1. 对计算结果进行消费
// 接收任务的处理结果并消费处理,无返回结果
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)

对比任务之间的执行顺序

  • thenApply:任务A执行完成后执行任务B,任务B需要任务A的结果,任务B有返回值
  • thenAccept:任务A执行完成后执行任务B,任务B需要任务A的结果,任务B无返回值
  • thenRun:任务A执行完成后执行任务B,并且B不需要A的结果
import java.util.concurrent.CompletableFuture;

/**
 * thenAccept任务A执行完成后执行任务B,任务B需要任务A的结果,任务B无返回值
 * 并且是消费型接口 -> join得到的是null
 * thenRun:没有返回值 -> join得到的是null
 * thenApply: 有返回值
 */
public class CompletableFutureAPI_3 {

    public static void main(String[] args) {

        CompletableFuture.supplyAsync(() ->{
            return 1;
        }).thenApply((val)->{
            return val+1;
        }).thenApply((val) ->{
            return val*10;
        }).thenAccept((val) ->{
            val *= 100;
            System.out.println(val);
        }).whenComplete((val, ex)->{
            if(ex == null){
                System.out.println(val);
            }
        });
        System.out.println("=========================");
        System.out.println(CompletableFuture.supplyAsync(() -> {
            return "res1";
        }).thenRun(() -> {
            System.out.println("thenRun...step");
        }).join());

        System.out.println("=========================");
        System.out.println(CompletableFuture.supplyAsync(() -> {
            return "res1";
        }).thenApply((val) -> {
            System.out.println("thenApply...step");
            return val + " res2";
        }).join());

    }
}
  1. 对计算速度进行选用
    public <U> CompletableFuture<U> applyToEither(
        CompletionStage<? extends T> other, Function<? super T, U> fn) {}
  1. 对计算结果进行合并

两个CompletableFuture任务都完成后,最终能把两个任务的结果一起交给thenCombine来处理,先完成的等待着分配其他任务

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class CompletableFutureSpeed {

    public static void main(String[] args) {
        CompletableFuture<String> play1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("player1 come in....");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "player1...";
        });
        CompletableFuture<String> play2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("player2 come in....");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "player2...";
        });
//    public  CompletableFuture applyToEither(
//        CompletionStage other, Function fn)
        // 对计算速度进行选用 -> 对对处理速度较快的CompletableFuture进行处理
        CompletableFuture<String> res = play1.applyToEither(play2, (who) -> {
            return who + " is winner!";
        });

        System.out.println(Thread.currentThread().getName() + " process\t==>" + res.join());
        System.out.println("===========================");
        thenCombine();
        System.out.println("===========================");
        thenCombine2();
    }

    public static void thenCombine(){
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t启动");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 10;
        });
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t启动");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 100;
        });
        //    public  CompletableFuture thenCombine(
        //        CompletionStage other,
        //        BiFunction fn)
        CompletableFuture<Integer> res = future1.thenCombine(future2, (x, y) -> {
            System.out.println(Thread.currentThread().getName() + "\t合并结果...");
            return x+y;
        });
        System.out.println(res.join());
    }

    public static void thenCombine2(){
        CompletableFuture<Integer> res = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t启动");
            return 10;
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t启动");
            return 20;
        }), (x, y) -> {
            System.out.println(Thread.currentThread().getName() + "\t启动");
            return x + y;
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t启动");
            return 30;
        }), (a, b) -> {
            System.out.println(Thread.currentThread().getName() + "\t启动");
            return a + b;
        });
        System.out.println(res.join());
    }
}

3. Java中的锁

3.1. 悲观锁和乐观锁

悲观锁:适合写操作多的场景,先加锁可以保证写操作时数据正确。显示的锁定之后再操作同步资源

乐观锁:认为自己在使用数据时不会有其他线程修改数据,不会添加锁

如果临界资源被没有被更新,当前线程将自己修改的数据成功写入。如果临界资源发生更新,则根据不同的实现方式执行不同的操作,比如放弃修改,重新去抢占锁等

判断规则:版本号机制version;CAS中的自旋锁(原子类的递增操作)

3.2. 线程8锁

import java.util.concurrent.TimeUnit;

/**
 * 多线程8锁
 * 1.标准访问,先打印邮件再执行发微信
 * 2.邮件先暂停4秒,先打印邮件再打印微信
 * 一个对象里面如果有多个synchronized普通方法,某一个时刻内,只能有一个线程去调用其中的一个synchronized方法,
 * 其他的线程只能等待;锁的是当前对象this(调用者),被锁定后,其他线程都不能进入到当前对象的其他synchronized方法
 * ==================================================
 * 3.新增一个普通方法,邮件先暂停4秒,先打印看电影再打印发邮件
 * 由于普通方法并没有加上synchronized上锁 并不需要和发邮件方法去抢占资源,所以可以直接调用watchMovie方法
 * ====================================================
 * 4.两个资源类,两个同步普通方法,邮件暂停4秒,先执行发微信再执行发邮件
 * sendEmail方法锁的是当前对象phone1,而phone2是另一个对象,不是同一把锁,各用各的,所以phone2的方法先执行
 * ======================================================
 * 5.两个静态同步方法,同一个资源,邮件暂停4秒,先打印邮件再打印微信
 * 6.两个静态同步方法,两个资源,邮件暂停4秒,先打印邮件再打印微信
 * 静态同步方法锁的是所在的整个类模板(类锁),加锁的对象从实例对象变成类,无论有多少资源,同一个时刻只能有一个线程去调用方法
 * ===============================================================
 * 7.1个普通同步方法1个静态同步方法,1个资源,邮件暂停4秒,先执行发微信再执行发邮件
 * 8.1个普通同步方法1个静态同步方法,2个资源,邮件暂停4秒,先执行发微信再执行发邮件
 * 普通同步方法锁的是当前new出来的实例对象(this),而静态同步方法锁的是这个类模板(.class),
 * 一个是对象锁一个是类锁,互不影响,所以微信方法执行;
 * 对于两个资源,一个实例对象也是一个新的对象锁,与类锁也不会竞争
 */
class Phone{

    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(4); // 暂停4秒钟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发送邮件...");
    }

    public static synchronized void sendWechat(){
        System.out.println("发微信...");
    }
		
    public void watchMovie(){
        System.out.println("看电影...");
    }
	
}

public class Lock8 {

    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
//        Phone phone2 = new Phone();

        new Thread(() -> {
            try{
                phone1.sendEmail();
            }catch (Exception e){
                e.printStackTrace();
            }
        }, "t1").start();
        Thread.sleep(100);

        new Thread(() -> {
            try{
              phone1.sendWechat();
//              phone1.watchMovie();
//                phone2.sendWechat();
            }catch (Exception e){
                e.printStackTrace();
            }
        }, "t2").start();

    }
}

能加对象锁,就不要用类锁。尽可能使加锁的代码块工作量小,避免在锁代码块中调用RPC方法

3.3. synchronized

具体应用在三个地方

  • 作用于实例方法,当前实例对象进行加锁,进入到同步代码之前获取当前实例的锁
  • 作用于同步代码块,对括号中的对象进行加锁
  • 作用于静态方法,当前类加锁,进入到同步代码块之前需要获取当前类锁

synchronized锁字节码分析

javac -p ..class class文件进行反编译:发现synchronized同步代码块实现是靠monitorenter和monitorexit两个指令(多一个monitorexit指令,防止出现异常不能正常释放锁)
JavaのJUC并发编程_第3张图片
javac -v ..class: 普通通方法会检查该方法的flags中的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程会先持有monitor锁,然后再执行方法内容,最后在方法执行结束时释放monitor锁
JavaのJUC并发编程_第4张图片
ACC_STATIC, ACC_SYNCHRONIZED访问标志区分该方法是否是静态同步方法
JavaのJUC并发编程_第5张图片


synchronized底层原语分析

为什么任何一个对象都可以成为一个锁?

根据OpenJDK的源码objectMonitor.java,objectMonitor.cpp,objectMonitor.hpp

由于每个对象天生就带着一个对象监视器objectMonitor,每一个被锁住的对象都会和Monitor所关联
JavaのJUC并发编程_第6张图片
其中有ObjectMonitor中有几个关键属性

_owner 指向持有ObjectMonitor对象的线程
_WaitSet 存放处与wait状态的线程队列
_EntryList 存放处于等待所block状态的线程队列
_recursions 锁的重入次数
_count 记录当前线程获取锁的次数

3.3. 公平锁和非公平锁

synchronized和Lock默认都是非公平锁,为什么默认是非公平锁?

  • 恢复挂起的线程到真正锁的获取还是有时间差的,非公平锁可以更充分的利用CPU时间片,尽量减少CPU的空闲状态时间
  • 当一个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程重新获取同步状态的概率就会变大,也就减少了线程切换的开销

总的来说非公平锁可以获得更高的吞吐量

3.4. 可重入锁(递归锁)

ReentrantLock和Synchronized均是可重入锁

同一线程外层函数获得锁之后,内层递归函数依旧能获取该锁的代码;同一个线程在外层方法获取锁时,在进入内层方法会自动获取锁。也就是说,线程可以进入任何一个他已经拥有的锁所同步着的代码块(自己可以获取自己的内部锁)

如果一个有synchronized修饰的递归方法或者代码块,在内部调用本类的其他synchronized方法,一定可以获取到锁。作用是防止死锁

synchronized原理分析

  • 每个锁对象拥有一个锁计数器count和一个指向持有该锁的线程的指针owner
  • 当执行monitorenter时,如果目标锁对象的计数器为0,则该锁没有被其他线程所持有,JVM会将该锁对象的持有线程owner参数设置为当前线程,并将计数器+1
  • 在count不为0的情况下,如果锁对象的持有线程是当前线程,那么JVM可以将其计数器+1,否则需要等待,直至持有线程释放该锁
  • 当执行monitorexit时,JVM将锁对象的计数器-1,计数器为0代表锁已被释放
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

class Lock{
    public synchronized void m1(){
        System.out.println(Thread.currentThread().getName() + "===============step1");
        m2();
        System.out.println(Thread.currentThread().getName() + "===============over...");
    }

    public synchronized void m2(){
        System.out.println(Thread.currentThread().getName() + "===============step2");
        m3();
    }
    public synchronized void m3(){
        System.out.println(Thread.currentThread().getName() + "===============step3");
    }

}
public class reentrantLockDemo {


    public static java.util.concurrent.locks.Lock  reentrantLock = new ReentrantLock();

    public static void main(String[] args) {

        final Object obj = new Object();
        new Thread(()->{
            synchronized (obj){
                System.out.println("==============1");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj){
                    System.out.println("===================2");
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj){
                        System.out.println("=======================3");
                        try {
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }, "t1").start();

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Lock lock = new Lock();
        new Thread(() ->{
            lock.m1();
        }, "t2").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("================Lock显示锁演示====================");
        new Thread(() ->{
            reentrantLock.lock();
            try{
                System.out.println(Thread.currentThread().getName() + "========step1");
                reentrantLock.lock();
                try{
                    System.out.println(Thread.currentThread().getName() + "========step2");

                }finally {
                    reentrantLock.unlock();
                }
            }finally {
                // reentrantLock的加锁和解锁必须成对出现 并且unlock方法必须在lock方法之后使用
                reentrantLock.unlock();
            }
        }, "t3").start();
    }
}

3.5. 死锁

死锁:两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉他们将无法推进下去

代码演示:

import java.util.concurrent.TimeUnit;

public class DeadLockDemo {

    public static final Object objA = new Object();
    public static final Object objB = new Object();

    public static void main(String[] args) {

        new Thread(() ->{
           synchronized (objA){
               System.out.println(Thread.currentThread().getName() + "\t自己持有"+objA+ "锁,希望获得"+ objB+"锁");
               try {
                   TimeUnit.SECONDS.sleep(1);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               synchronized (objB){
                   System.out.println(Thread.currentThread().getName() + "\t成功获得"+ objB+"锁");
               }
           }
        }, "t-1").start();

        new Thread(() ->{
            synchronized (objB){
                System.out.println(Thread.currentThread().getName() + "\t自己持有"+objB+ "锁,希望获得"+ objA+"锁");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (objA){
                    System.out.println(Thread.currentThread().getName() + "\t成功获得"+ objA+"锁");
                }
            }
        }, "t-2").start();

    }
}

解决:

windows下查看java运行程序,类似ps的查看线程的命令,在IDEA的terminal中使用

jps -l 定位哪一个线程

jstack 线程id 找到该线程的java工作栈查看
JavaのJUC并发编程_第7张图片
在cmd窗口输入jconsole通过图形化界面查看当前线程的死锁状态
JavaのJUC并发编程_第8张图片


总结
synchronized(this):指针指向monitor对象(管程或监视器对象)的起始地址,每个对象都存在一个monitor与之对应,当一个monitor被某个线程持有后,便处于锁定锁状态。在JVM中,monitor对象是由ObjectMonitor对象实习的,在JVM-hotspot源码objectMonitor.hpp文件中
JavaのJUC并发编程_第9张图片
JavaのJUC并发编程_第10张图片

锁的性质:Java中的任何对象都可以当成锁来使用,相当于给对象做个标记,对象在内存中的存储布局,包括:

  • markword:占用8字节,包括GC信息,hashcode,锁的信息
  • 类型指针class pointer:占用4字节
  • 实例数据instance data:成员变量的字节数总和,引用类型为4个字节
  • 对齐padding(保证每一个java对象都是8字节的整数倍)

添加jol-core依赖查看具体对象在内存中的存储布局

public class InstanceLock {

    private static class T{
        int m;
        long p;
        boolean b;
        String str = "str";
    }

    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        T t = new T();
        // System.out.println("创建对象时的布局:" + ClassLayout.parseInstance(obj).toPrintable());
        System.out.println("创建对象时的布局:" + ClassLayout.parseInstance(t).toPrintable());

        synchronized (t){
            System.out.println("加锁之后对象的布局: " + ClassLayout.parseInstance(t).toPrintable());
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println("解锁之后对象的布局:" + ClassLayout.parseInstance(t).toPrintable());
    }
}

打印信息发现给某个对象加锁,只会改变该对象的mark部分,相当于对mark部分做一些标记

markword部分的最后三位的状态如果为

001则处于无锁状态
101则为偏向锁状态
00则为轻量级锁,自旋锁或者无锁
10为重量级锁
11为GC标记信息(CMS过程用到的标记信息)

轻量级锁和重量级锁的区别?

JVM层面能解决的锁称之为轻量级锁,使用自旋来完成,当并发量比较高或者临界区比较复杂时,该种方式的开销较大

需要交给底层OS来完成的称为重量级锁,线程进入等待队列

偏向锁

由于JVM虚拟机有一些默认启动的线程,存在许多sync代码,这些sync代码启动时就知道肯定会存在竞争,如果使用偏向锁,就会造成偏向锁不断进行锁撤销和锁升级的操作,效率降低。默认为4s,当4s之后,再次锁该对象,则此时锁的状态是偏向锁
使用参数 -XX:BiasedLockingStartupDelay=? 可设置具体时间

你可能感兴趣的:(JUC&JVM,java,jvm,JUC,intellij-idea)