【无标题】

JMM三大核心

重排序

    • JVM重排
    • CPU指令重排序
    • 内存重排序:实际是可见性问题

可见性

  • voliate来保证可见性和重排序 voliate不适用于a++,不保证原子性
    • 两个作用:
      • 可见性。读一个voliate变量之前,需要先相应的本地缓存失效,必须到主存读取最新值,写一个voliate时会立即刷入到主内存
      • 禁止指令重排序优化:解决单例双重锁乱序问题

voliate只能保证它之前的操作都被后面看到

适用场景:

  • 多个线程共享,其中一个线程改变了该值,其他线程可以立即得到这个值
  • 是无锁的 不能替代synchronized
  • 只能用于属性,不具备原子性
  • 提供了可见性
  • 提供了Happes-before保证
  • 对long和double赋值是原子的

原子性

什么是原子性?

一系列的操作,要么全部执行成功,要么全部不执行,不会出现执行一半的情况,是不可分割的

long和double是每个32位写一次。在64位上是原子的

JMM应用实例

单例模式的8种写法,单例和并发的关系

为什么需要单例?

  • 一个对象很耗资源,而且不变动,没必要每次都新建,节省内存和计算
  • 保证结果正确
  • 方便管理

适用场景:

  1. 无状态的工具类:比如日志工具类
  2. 全局信息类

单例模式


/**
 * 饿汉式
 */
public class Singleton1 {

    private final static Singleton1 INSTANCE = new Singleton1();

    private Singleton1() {

    }

    public Singleton1 getInstance() {
        return INSTANCE;
    }
}
public class Singleton2 {

    private final static Singleton2 INSTANCE;

    static {
        INSTANCE = new Singleton2();
    }

    private Singleton2() {

    }

    public Singleton2 getInstance() {   
        return INSTANCE;
    }


}
private static Singleton3 INSTANCE = null;

/**
* 懒汉式  (线程不安全)
**/
    private Singleton3() {
    }
    

    public Singleton3 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton3();
        }
        return INSTANCE;
    }

推荐用

  private static Singleton4 INSTANCE = null;

    private Singleton4() {
    }

 /**
     * 加synchronized版本
     * @return
     */
    public synchronized Singleton4 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton4();
        }
        return INSTANCE;
    }

优点:线程安全,延迟加载,效率较高

为什么要DCL:

  • 线程安全
  • 单check行不行?
  • 性能问题?
  • 为什么需要voliate?
    • 新建对象实际上有三个步骤:
      • 分配内存空间
      • 初始化对象
      • 将内存空间的地址赋值给对应的引用

(2)(3)会被处理器优化,发生重排序

这时有可能其他线程判断INSTANCE == null会判断不是null,但是实际使用的时候INSTANCE.属性可能是null

这里要思考一个问题?

为什么synchroized可以保证可见性,重排序,原子性,那为什么 INSTANCE = new Singleton5();还是有重排序的问题?还需要加voliate

sync能保证如下代码,按;分割的语句禁止重排序,但是比如INSTANCE = new Singleton5();这一个语句多个指令。sync无法禁止这种重排序。但是voliate是可以的,所以需要加上voliate


    /**
     * 双重检查 推荐使用
     *
     * @return
     */
    public Singleton5 getINSTANCE() {
        if (INSTANCE == null) {
            synchronized (Singleton5.class) {
                int a = 2;
                int b = 3;
                int c = 3;
                if (INSTANCE == null) {
                    INSTANCE = new Singleton5();
                }
            }
        }
        return INSTANCE;
    }
/**
     * 静态内部类
     */
    private Singleton6() {

    }

    private static class SingletonInstance {
        private static final Singleton6 INSTANCE = new Singleton6();
    }


    public static Singleton6 getInstance() {
        return SingletonInstance.INSTANCE;
    }

public enum Singleton8 {
    /**
     * 枚举单例
     */
    INSTANCE;
    
}

饿饿汉:简单,但是没有lazy loading

懒汉:有线程安全问题

静态内部类:可用

双重检查:面试用

枚举:实际使用最佳

用哪种单例的实现方案最好?

枚举最好。

写法简单,线程安全有保障,枚举是特殊类,枚举会被编译长final Class,实际是静态对象,同样也是懒加载,避免反序列化破坏单例

如果一开始要加载很多资源,应该使用懒加载

饿汉式如果是对象的创建需要配置文件就不适用

关于可见性的问题

for(;;)和while是有区别的

 int a = 1;
    int b = 2;

    void change() {
        a = 3;
        b = a;
    }
// 可以尝试在b=a后面加c=b  会发现c的值是不固定的
    void print() {
        System.out.println("b=" + b + ",a=" + a);
    }

    public static void main(String[] args) throws InterruptedException {
        // 如果使用while  是有可能出现b=3,a=1的
        for (; ; ) {
            ThreadVisi threadVisi = new ThreadVisi();
            new Thread(() -> {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                threadVisi.change();

            }).start();
            new Thread(() -> {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                threadVisi.print();
            }).start();
        }
    }

private static int x = 0, y = 0;
    private static int a = 0, b = 0;


    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for (; ; ) {
            i++;
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            CountDownLatch count = new CountDownLatch(1);
            Thread thread = new Thread(() -> {
                try {
                    count.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                a = 1;
                x = b;
            });


            Thread thread1 = new Thread(() -> {
                try {
                    count.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                b = 1;
                y = a;
            });
            thread1.start();
            thread.start();
            count.countDown();

            thread1.join();
            thread.join();
            String result = "第" + i + "次";
            if (x == 0 && y == 0) {
                System.out.println(result + "x=" + x + ",y=" + y);
                break;
            }
            System.out.println(result + "x=" + x + ",y=" + y);
        }

    }

重排序的好处:

  • 提高响应速度

JMM三大核心

重排序

    • JVM重排
    • CPU指令重排序
    • 内存重排序:实际是可见性问题

可见性

  • voliate来保证可见性和重排序 voliate不适用于a++,不保证原子性
    • 两个作用:
      • 可见性。读一个voliate变量之前,需要先相应的本地缓存失效,必须到主存读取最新值,写一个voliate时会立即刷入到主内存
      • 禁止指令重排序优化:解决单例双重锁乱序问题

voliate只能保证它之前的操作都被后面看到

适用场景:

  • 多个线程共享,其中一个线程改变了该值,其他线程可以立即得到这个值
  • 是无锁的 不能替代synchronized
  • 只能用于属性,不具备原子性
  • 提供了可见性
  • 提供了Happes-before保证
  • 对long和double赋值是原子的

原子性

什么是原子性?

一系列的操作,要么全部执行成功,要么全部不执行,不会出现执行一半的情况,是不可分割的

long和double是每个32位写一次。在64位上是原子的

JMM应用实例

单例模式的8种写法,单例和并发的关系

为什么需要单例?

  • 一个对象很耗资源,而且不变动,没必要每次都新建,节省内存和计算
  • 保证结果正确
  • 方便管理

适用场景:

  1. 无状态的工具类:比如日志工具类
  2. 全局信息类

单例模式


/**
 * 饿汉式
 */
public class Singleton1 {

    private final static Singleton1 INSTANCE = new Singleton1();

    private Singleton1() {

    }

    public Singleton1 getInstance() {
        return INSTANCE;
    }
}
public class Singleton2 {

    private final static Singleton2 INSTANCE;

    static {
        INSTANCE = new Singleton2();
    }

    private Singleton2() {

    }

    public Singleton2 getInstance() {   
        return INSTANCE;
    }


}
private static Singleton3 INSTANCE = null;

/**
* 懒汉式  (线程不安全)
**/
    private Singleton3() {
    }
    

    public Singleton3 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton3();
        }
        return INSTANCE;
    }

推荐用

  private static Singleton4 INSTANCE = null;

    private Singleton4() {
    }

 /**
     * 加synchronized版本
     * @return
     */
    public synchronized Singleton4 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton4();
        }
        return INSTANCE;
    }

优点:线程安全,延迟加载,效率较高

为什么要DCL:

  • 线程安全
  • 单check行不行?
  • 性能问题?
  • 为什么需要voliate?
    • 新建对象实际上有三个步骤:
      • 分配内存空间
      • 初始化对象
      • 将内存空间的地址赋值给对应的引用

(2)(3)会被处理器优化,发生重排序

这时有可能其他线程判断INSTANCE == null会判断不是null,但是实际使用的时候INSTANCE.属性可能是null

这里要思考一个问题?

为什么synchroized可以保证可见性,重排序,原子性,那为什么 INSTANCE = new Singleton5();还是有重排序的问题?还需要加voliate

sync能保证如下代码,按;分割的语句禁止重排序,但是比如INSTANCE = new Singleton5();这一个语句多个指令。sync无法禁止这种重排序。但是voliate是可以的,所以需要加上voliate


    /**
     * 双重检查 推荐使用
     *
     * @return
     */
    public Singleton5 getINSTANCE() {
        if (INSTANCE == null) {
            synchronized (Singleton5.class) {
                int a = 2;
                int b = 3;
                int c = 3;
                if (INSTANCE == null) {
                    INSTANCE = new Singleton5();
                }
            }
        }
        return INSTANCE;
    }
/**
     * 静态内部类
     */
    private Singleton6() {

    }

    private static class SingletonInstance {
        private static final Singleton6 INSTANCE = new Singleton6();
    }


    public static Singleton6 getInstance() {
        return SingletonInstance.INSTANCE;
    }

public enum Singleton8 {
    /**
     * 枚举单例
     */
    INSTANCE;
    
}

饿饿汉:简单,但是没有lazy loading

懒汉:有线程安全问题

静态内部类:可用

双重检查:面试用

枚举:实际使用最佳

用哪种单例的实现方案最好?

枚举最好。

写法简单,线程安全有保障,枚举是特殊类,枚举会被编译长final Class,实际是静态对象,同样也是懒加载,避免反序列化破坏单例

如果一开始要加载很多资源,应该使用懒加载

饿汉式如果是对象的创建需要配置文件就不适用

关于可见性的问题

for(;;)和while是有区别的

 int a = 1;
    int b = 2;

    void change() {
        a = 3;
        b = a;
    }
// 可以尝试在b=a后面加c=b  会发现c的值是不固定的
    void print() {
        System.out.println("b=" + b + ",a=" + a);
    }

    public static void main(String[] args) throws InterruptedException {
        // 如果使用while  是有可能出现b=3,a=1的
        for (; ; ) {
            ThreadVisi threadVisi = new ThreadVisi();
            new Thread(() -> {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                threadVisi.change();

            }).start();
            new Thread(() -> {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                threadVisi.print();
            }).start();
        }
    }

private static int x = 0, y = 0;
    private static int a = 0, b = 0;


    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for (; ; ) {
            i++;
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            CountDownLatch count = new CountDownLatch(1);
            Thread thread = new Thread(() -> {
                try {
                    count.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                a = 1;
                x = b;
            });


            Thread thread1 = new Thread(() -> {
                try {
                    count.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                b = 1;
                y = a;
            });
            thread1.start();
            thread.start();
            count.countDown();

            thread1.join();
            thread.join();
            String result = "第" + i + "次";
            if (x == 0 && y == 0) {
                System.out.println(result + "x=" + x + ",y=" + y);
                break;
            }
            System.out.println(result + "x=" + x + ",y=" + y);
        }

    }

重排序的好处:

  • 提高响应速度

AQS是JUC提供的一个抽象类,用于实现同步的逻辑

关于CountDownLatch,先看下以下代码

 private static CountDownLatch countDownLatch = new CountDownLatch(3);

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            System.out.println("减1了");
            countDownLatch.countDown();
        }).start();
        new Thread(() -> {
            System.out.println("减1了");
            countDownLatch.countDown();
        }).start();
        new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("睡了3秒也减1了");
            countDownLatch.countDown();
        }).start();
        new Thread(() -> {
            try {
                countDownLatch.await(10, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("多线程被唤醒了1");
        }).start();
        new Thread(() -> {
            try {
                countDownLatch.await(10, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("多线程被唤醒了2");
        }).start();

        countDownLatch.await(10, TimeUnit.SECONDS);
        System.out.println("多线程被唤醒了3");
    }

也就是可以多个线程等待多个线程释放。

那么思想应该是使用了countDown()的去释放锁,调用了wait的去排队等待,如果state被改为0,那么就会被唤醒

共享锁的tryAcquireShared(int acquires)返回的是一个整型值:

  • 如果该值小于0,则代表当前线程获取共享锁失败
  • 如果该值大于0,则代表当前线程获取共享锁成功,并且接下来其他线程尝试获取共享锁的行为很可能成功
  • 如果该值等于0,则代表当前线程获取共享锁成功,但是接下来其他线程尝试获取共享锁的行为会失败

共享锁的获取:

public final void acquireShared(int arg) {
// 返回-1  说明没有其他线程获取共享锁的行为会失败  所以可以执行以下了 
// 这里试想以下  当前线程是wait  那么已知state是-1了 获取共享锁失败了
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
// 如果当前state的状态已经成0了,说明都countdown了 那么应该返回-1 
protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

先看固定代码:

private void doAcquireShared(int arg) {
        // 循环入队
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 获取当前节点的前一个节点
                final Node p = node.predecessor();
                // 如果前一个节点是头节点
                if (p == head) {
                    // 获取当前state
                    int r = tryAcquireShared(arg);
                    // state>=0 说明state还不到0 还有线程没有countdown
                    if (r >= 0) {
                        // 设置头节点
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                // 如果前一个节点不是头节点 那么看是否应该park
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

释放锁

 public void countDown() {
        sync.releaseShared(1);
    }
public final boolean releaseShared(int arg) {
        // 如果返回true 才执行下面的  返回false  直接返回false
    // 返回true就代表state为0
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

CountDownlatch的尝试释放共享锁,把cas和自旋的完美结合。直观体现

protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

staet为0代表什么,本来就等了5个线程执行,现在5个线程执行完了,那么该唤醒等待的线程了。

锁的释放

private void doReleaseShared() {
        for (;;) {
            Node h = head;
            // 如果头节点不为空 并且头节点不等于尾节点
            if (h != null && h != tail) {
                // 获取当前头节点的状态
                int ws = h.waitStatus;
                // 如果当前节点状态是SIGNAL
                if (ws == Node.SIGNAL) {
                    // 更改当前等待状态
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                    // 当前节点状态是0并且尝试更改状态为-3
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值