超人自学手册:JUC并发编程

JUC并发编程

    • 1. 线程与进程
    • 2. Lock锁
    • 3. 生产者和消费者问题
      • 3.1 Synchronized(wait, notify)
      • 3.2 Lock(Condition对象)
      • 3.3 Condition实现精准唤醒
    • 4. 八种锁的场景
    • 5. 集合类不安全
      • 5.1 CopyOnWriteArrayList
      • 5.2 CopyOnWriteArraySet
      • 5.3 ConcurrentHashMap
    • 10 Callable
    • 11 常用的辅助类
    • 12 读写锁
    • 13 阻塞队列
    • 14 7大参数及自定义线程池
    • 15 四大函数式接口(必需掌握)
      • 15.1 Function 函数式接口
      • 15.2 Predicate断定型接口
      • 15.3 消费型接口
      • 15.4 供给型接口
    • 16 Stream流式计算
    • 17 ForkJoin
    • 18 异步回调
    • 19 理解JMM
    • 20 Volatile
    • 21 指令重排详解
    • 22 深入理解单例模式
    • 23 深入理解CAS
    • 24 原子操作
    • 25 各种锁的理解

多线程进阶=》JUC并发编程
java.util.concurrent工具包
业务:普通的线程代码Thread
Runnable没有返回值、效率相比入Callable相对较低

1. 线程与进程

进程:一个程序。一个进程往往可以包含多个线程,至少包含一个
java默认有几个线程?2个:main、GC
线程:开启了一个程序,打字,自动保存(线程负责)
对于java而言:Thread、Runnable、Callable
Java可以开启线程吗?不能
并发:多线程操作同一个资源
CPU一核
并行:多个人一起行走
CPU多核

    public static void main(String[] args) {
//        获取cpu的核数
//        CPU密集型,IO密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }

线程有几个状态:6个
new新生
runnable运行
blocked阻塞
waiting等待,死死等待
timed_waiting超时等待
terminated终止

wait、sleep的区别
1.来自不同的类
wait是Object类
sleep是Thread类
TimeUnit.Days.sleep(1)休眠一天
2.关于锁的释放
wait会释放锁,sleep睡觉了,抱着锁睡觉,不会释放
3.使用的范围是不同的
wait必须在同步代码块中
sleep可以在任何地方睡
4.是否需要捕获异常
wait也需要
sleep需要捕获异常

2. Lock锁

传统Synchronized 本质:队列、锁
可重入锁(常用,ReentrantLock()),读锁,写锁
Lock的使用:

  1. new ReentrantLock();
  2. lock.lock();//加锁
  3. finally=> lock.unlock;//解锁

公平锁:十分公平:可以先来后到
非公平锁:十分不公平:可以插队(默认)

Synchronized和lock的区别

  1. Synchronized内置的java关键字,Lock是一个Java类
  2. Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
  3. Synchronized会自动释放锁,lock必须要手动释放锁!如果不释放锁,死锁
  4. Synchronized线程1(获得锁,阻塞),线程2(等待);Lock锁就不一定会等待下去。
  5. Synchronized 可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置)。
  6. Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的代码。

锁是什么 ,如何判断锁的什么

3. 生产者和消费者问题

3.1 Synchronized(wait, notify)

if在多线程中会出现虚假唤醒的问题,while判断可以防止虚假唤醒

synchronized (obj){
	while(<condition does not hold>)
		obj.wait(timeout);
		...//Perform action appropriate to condition
}

3.2 Lock(Condition对象)

private Lock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
condition.await();//等待
condition.signal();//唤醒

3.3 Condition实现精准唤醒

class Data3{//资源类Lock
	private Lock lock = new ReentrantLock();
	private Condition condition1 = lock.newCondition();
	private Condition condition2 = lock.newCondition();
	private Condition condition3 = lock.newCondition();
	private int number = 1;//1A 2B 3C
	public void printA(){
		lock.lock();
		try{
			//业务, 判断->执行->通知
			while(number != 1){
				//等待
				condition1.await();
				
			}
			System.out.println(Thread.currentThread().getName()+"=>AAAAAA");
			//唤醒,唤醒指定的人,B
			condition2.signal();
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
}
	public void printB(){...//唤醒,唤醒指定的人,C}
	public void printC(){...//唤醒,唤醒指定的人,A}

4. 八种锁的场景

1.标准情况下,两个线程先打印 发短信

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

        new Thread(()->{
            phone.sendSms();
        }, "A").start();

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

        new Thread(()->{
            phone.call();
        }, "B").start();
    }

}

class Phone{
    public synchronized void sendSms(){
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

2.sendSms延迟4秒,两个线程先打印 发短信
对于场景1和场景2,synchronized锁的对象是方法的调用者!两个方法用的是同一个锁,谁先拿到谁执行!

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

        new Thread(()->{
            phone.sendSms();
        }, "A").start();

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

        new Thread(()->{
            phone.call();
        }, "B").start();
    }

}

class Phone{
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

3.增加一个普通方法后,先执行普通方法

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

        new Thread(()->{
            phone.sendSms();
        }, "A").start();

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

        new Thread(()->{
            phone.hello();
        }, "B").start();
    }

}

class Phone2{
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }

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

4.两个对象,两个同步方法,两把锁
结果:
打电话
发短信

/**
 * 4.两个对象,两个同步方法,两把锁
 */
public class test2 {
    public static void main(String[] args) {
        //两个对象
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();

        new Thread(()->{
            phone1.sendSms();
        }, "A").start();

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

        new Thread(()->{
            phone2.call();
        }, "B").start();
    }

}

class Phone2{
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }

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

5.增加两个静态的同步方法
结果:
发短信
打电话

/**
 * 5.增加两个静态的同步方法
 */
public class test3 {
    public static void main(String[] args) {
        //1个对象
        Phone2 phone = new Phone2();

        new Thread(()->{
            phone.sendSms();
        }, "A").start();

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

        new Thread(()->{
            phone.call();
        }, "B").start();
    }

}

class Phone3{
    // synchronized 锁的对象是方法的调用者
    //static 静态方法
    //类一加载就有了,锁的是Class对象
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public static synchronized void call(){
        System.out.println("打电话");
    }
    
}

6.两个对象,增加两个静态同步方法
锁的是Class,两个对象的Class模版只有一个,static
结果:
发短信
打电话

/**
 * 6.两个对象,增加两个静态同步方法
 */
public class test3 {
    public static void main(String[] args) {
        //2个对象
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();

        new Thread(()->{
            phone1.sendSms();
        }, "A").start();

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

        new Thread(()->{
            phone2.call();
        }, "B").start();
    }

}

class Phone3{
    // synchronized 锁的对象是方法的调用者
    //static 静态方法
    //类一加载就有了,锁的是Class对象
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public static synchronized void call(){
        System.out.println("打电话");
    }

}

7.一个静态同步方法,一个普通同步方法
静态方法锁的Class模版,
普通同步方法锁的是对象
结果:
打电话
发短信

/**
 * 7.1个静态同步方法,一个普通同步方法
 */
public class test4 {
    public static void main(String[] args) {
        //1个对象
        Phone4 phone = new Phone4();

        new Thread(()->{
            phone.sendSms();
        }, "A").start();

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

        new Thread(()->{
            phone.call();
        }, "B").start();
    }

}

class Phone4{
    // synchronized 锁的对象是方法的调用者
    //static 静态方法
    //类一加载就有了,锁的是Class对象
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public static void call(){
        System.out.println("打电话");
    }

}

8.一个静态同步方法,一个普通同步方法,2个对象
结果:打电话 发短信

/**
 * 8.1个静态同步方法,一个普通同步方法,2个对象
 */
public class test4 {
    public static void main(String[] args) {
        //2个对象
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();

        new Thread(()->{
            phone1.sendSms();
        }, "A").start();

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

        new Thread(()->{
            phone2.call();
        }, "B").start();
    }

}

class Phone4{
    // synchronized 锁的对象是方法的调用者
    //static 静态方法
    //类一加载就有了,锁的是Class对象
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public static void call(){
        System.out.println("打电话");
    }

}

总结
new 出来的具体一个对象
static 是Class模版

5. 集合类不安全

5.1 CopyOnWriteArrayList

并发下,ArrayList不安全,Synchronized;
多线程会出现并发修改异常java.util.ConcurrentModificationException
解决方案:

  1. Vector
List<String> list = new Vector<>();
  1. synchronizedList
List<String> list = Collection.synchronizedList(new ArrayList<>());
  1. CopyOnWrite写入时复制,COW 计算机程序设计领域的一种优化策略

多个线程调用 的时候,list,读取的时候,固定,写入的时候避免覆盖,造成数据问题
读写分离
CopyOnWriteArrayList比Vector厉害在哪里?

List<String> list = new CopyOnWriteArrayList<>();

测试代码示例:

public class test5 {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }

}

5.2 CopyOnWriteArraySet

解决方案:

  1. Collections.synchronizedSet
Set<String> set = Collections.synchronizedSet(new HashSet<>());
  1. CopyOnWriteArraySet
Set<String> set = new CopyOnWriteArraySet<>();

HashSet的底层什么?
Map

5.3 ConcurrentHashMap

ConcurrentHashMap是采用分段锁的技术!线程安全!

public class MapTest {
    public static void main(String[] args) {
        //默认等价于什么?new HashMap<>(16,0.75);
        Map<String, String> map = new ConcurrentHashMap<>();
        //加载因子0.75,初始化容量16
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

10 Callable

Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,A Runnable不返回结果,也不能抛出被检查的异常。

  1. 可以有返回值
  2. 可以抛出异常
  3. 方法不同,run()/ call()

代码测试:

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        new Thread(new MyThread()).start();
        MyThread myThread=new MyThread();
        FutureTask futureTask=new FutureTask(myThread);
        new Thread(futureTask,"A").start();//怎么启动Callable,结果有缓存
        System.out.println(futureTask.get());//get方法可能会产生阻塞
    }
}
//Callable的泛型代表方法的返回值
class MyThread implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("call()");
        return "Callable";
    }
}

可能的缺点:

  • 有缓存
  • 结果可能需要等待,会阻塞!

11 常用的辅助类

  1. CountDownLatch 减法记数器

原理:
countDownLatch.countDown();//数量-1
countDownLatch.await();//等待计数器归0然后再向下执行
每次有线程调用countDown数量-1,假设计数器归0,才会执行await会的代码

  1. CyclicBarrier 加法计数器
  2. Semaphore 信号量

原理
semaphore.acquire();获取,假设已经满了,等待被释放为止!
semaphore.release();释放,会将当前的信号量释放+1,然后唤醒等待的线程
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!

12 读写锁

13 阻塞队列

14 7大参数及自定义线程池

15 四大函数式接口(必需掌握)

对于码农来说4个很重要的点:lambda表达式、链式编程、函数式接口 、Stream流式计算
函数式接口:只有一个方法的接口

@FunctionalInterface
public interface Runnable{
    public abstract void run();
}
//超级多FunctionalInterface
//简化编程模型,在新版本的框架底层大量应用!
//foreach(消费者类的函数式接口) 

15.1 Function 函数式接口

都可以用lambda表达式简化

/**
 * Function 函数式接口,有一个输入参数,有一个输出
 * 只要是 函数型接口可以 用lambda表达式简化
 */
public class test1 {
    public static void main(String[] args) {
    //方式1:
//        Function function = new Function() {
//            @Override
//            public String apply(String str) {
//                return str;
//            }
//        };
	//方式2:
        Function<String, String> function = (str)->{return str;};
        System.out.println(function.apply("你好"));
    }
}

15.2 Predicate断定型接口

/**
 * 断定型接口:有一个输入参数,返回值只能是布尔值
 */
public class test2 {
    public static void main(String[] args) {
        //判断字符串是否为空
        //方式1:
//        Predicate predicate = new Predicate() {
//            @Override
//            public boolean test(String s) {
//                return s.isEmpty();
//            }
//        };
	//方式2:
        Predicate<String> predicate = (str)->{return str.isEmpty();};
        System.out.println(predicate.test(""));
    }
}

15.3 消费型接口

/**
 * Consumer 消费型 接口:只有输入,没有返回值
 */
public class test3 {
    public static void main(String[] args) {
    //方式1:
//        Consumer consumer = new Consumer() {
//            @Override
//            public void accept(String str) {
//                System.out.println(str);
//            }
//        };
	//方式2:
        Consumer<String> consumer = (str)->{System.out.println(str);};
        consumer.accept("打印字符串");
    }
}

15.4 供给型接口

/**
 * Supplier 供给型 接口 没有参数,只有返回值
 */
public class test4 {
    public static void main(String[] args) {
     //方式1:
//        Supplier supplier= new Supplier() {
//            @Override
//            public Integer get() {
//                System.out.println("get()");
//                return 1024;
//            }
//        };
	 //方式2:
        Supplier supplier= ()->{return 1024;};
        System.out.println(supplier.get());
    }
}

16 Stream流式计算

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

public class test5 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("a");
        list.add("a");
        list.add("b");
        //计算交给Stream流
        list.stream()
                .filter(u->{return u.equals("a");})
                .map(u->{return u.toUpperCase();})
                .limit(1)
                .forEach(System.out::println);
    }
}

另一个例子:
将list转换map

//声明一个List集合
List<Person> list = new ArrayList();  
        list.add(new Person("1001", "小A"));  
        list.add(new Person("1002", "小B"));  
        list.add(new Person("1003", "小C"));
        System.out.println(list);
//将list转换map
//1.重复时用后面的value 覆盖前面的value
Map<String, String> map = list.stream().collect(Collectors.toMap(Person::getId, Person::getName,(key1 , key2)-> key2 ));
System.out.println(map); 
//2.重复时将前面的value 和后面的value拼接起来;
Map<String, String> map = list.stream().collect(Collectors.toMap(Person::getId, Person::getName,(key1 , key2)-> key1+","+key2 ));
System.out.println(map);

17 ForkJoin

中文:分支合并
特点:工作窃取,提高工作效率,不让线程等待
这个里面维护的都是双端队列
求和计算的任务有两种方法:

  1. ForkJoin
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

/**
 * 求和计算的任务
 * 3000 6000(ForkJoin) 9000(Stream并行流)
 * 如何使用forkJoin
 * 1.forkJoinPool通过它来执行
 * 2.计算任务forkJoinPool.execute(ForkJoinTask task)
 * 3.计算类继承RecursiveTask
 * */
public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start;
    private Long end;
    private Long temp=100000L;

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if((end-start)<temp){
            Long sum=0L;
            for (Long i = start; i < end; i++) {
                sum+=i;
            }
            return sum;
        }else{
            //分并计算 forkjoin 递归
            long middle = (start + end) / 2;//中间值
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork();//拆分任务,把任务压入线程队列
            ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
            task2.fork();
            return task1.join()+ task2.join();
        }
    }
}
  1. Stream并行流等较高级的方法
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
/*
* 同一个任务,别人
* */
//3000 6000(ForkJoin) 9000(Stream并行流)
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //test1();//18515
        //test2();//16583
        test3();//1565
    }
    public static void test1(){
        long start=System.currentTimeMillis();
        Long sum=0L;
        for (Long i = 1L; i <= 10_0000_0000; i++) {
            sum+=i;
        }
        long end=System.currentTimeMillis();
        System.out.println("sum="+sum +"时间:"+(end-start));
    }
    public static void test2() throws ExecutionException, InterruptedException {
        long start=System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> forkJoinDemo = new ForkJoinDemo(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinDemo);
        Long sum = submit.get();
        long end=System.currentTimeMillis();
        System.out.println("sum="+sum+"时间:"+(end-start));
    }
    public static void test3(){
        long start=System.currentTimeMillis();
        //Stream并行流
        Long sum=LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0,Long::sum);
        long end=System.currentTimeMillis();
        System.out.println("sum="+sum+"时间:"+(end-start));
    }
}

18 异步回调

Future设计的初衷,对将来的某个事件的结果进行建模
可以想象成Ajax

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

//异步调用:CompletableFuture
/**
 * 异步执行
 * 成功回调
 * 失败回调
 * */
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //没有返回值的runAsync异步回调
//        CompletableFuture completableFuture = CompletableFuture.runAsync(()->{
//            try {
//                TimeUnit.SECONDS.sleep(2);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName()+"runAsync"+":Void");
//        });
//        System.out.println("1111111");
//        completableFuture.get();//获取阻塞执行结果
        //有返回值的异步回调 supplyAsync 异步回调
        //ajax:成功和失败的回调
        //返回的错误信息;
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"supplyAsync"+":Integer");
            return 1024;
        });
        System.out.println(completableFuture.whenComplete((t, u) -> {
            //t:正常的返回结果
            //u:错误信息
            System.out.println(t + "\t" + u);
        }).exceptionally((e) -> {
            e.getMessage();//打印错误信息
            return 233;// 可以获取到错误的返回结果
        }).get());
    }
}

19 理解JMM

Volatile是Java虚拟机提供轻量级的同步机制

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

JMM 是java内存模型,不存在的东西,是一种概念或者是约定
关于JMM的一些同步的约定

  • 线程解锁前,必须把共享变量立刻刷回主存!
  • 线程加锁前,必须读取主存中的最新值到工作内存中!
  • 加锁和解锁是同一把锁!

8种操作
内存交互操作有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操作之前,必须把此变量同步回主内存

20 Volatile

  1. 保证可见性
  2. 不保证原子性

如果不可lock和Synchronized,怎么保证原子性呢?
使用原子类作为volatile的补充,解决原子性问题

    //volatile  不保证原子性
    //原子类的Integer
    private volatile static AtomicInteger num=new AtomicInteger(0);

这些类的底层都直接和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在!

21 指令重排详解

指令重排:写的程序,计算机并不是按照你写的顺序执行的。
源代码–>编译器优化的重排–>指令并行也可能会得排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性

Volatile是可以保证可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!在单例模式使用最多

22 深入理解单例模式

点击这里,可以参考笔者的另外一篇设计模式的博客中单例模式的解析
枚举有参构造方法能够解决单例模式的不安全

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

//enum 是一个什么?本身也是一个Class类
public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle instance1 = EnumSingle.INSTANCE;

        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle1 = declaredConstructor.newInstance();
        EnumSingle enumSingle2 = declaredConstructor.newInstance();
        System.out.println(enumSingle1);
        System.out.println(enumSingle2);

    }
}

23 深入理解CAS

CAS是Compare And Set的一个简称,如下理解:

  1. 已知当前内存里面的值current和预期要修改成的值new传入

  2. 内存中AtomicInteger对象地址对应的真实值(因为有可能别修改)real与current对比,相等表示real未被修改过,是“安全”的,将new赋给real结束然后返回;不相等说明real已经被修改,结束并重新执行1直到修改成功

优点:
CAS相比Synchronized,避免了锁的使用,总体性能比Synchronized高很多。
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环
缺点:

  • 由于底层是自旋锁,它的旋转会浪费时间!
  • 一次只能保证一个共享变量的原子性
  • ABA问题
    ABA问题的样例代码:
import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {
    //CAS  compareAndSet比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger=new AtomicInteger(2020);

        //期望,更新
        //    public final boolean compareAndSet(int expect, int update)
        //如果我期望的值达到了,那么就更新,否则就不更新,CAS 是CPU的并发原语!
        //==============捣乱的线程==============
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
        //atomicInteger.getAndIncrement();

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

24 原子操作

解决ABA问题,引入原子引用!对应的思想:乐观锁!也就是带版本号的原子操作

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

public class CASDemo {
    //CAS  compareAndSet比较并交换
    public static void main(String[] args) {
        //AtomicInteger atomicInteger=new AtomicInteger(2020);

        //AtomicStampedReference注意,如果泛型是一个包装类,注意对象的引用问题
        //正常在业务操作,这里面比较的都是一个人对象
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);

        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();//获得版本号
            System.out.println("A1=>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(1,2,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println("A2=>"+atomicStampedReference.getStamp());
            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();
            }
            atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1);
            System.out.println("B2=>" + atomicStampedReference.getStamp());
        },"B").start();
    }
}

25 各种锁的理解

  1. 公平锁、非公平锁
    公平锁:非常公平,不能插队,必须先来后到!
    非平锁:非常不公平,可以插队(默认都是非公平的)
	//1. 非公平
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    
	//2. 如果想设置公平锁(使用Lock锁的重载方法)
	//Lock lock=new ReentrantLock(true);
	//公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
  1. 可重入锁
    可重入锁(递归锁)
    也就是大锁包小锁,拿到了外面的锁之后,就可以拿到里面的锁,自动获得。

synchronized形式的可重入锁

//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 Demo02 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();


    }
}
class Phone2{
    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();//细节问题
        //lock锁必须配对一把钥匙,否则就会死在里面
        try {
            System.out.println(Thread.currentThread().getName()+"call");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
  1. 自旋锁
    也就是上面介绍的CAS, 不断的尝试,直到成功为止

自定义自旋锁

import java.util.concurrent.atomic.AtomicReference;

/**
 * 自旋锁
 * */
public class SpinlockDemo {
    //Thread null
    AtomicReference<Thread> atomicReference=new AtomicReference<>();
    //加锁
    public void myLock(){
        Thread thread=Thread.currentThread();
        System.out.println(thread.getName()+"==>"+"myLock");
        //自旋锁
        while (!atomicReference.compareAndSet(null,thread)){

        }
    }
    //解锁
    public void myUnLock(){
        Thread thread=Thread.currentThread();
        System.out.println(thread.getName()+"==>"+"myUnLock");
        atomicReference.compareAndSet(thread,null);
    }
}

使用自旋锁

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

public class TestSpinLock {
    public static void main(String[] args) {
        //底层使用的自旋锁
        SpinlockDemo spinlockDemo=new SpinlockDemo();

        new Thread(()->{
            spinlockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlockDemo.myUnLock();
            }

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

        },"T2").start();
    }
}
  1. 死锁
    死锁见下面博客
    https://blog.csdn.net/guaiguaihenguai/article/details/80303835
    解决死锁的问题除了看日志之外,还可以看堆栈信息:
    使用jps -l定位进程号
    使用jstack进程号查看堆栈信息,找到死锁问题

你可能感兴趣的:(后端,java,开发语言,后端)