Day44

    科技2022-07-10  88

    HappenBefore

    指令重排:执行代码的顺序可能与编写的代码不一致,即虚拟机优化代码顺序

    happen-before即:编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段

    你写的代码很可能根本没按你期望的顺序执行,因为编译器和CPU会尝试重排指令使得代码更快的运行

    一般是各自运行互不影响,不影响最后的运行结果,系统CPU会自动尝试重排使代码运行的更快

    对于多线程肯定是有影响的 这时候就需要指令重排就行调整

    (1) 从内存器里获取指令(2)解码指令 从寄存器里拿值(3)操作 进行计算运行该(4)将结果写回到寄存器中

    数据依赖 如果两个操作访问同一个变量,且两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。

    所以编译器和处理器在重排序时。会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。

    从代码中可以看出这里有两个线程

    两个线程相互独立

    第一个线程更改初始值可能还没返回就进行了第二个线程,是以a0进去的,可返回的a确是1,这是因为CPU为了提高效率进行了重排指令,a1还没写回就进去了第二个线程,输出a的时候第一个线程的a返回了1,所以输出了a。这就导致了a0进去的出来的时候a1,这就出现了错误

    package com.sxt.others; /** * 指令重排:代码的执行顺序和预期不一致 * 目的是提高性能 */ public class HappenBefore { //变量1 private static int a=0; //变量2 private static boolean flag = false; public static void main(String[] args) throws InterruptedException { for (int i = 0; i <10 ; i++) { a=0; flag=false; //线程1 更改数据 Thread t1 = new Thread(() -> { a = 1; flag = true; }); //线程2 读取数据 Thread t2 = new Thread(() -> { if (flag) { a *= 1; } //指令重排 if (a == 0) { System.out.println("happen before->" + a); } }); t1.start(); t2.start(); //合并线程 线程的插队 t1.join(); t2.join(); } } } /** happen before->1 */

    volatile

    volatile保证线程区间变量的可见性

    volatile 是不错的机制 ,但是 volatile不能保证原子性

    如果不添加volatile,在死循环里他会一直忙的不可开交,来不及看num是否改变了,一直还在死循环里

    但添加了volatile后,变量跟改了系统会第一时间通知来取最新数据,变量num就更改了,就会跳出循环结束 这就体现了volatile数据同步的作用

    package com.sxt.others; /** * volatile用以保证数据的同步,也就是可见性 */ public class VolatileTest { private volatile static int num=0; public static void main(String[] args) throws InterruptedException { new Thread(()->{ while (num==0){//此处不要编写代码 } }).start(); Thread.sleep(1000); num=1; } }

    设计模式

    单例模式

    double-checking

    volatile

    实现单例

    package com.sxt.others; /** * DCL:单例模式:懒汉式套路基础上加入并发控制 保证在多线程环境下 对外存在一个对象 * 1、构造器私有化--->避免外部new构造器 * 2、提供私有静态属性--->存储对象的地址 * 3、提供公共的静态方法--->获取属性 */ public class DoubleCheckedLocking { //2、提供私有静态属性 private volatile static DoubleCheckedLocking instance; //没有volatile其他线程可能会访问到一个没有初始化的对象 //1、构造器私有化 private DoubleCheckedLocking() { } //3、提供公共的静态方法--->获得属性 public static DoubleCheckedLocking getInstance() { //Double Checking if (null != instance) {//避免不必要的同步 已经存在对象 return instance; } synchronized (DoubleCheckedLocking.class) { if (null == instance) { instance = new DoubleCheckedLocking(); //1、开辟空间 2、初始化对象 3、返回对象的地址给引用 } } return instance; } public static DoubleCheckedLocking getInstance1(long time) throws InterruptedException { if (null == instance) { Thread.sleep(time); instance = new DoubleCheckedLocking(); //1、开辟空间 2、初始化对象 3、返回对象的地址给引用 } return instance; } public static void main(String[] args) throws InterruptedException { // Thread t = new Thread(()->{ // System.out.println(DoubleCheckedLocking.getInstance()); // }); // t.start(); // System.out.println(DoubleCheckedLocking.getInstance()); Thread t1 = new Thread(()->{ try { System.out.println(DoubleCheckedLocking.getInstance1(500)); } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); System.out.println(DoubleCheckedLocking.getInstance1(1000)); } }

    ThreadLocal

    在多线程的环境下,每个线程都有自己的数据,一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己可以看见,不会影响其他线程

    ThreadLocal能够放一个线程级别的变量,其本身能够被多个线程共享使用,并且又能达到线程安全的目的。说白了,ThreadLocal就是想在多线程环境下去保证成员变量的安全

    常用的方法有get/set/initiaValue方法

    JDK建议ThreadLocal定义为private static

    package com.sxt.others; /** * ThreadLocal:每个线程自身的存储本地、局部区域 * get/set/initialValue */ public class ThreadLocalTest01 { // private static ThreadLocal<Integer> threadLocal=new ThreadLocal();//现在就有了一块大的存储空间 //更改初始值 /* private static ThreadLocal<Integer> threadLocal=new ThreadLocal<>(){ protected Integer initialValue(){ return 200; } };*/ private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 50); public static void main(String[] args) { //获取值 System.out.println(Thread.currentThread().getName() + "--->" + threadLocal.get()); //设置值 threadLocal.set(99); System.out.println(Thread.currentThread().getName()+"--->"+threadLocal.get()); new Thread(new MyRun()).start(); new Thread(new MyRun()).start(); new Thread(new MyRun()).start(); } public static class MyRun implements Runnable{ @Override public void run() { threadLocal.set((int)(Math.random()*99)); System.out.println(Thread.currentThread().getName()+"--->"+threadLocal.get()); } } }

    ThreadLocal:每个线程自身的数据,更改不会影响其他的线程

    package com.sxt.others; /** * ThreadLocal:每个线程自身的数据,更改不会影响其他的线程 */ public class ThreadLocalTest02 { private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1); public static void main(String[] args) { for (int i = 0; i <5 ; i++) { new Thread(new MyRun()).start(); } } public static class MyRun implements Runnable{ @Override public void run() { Integer left= threadLocal.get(); System.out.println(Thread.currentThread().getName()+"得到了--->"+left); threadLocal.set(left-1); System.out.println(Thread.currentThread().getName()+"还剩下--->"+threadLocal.get()); } } } /** Thread-0得到了--->1 Thread-1得到了--->1 Thread-3得到了--->1 Thread-2得到了--->1 Thread-1还剩下--->0 Thread-4得到了--->1 Thread-4还剩下--->0 Thread-0还剩下--->0 Thread-3还剩下--->0 Thread-2还剩下--->0 */

    ThreadLocal:分析上下文的环境 起点

    1、构造器:哪里调用就属于谁 找线程体run方法:本线程本身的 package com.sxt.others; /** * ThreadLocal:分析上下文的环境 起点 * 1、构造器:哪里调用就属于谁 找线程体 * run方法:本线程本身的 */ public class ThreadLocalTest03 { private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1); public static void main(String[] args) { new Thread(new MyRun()).start(); new Thread(new MyRun()).start(); } public static class MyRun implements Runnable{ public MyRun(){ threadLocal.set(-100); System.out.println(Thread.currentThread().getName()+"--->"+threadLocal.get()); } @Override public void run() { System.out.println(Thread.currentThread().getName()+"--->"+threadLocal.get()); } } } /** main--->-100 main--->-100 Thread-0--->1 Thread-1--->1 */

    InheritableThreadLocal:继承上下文环境的数据 拷贝一份给子线程 不是共享

    package com.sxt.others; /** * InheritableThreadLocal:继承上下文环境的数据 拷贝一份给子线程 不是共享 * 1、构造器:哪里调用就属于谁 找线程体 * run方法:本线程本身的 */ public class ThreadLocalTest04 { private static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>(); public static void main(String[] args) { threadLocal.set(2); System.out.println(Thread.currentThread().getName()+"--->"+threadLocal.get()); //线程由main线程开辟 new Thread(()->{ threadLocal.set(4); System.out.println(Thread.currentThread().getName()+"--->"+threadLocal.get()); }).start(); } }

    可重入锁

    如果某个线程试图获取一个已经有它自己持有的锁时,那么这个请求会立刻成功,并且这个锁的计数值加1,当线程退出同步代码块的时候,计数器会递减,直到计数器等于0时释放锁。如果没有可重入锁,在第二次企图获取锁时将会进入死锁状态,可重入锁随处可见。

    package com.sxt.others; /** * 可重入锁:锁可以延续使用 */ public class LockTest { public void test() { //第一次获得锁 synchronized (this) { while (true) { //第二次获得锁 synchronized (this) { System.out.println("ReentrantLock!"); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { new LockTest().test(); } }

    自写不可重入锁

    package com.sxt.others; /** * 不可重入锁:锁不可以延续使用 */ public class LockTest02 { Lock lock = new Lock(); public void a() throws InterruptedException { lock.lock(); doSomething(); lock.unlock(); } //不可重入 public void doSomething() throws InterruptedException { lock.lock(); //....... lock.unlock(); } public static void main(String[] args) throws InterruptedException { LockTest02 test = new LockTest02(); test.a(); test.doSomething(); } } //不可重入锁类 class Lock{ //是否占用 private boolean isLocked=false; //使用锁 public synchronized void lock() throws InterruptedException { while (isLocked){ wait(); } isLocked=true; } //释放锁 public synchronized void unlock(){ isLocked=false; notifyAll(); } }

    可重入锁手动实现

    了解其原理

    package com.sxt.others; /** * 可重入锁:锁可以延续使用+计数器 */ public class LockTest03 { ReentrantLock relock = new ReentrantLock(); public void a() throws InterruptedException { relock.lock(); System.out.println(relock.getHoldCount()); doSomething(); relock.unlock(); System.out.println(relock.getHoldCount()); } //可重入 public void doSomething() throws InterruptedException { relock.lock(); System.out.println(relock.getHoldCount()); //....... relock.unlock(); System.out.println(relock.getHoldCount()); } public static void main(String[] args) throws InterruptedException { LockTest03 test = new LockTest03(); test.a(); Thread.sleep(1000); System.out.println(test.relock.getHoldCount()); } } //可重入锁类 class ReentrantLock{ //是否占用 private boolean isLocked=false; private Thread lockedBy=null;//存储线程 private int holdCount=0; //使用锁 public synchronized void lock() throws InterruptedException { Thread t=Thread.currentThread(); while (isLocked&&lockedBy!=t){ wait(); } isLocked=true; lockedBy=t; holdCount++; } //释放锁 public synchronized void unlock() { if (Thread.currentThread() == lockedBy) holdCount--; if (holdCount == 0) { isLocked = false; notifyAll(); lockedBy = null; } } public int getHoldCount() { return holdCount; } }
    CAS

    锁分为两类

    悲观锁:synchronized是独占锁即悲观锁,会导致其他所有有需要锁的线程挂起,等待持有锁的线程释放锁乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止

    Compare and Swap 比较并交换:

    有三个值 A—V----B

    A和V进行比较,如果A=V,就把A换成B,否则交换失败

    ABA问题

    虽然最后还是A,但被更改过,需要利用代码解决

    CAS是属于内部方法,是CPU实现的,我们不能进行更改

    有关Atomic的里面都有涉及CAS

    package com.sxt.others; import java.util.concurrent.atomic.AtomicInteger; /** * CAS比较并交换 */ public class CAS { //库存 private static AtomicInteger stock=new AtomicInteger(5); public static void main(String[] args) { for (int i = 0; i <5 ; i++) { new Thread(()->{ //模拟网络延时 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Integer left=stock.decrementAndGet(); if (left<1){ System.out.println("抢完了"); return; } System.out.println(Thread.currentThread().getName()+"抢了一个商品"+"--->还剩"+left); }).start(); } } }
    Processed: 0.019, SQL: 8