JUC并发编程(三)

JUC并发编程(三)

十二. 四大函数式接口(必须掌握)

程序员必须掌握的几种操作:

1.lambda表达式
2.链式编程
3.函数式接口
4.sream流式计算

函数式接口:只有一个方法的接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

我们把这样的接口统称为函数式接口,这样的优点在于:简化编程框架,在新版本的框架底层中大量应用.
在这里插入图片描述

1.Function函数式接口

有一个输入参数,一个输出参数
在这里插入图片描述

只要是函数式接口,就可以用lambda表达式简化
代码测试:

import java.util.function.Function;
//FunctionalInterface 函数型接口
public class Demo01 {
    public static void main(String[] args) {
        //写一个没用的接口,作用是:输出输入的值
        /*Function function = new Function<String,String>() {
            @Override
            public String apply(String str) {
                return str;
            }
        };*/
        //用lambda表达式简化
        Function<String,String> function = (str)->{return str;};
        System.out.println(function.apply("asdf"));
    }
}
---------------结果---------------
asdf

2.Predicate断定型接口

在这里插入图片描述

import java.util.function.Predicate;
//段定型接口,有一个输入参数,返回值是布尔值
public class Demo02 {
    public static void main(String[] args) {
        /*Predicate predicate = new Predicate<String>() {
            //判断字符串是否为空
            @Override
            public boolean test(String str) {
                return str.isEmpty();
            }
        };*/
        Predicate<String> predicate = (str)->{return str.isEmpty();};
        System.out.println(predicate.test(""));
    }
}
---------------结果---------------
true

3. Consumer 消费型接口

在这里插入图片描述

//Consumer 消费型接口 只有一个输入,没有输出
import java.util.function.Consumer;

public class Demo03 {
    public static void main(String[] args) {
        /*Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String str) {
                System.out.println(str);
            }
        };*/
        Consumer<String> consumer = (str)->{System.out.println(str); };
        consumer.accept("asdfghjkl");
    }
}
---------------结果---------------
asdfghjkl

4. Supplier 供给型接口

在这里插入图片描述

//Supplier 供给型接口,没有参数,只有返回值
import java.util.function.Supplier;

public class Demo04 {
    public static void main(String[] args) {
        /*Supplier<String> supplier = new Supplier<String>() {
            @Override
            public String get() {
                return "执行成功";
            }
        };*/
        Supplier<String> supplier = ()->{return "执行成功";};
        System.out.println(supplier.get());
    }
}
---------------结果---------------
执行成功

十三. Stream流式计算

什么是 Strean流式计算

大数据:存储+计算
集合、 MySQL本质就是存储东西的;
计算都应该交给流来操作!

用一个实例来演示四个四大函数式接口

import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/*题要求:一分钟内完成此题月能用一行代码实现!
  现在有5个用户!筛选
   1、ID必须是偶数
   2、年龄必须大于23岁
   3、用户名转为大写字母
   4、用户名字母倒着排序字
   5、只输出一个用户!
*/
public class Demo01 {
    public static void main(String[] args) {
        USER U1 = new USER(1, "a", 21);
        USER U2 = new USER(2, "b", 22);
        USER U3 = new USER(3, "c", 23);
        USER U4 = new USER(4, "d", 24);
        USER U5 = new USER(5, "e", 25);
        USER U6 = new USER(6, "f", 26);
        //集合就是存储
        List<USER> list = Arrays.asList(U1, U2, U3, U4, U5,U6);
        //计算交给Stream流
        list.stream()
                .filter(u->{return u.getId() % 2 == 0;})
                .filter(u->{return u.getAge() > 23;})
                .map(u->{return u.getName().toUpperCase(Locale.ROOT);})
                .sorted((u1,u2)->{return u2.compareTo(u1);})
                .limit(1)
                .forEach(System.out::println);
    }
}
---------------结果---------------
F
public class USER {
    private int id;
    private String name;
    private int age;

    public USER(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public USER() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "USER{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

十四. ForkJoin 分支合并

什么是ForkJoin
并行执行一些任务,提高效率的,前提是大量数据
常用于大数据,在大数据中会学Map Reduce (把大任务,拆成小任务)

ForkJoin的特点:工作窃取
窃取别的线程的任务来执行,提高工作效率
这里面维护的都是双端队列

import java.util.concurrent.RecursiveTask;

//求和计算的任务
//如何使用ForkJoin
//1.ForkJoinPool 通过它来执行
//2.计算任务 ForkJoinPool.excute(ForkJoinTask task)
//3.计算类要继承 ForkJoinTask
public class ForkJoinDemo01 extends RecursiveTask<Long> {

    private long start;
    private long end;
    private long temp = 10000L;//临界值

    public ForkJoinDemo01(long start, long end) {
        this.start = start;
        this.end = end;
    }

    //计算方法
    @Override
    protected Long compute() {
        if(end-start > temp){
            long middle = (start + end) / 2; //中间值
            ForkJoinDemo01 task1 = new ForkJoinDemo01(start, middle);
            task1.fork();
            ForkJoinDemo01 task2 = new ForkJoinDemo01(middle+1, end);
            task2.fork();
            return task1.join()+task2.join();


        }else{
            long sum= 0L;
            for (long i = start; i <= end; i++) {
                sum+=i;
            }
            return sum;
        }
    }
}

测试类

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //test1();//3753
        //test2();//412
        test3();//263

    }

    public static void test1(){
        long sum = 0L;
        long start = System.currentTimeMillis();
        for (Long i = 0L; i <= 10_0000_0000; i++) {
            sum+=i;
        }
        long end = System.currentTimeMillis();
        System.out.println("一共执行了:"+(end-start));
        System.out.println("结果为:"+sum);
    }

    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo01(0L, 10_0000_0000);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("一共执行了:"+(end-start));
        System.out.println("结果为:"+sum);
    }

    public static void test3(){
        long start = System.currentTimeMillis();
        //Stream 并行流
        long sum = LongStream.rangeClosed(0L, 10_0000_0000).parallel().reduce(0, Long::sum);

        long end = System.currentTimeMillis();
        System.out.println("一共执行了:"+(end-start));
        System.out.println("结果为:"+sum);
    }
}

十五. 异步回溯

Future 它设计的初衷就是:对将来的某个事件结果进行建模

实例:

//异步调用:CompletableFuture
//异步执行
//成功回调
//失败回调

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

public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //发起一个请求
        //无返回值的异步回调
        CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(2);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"runAsync=>Void");

        });
        System.out.println("1111");
        future.get();//异步回调  阻塞获取结果
    }
}
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Demo02 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //有返回值的异步回调
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{

            System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
            return 1024;
        });

        System.out.println(future.whenComplete((t, u) -> {
            System.out.println("t->" + t); //正常的返回结果
            System.out.println("u->" + u); //错误信息
        }).exceptionally((t) -> {
            System.out.println(t.getMessage());
            return 2333;
        }).get());
    }
}

十六. JMM

面试的时候,经常会问到你对Volatile的理解:

Volatile是Java虚拟机提供的轻量级的同步机制
1.保证可见性
2.不保证原子性
3.禁止指令重排

这是就要提到JMM了

什么是JMM
JMM:Java内存模型,不存在的东西,概念 约定

关于JMM的一些同步的约定
1.线程解锁前:必须把共享变量,立刻刷回主存
2.线程加锁前:必须读取主存中的最新值到工作内存中
3.加锁和解锁是同一把锁

线程:工作内存,主内存
八种操作

在这里插入图片描述

在这里插入图片描述

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
  • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
  • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
  • write  (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

JMM对这八种指令的使用,制定了如下规则:

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

问题:线程1不知道主内存的值已经被修改过了,这时候就要引出volatile的三大特性

import java.util.concurrent.TimeUnit;

public class Demo01 {
    private static int num=0;
    public static void main(String[] args) {  //main线程
        new Thread(()->{ //线程1
            while (num==0){

            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num=1;
        System.out.println(num);
    }
}
//线程会一直执行

十七. volatile

三大特性

1.保证可见性

import java.util.concurrent.TimeUnit;

public class Demo01 {
    //不加volatile 程序就会死循环
    //加volatile 可以保证可见性
    private volatile static int num=0;
    public static void main(String[] args) {  //main线程
        new Thread(()->{ //线程1  不可见 对主内存的变化是不知道的
            while (num==0){
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num=1;
        System.out.println(num);
    }
}

2.不保证原子性

要了解不保证原子性,就要先理解原子性是什么
原子性:不可分割就是原子性
线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败

//不保证原子性
public class Demo02 {
    private volatile static int num = 0;

    public static void add(){
        num++;
    }
    public static void main(String[] args) {
        //理论上结果应该是20000
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2){ //Java中有两个默认执行的线程 1.main 2.jc
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+" "+ num);
    }
}
---------------结果---------------
main 18418

如果不加lock或者synchronized,怎么样保证原子性

当我们打开这个文件的反编译文件时,发现它是不安全的
在这里插入图片描述
所以我们可以用原子类来解决原子性问题

import java.util.concurrent.atomic.AtomicInteger;

//不保证原子性
public class Demo02 {
    //原子类的Integer
    private static AtomicInteger num = new AtomicInteger();

    public static void add(){
        num.getAndIncrement();//AtomicInteger 的+1方法 底层的CAS操作
    }
    public static void main(String[] args) {
        //理论上结果应该是20000
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2){ //Java中有两个默认执行的线程 1.main 2.jc
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+" "+ num);
    }
}

这些类的底层都直接和操作系统挂钩,getAndIncrement 不只是+1 ,而是在内存中修改值
在这里插入图片描述
我们看到getAndIncrement 的源码中,有
unsafe类是一个非常特殊的存在

3.禁止指令重排

什么是指令重排:你写的程序,并不是按你写的那样执行的
源代码->编译器优化重排->指令并行重排->内存系统重排->执行

int x = 1;  //1
int y = 2;  //2
x = x + 5;  //3
y = x * x;  //4
我们所希望的执行顺序:1234  但可能的执行顺序会变成:2134 1324
可不可能是4123?

处理器在进行指令重排的时候会考虑:数据之间的依赖性!

在这里插入图片描述
volatile可以避免指令重排
内存屏障,CPU指令作用:
1.保证特定的操作执行顺序
2.可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现了可见性)
在这里插入图片描述

volatile可以保证可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排现象的产生

十八. 深入单例模式

饿汉式,DCL懒汉式

public class Hungry {
    //饿汉式单例
    //可能会浪费空间
    private byte[] date1 = new byte[1024*1024];
    private byte[] date2 = new byte[1024*1024];
    private byte[] date3 = new byte[1024*1024];
    private byte[] date4 = new byte[1024*1024];
    private Hungry(){

    }

    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance(){
        return hungry;
    }
}
public class LazyMan {
    //懒汉式单例
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+" ok");
    }
    public volatile static LazyMan lazyMan;
    //双重检测锁模式的懒汉式单例 DCL懒汉式
    public static LazyMan getInstance(){
        //加锁
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if(lazyMan==null){
                    lazyMan = new LazyMan();//不是一个原子性操作
                    /**
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把这个对象指向这个空间
                     * 可能出现的问题
                     * 线程A 执行顺序:132
                     * 线程B 执行的时候,他会认为lazyMan已经创建,不是一个null,但是lazyMan还没有完成构造
                     * 就会出现问题
                     * 解决方法,添加volatile
                     */
                }
            }
        }
        return lazyMan;
    }
    //多线程测试
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

静态内部类型的单例模式

//静态内部类
public class Holder {
    private Holder(){
        //构造器私有
    }
    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }
    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

利用反射来破坏单例

import java.lang.reflect.Constructor;

public class LazyMan {
    //懒汉式单例
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+" ok");
    }
    public volatile static LazyMan lazyMan;

    //双重检测锁模式的懒汉式单例 DCL懒汉式
    public static LazyMan getInstance(){
        //加锁
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if(lazyMan==null){
                    lazyMan = new LazyMan();//不是一个原子性操作
                    /**
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把这个对象指向这个空间
                     * 可能出现的问题
                     * 线程A 执行顺序:132
                     * 线程B 执行的时候,他会认为lazyMan已经创建,不是一个null,但是lazyMan还没有完成构造
                     * 就会出现问题
                     * 解决方法,添加volatile
                     */
                }
            }
        }
        return lazyMan;
    }
    //反射 利用反射来创建单例
    public static void main(String[] args) throws Exception {
        LazyMan instance1 = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        //根据结果发现,两个单例不相同
    }
}
---------------结果---------------
main ok
main ok
com.mnm.javaSe.JUCStudy.single.LazyMan@135fbaa4
com.mnm.javaSe.JUCStudy.single.LazyMan@45ee12a7

如何解决用反射破坏单例

import java.lang.reflect.Constructor;

public class LazyMan {
    //懒汉式单例
    private LazyMan(){
        synchronized (LazyMan.class){
            if(lazyMan!=null){
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }
    public volatile static LazyMan lazyMan;

    //双重检测锁模式的懒汉式单例 DCL懒汉式
    public static LazyMan getInstance(){
        //加锁
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if(lazyMan==null){
                    lazyMan = new LazyMan();//不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }
    //反射 利用反射来创建单例
    public static void main(String[] args) throws Exception {
        LazyMan instance1 = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        //根据结果发现,两个单例不相同
    }
}
---------------结果---------------
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.mnm.javaSe.JUCStudy.single.LazyMan.main(LazyMan.java:43)
Caused by: java.lang.RuntimeException: 不要试图使用反射破坏异常
	at com.mnm.javaSe.JUCStudy.single.LazyMan.<init>(LazyMan.java:10)
	... 5 more

如果两个对象都用反射来创建呢?

import java.lang.reflect.Constructor;

public class LazyMan {
    //懒汉式单例
    private LazyMan(){
        synchronized (LazyMan.class){
            if(lazyMan!=null){
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }
    public volatile static LazyMan lazyMan;

    //双重检测锁模式的懒汉式单例 DCL懒汉式
    public static LazyMan getInstance(){
        //加锁
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if(lazyMan==null){
                    lazyMan = new LazyMan();//不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }
    //反射 利用反射来创建单例
    public static void main(String[] args) throws Exception {
        //如果两个对象都用反射来创建呢
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance2 = declaredConstructor.newInstance();
        LazyMan instance1 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        //根据结果发现,两个单例不相同
    }
}
---------------结果---------------
com.mnm.javaSe.JUCStudy.single.LazyMan@135fbaa4
com.mnm.javaSe.JUCStudy.single.LazyMan@45ee12a7

十九. 深入理解CAS

了解CAS

Unsafe 类

在这里插入图片描述

import java.util.concurrent.atomic.AtomicInteger;

public class Demo01 {

    //CAS  compareAndSet 比较并交换
    public static void main(String[] args) {

        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //源码:public final boolean compareAndSet(int expect, int update) {
        //如果期望的值达到了,那么就更新,否则不更新
        //cas是 CPU的并发原语
        atomicInteger.compareAndSet(2020,2021);
        System.out.println(atomicInteger.get());
        atomicInteger.getAndIncrement();//相当于num++
    }
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
总结:
CAS:比较当前工作内存中的值,和主内存中的值,如果这个值是期望的,则执行操作,如果不是,则一直循环(自旋锁)

缺点:
1.循环会耗时
2.一次性只能保证一个共享变量的原子性
3.会出现ABA问题

ABA问题(狸猫换太子)

import java.util.concurrent.atomic.AtomicInteger;

public class Demo01 {
    //CAS  compareAndSet 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //对于我们平时写的SQL:乐观锁
        //源码:public final boolean compareAndSet(int expect, int update) {
        //如果期望的值达到了,那么就更新,否则不更新
        //cas是 CPU的并发原语
        //捣乱的线程
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2021, 2020));
        System.out.println(atomicInteger.get());
        //期望的线程
        System.out.println(atomicInteger.compareAndSet(2020, 6666));
        System.out.println(atomicInteger.get());
    }
}

二十. 原子引用

解决ABA问题,只需要引入原子引用,对应的思想:乐观锁.
带版本号的原子操作
在这里插入图片描述
在这里插入图片描述

注意点

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class Demo01 {
    //如果泛型是一个包装类,要注意对象的引用问题
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
    //CAS  compareAndSet 比较并交换
    public static void main(String[] args) {
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();  //获取版本号
            System.out.println("a1=>"+stamp);  //输出当前版本号

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

            System.out.println(atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("a2=>"+atomicStampedReference.getStamp()); //输出当前版本号

            System.out.println(atomicStampedReference.compareAndSet(2, 1,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("a3=>"+atomicStampedReference.getStamp()); //输出当前版本号
        },"a").start();

        //和乐观锁的原理相同
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();  //获取版本号
            System.out.println("b1=>"+stamp);

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

            System.out.println(atomicStampedReference.compareAndSet(1, 6,
                    stamp, stamp+1));

            System.out.println("b2=>"+atomicStampedReference.getStamp());
        },"b").start();
    }
}
---------------结果---------------
a1=>1
b1=>1
true
a2=>2
true
a3=>3
false
b2=>3

二十一. 各种锁的理解

1.公平锁,非公平锁

公平锁:非常公平,不能插队 先来后到
非公平锁:可以存在插队执行(默认都是非公平锁)

源码:

public ReentrantLock() {
    sync = new NonfairSync();
}
//当参数中增加一个true就会变成公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

2.可重入锁

可重入锁(递归锁)
在这里插入图片描述
synchronized锁版

public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}

class Phone{
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"->sms");
        call();//这里也有锁
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"->call");
    }
}

Lock锁版

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}

class Phone{
    Lock lock = new ReentrantLock();
    public void sms(){
        lock.lock();//锁必须配对,否则就会死在里面

        try {
            System.out.println(Thread.currentThread().getName()+"->sms");
            call();//这里也有锁
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"->call");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

3.自旋锁

spinlock 自旋锁

在这里插入图片描述

我们先定义一个锁测试

import java.util.concurrent.atomic.AtomicReference;
//自旋锁
public class SpinlockDemo {

    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    //加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"=> mylock");
        while (!atomicReference.compareAndSet(null,thread)){
        }
    }
    //解锁
    public void myUnLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"=> myUnlock");
        atomicReference.compareAndSet(thread,null);
    }
}

测试结果:

import java.util.concurrent.TimeUnit;

public class TestSpinlock {
    public static void main(String[] args) throws InterruptedException {
        /*ReentrantLock reentrantLock = new ReentrantLock();
        reentrantLock.lock();
        reentrantLock.unlock();*/
        //底层使用自旋锁cas
        SpinlockDemo lock = new SpinlockDemo();

        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"T1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"T2").start();
    }
}
---------------结果---------------
T1=> mylock
T2=> mylock
T1=> myUnlock
T2=> myUnlock

4.死锁

死锁是什么:

在这里插入图片描述
死锁测试,如何排除死锁

import java.util.concurrent.TimeUnit;

public class DeadlockDemo {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        new Thread(new MyThread(lockA,lockB),"T1").start();
        new Thread(new MyThread(lockB,lockA),"T2").start();
    }
}

class MyThread implements Runnable{

    private String lockA;
    private String lockB;

    public MyThread(String lockA,String lockB){
        this.lockA = lockA;
        this.lockB = lockB;
    }
    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName()+"lock:"+lockA+"=>get"+lockB);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+"lock:"+lockB+"=>get"+lockA);
            }
        }
    }
}
---------------结果---------------
T1lock:lockA=>getlockB
T2lock:lockB=>getlockA

如何解决问题:
1.Java自带的"jps - l" 定位进程号

在这里插入图片描述
2.使用"jstack 进程号"查看进程信息
在这里插入图片描述
面试或者工作中,排查问题:
1.日志
2.堆栈信息

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值