第四章 共享模型之 管程 (上)

JUC并发编程系列文章

http://t.csdn.cn/UgzQi


文章目录


前言


在这里插入图片描述


一、共享带来的问题

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

java中的体现

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

临界区与竞态条件

在这里插入图片描述
在这里插入图片描述

二、synchronized解决内存共享问题

在这里插入图片描述

在这里插入图片描述

对象锁解决 内存变量共享问题

import java.util.Date;

/**
 * 对象锁解决 内存变量共享问题
 */
public class testThread05 {
    static int count = 0;

    static Object lock = new Object();

    public static void main(String[] args) throws Exception{
        Thread thread1 = new Thread(()->{
           for (int i = 0; i <= 5000;i++){
               synchronized (lock){
                   //临界区
                   count++;
               }
           }
        });

        Thread thread2 = new Thread(()->{
            for (int i = 0; i <= 5000;i++){
                synchronized (lock){
                    //临界区
                    count--;
                }
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        //输出共享变量
        System.out.println("输出共享变量="+count);
    }
}

输出共享变量=0

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用面向对象的思想解决共享变量问题

import java.util.Date;

/**
 * 使用面向对象的思想解决共享变量问题
 * 对象锁解决 内存变量共享问题
 */
public class testThread05 {

    public static void main(String[] args) throws Exception{
        Room room = new Room();
        Thread thread1 = new Thread(()->{
            for (int i = 0; i <= 5000;i++){
                room.addition();
            }
        });

        Thread thread2 = new Thread(()->{
            for (int i = 0; i <= 5000;i++){
                room.subtraction();
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        //输出共享变量
        System.out.println("输出共享变量="+room.getCount());
    }
}


class Room{
    private int count = 0;


    public  void addition(){
        synchronized (this){
            //临界区
            count++;
        }
    }



    public void subtraction (){
        synchronized (this) {
            //临界区
            count--;
        }

    }

    public int getCount(){
        synchronized (this){
            return count;
        }
    }
}

三、方法上的 synchronized

synchronized 添加在普通方法上锁住的是当前对象,synchronized 添加在静态方法上锁住的是类对象。不加synchronzied 的方法就好比不遵守规则的人,不去老实排队(好比翻窗户进去的)

在这里插入图片描述

线程八锁

在这里插入图片描述

情况一 对象锁住当前对象

测试锁住的是不是 this 对象,
在两个方法 a,b 上都添加 synchronized 关键字
当使用它们两个使用同一个对象调用两个方法时,
一个线程会拿到当前对象的对象锁,另一个线程拿不到锁就需要等待

import lombok.extern.slf4j.Slf4j;

/**
 * 测试锁住的是不是 this 对象,
 * 在两个方法 a,b 上都添加 synchronized 关键字
 * 当使用它们两个使用同一个对象调用两个方法时,
 * 一个线程会拿到当前对象的对象锁,另一个线程拿不到锁就需要等待
 */

@Slf4j(topic = "c.Number")
class Number{
    public synchronized void a() {
        log.debug("1");
    }
    public synchronized void b() {
        log.debug("2");
    }
}



@Slf4j(topic = "c.testThread06")
public class testThread06 {

    public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(()->{
            log.debug("being");
            n1.a(); }).start();
        new Thread(()->{ log.debug("being");
            n1.b(); }).start();
    }
}


情况二 需要等待锁的释放

在这里插入图片描述

import lombok.extern.slf4j.Slf4j;

/**
 * 一个线程会拿到当前对象的对象锁,另一个线程拿不到锁就需要等待
 * 即使拿到锁的线程在 sleep 睡眠
 */

@Slf4j(topic = "c.Number")
class Number{
    public synchronized void a() throws Exception {
        Thread.sleep(5000);
        log.debug("1");
    }
    public synchronized void b() {
        log.debug("2");
    }
}



@Slf4j(topic = "c.testThread06")
public class testThread06 {

    public static void main(String[] args) throws Exception {
        Number n1 = new Number();
        new Thread(()->{
            log.debug("being");
            try {
                n1.a();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{ log.debug("being");
            n1.b(); }).start();
    }
}


情况三 没有synchronized 的方法执行是不需要等待的

在这里插入图片描述

import lombok.extern.slf4j.Slf4j;

/**
 * 如果有没有添加 synchronized 的方法被当前对象调用,该方法的执行是不需要等待的
 * 
 * 
 * 一个线程会拿到当前对象的对象锁,另一个线程拿不到锁就需要等待
 * 即使拿到锁的线程在 sleep 睡眠
 */

@Slf4j(topic = "c.Number")
class Number{
    public synchronized void a() throws Exception {
        Thread.sleep(5000);
        log.debug("1");
    }
    public synchronized void b() {
        log.debug("2");
    }

    public  void c() {
        log.debug("3");
    }
}



@Slf4j(topic = "c.testThread06")
public class testThread06 {

    public static void main(String[] args) throws Exception {
        Number n1 = new Number();
        new Thread(()->{
            log.debug("being");
            try {
                n1.a();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"T1").start();
        new Thread(()->{ log.debug("being");
            n1.b(); },"T2").start();

        new Thread(()->{ log.debug("being");
            n1.c(); },"T3").start();
    }
}


情况四 普通方法的对象锁,锁不住两个对象

在这里插入图片描述

import lombok.extern.slf4j.Slf4j;

/**
 * 同时调用两个对象的两个方法是锁不住的,
 * 因为 synchronized 是锁的当前对象
 * 如果需要锁住两个对象需要使用 类锁
 */

@Slf4j(topic = "c.Number")
class Number{
    public  synchronized void a() throws Exception {
        Thread.sleep(5000);
        log.debug("1");
    }
    public  synchronized void b() {
        log.debug("2");
    }

    public  void c() {
        log.debug("3");
    }
}



@Slf4j(topic = "c.testThread06")
public class testThread06 {

    public static void main(String[] args) throws Exception {
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(()->{
            log.debug("being");
            try {
                n1.a();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"T1").start();
        new Thread(()->{ log.debug("being");
            n2.b(); },"T2").start();

    }
}


情况五 类锁只能锁住不同类对象调用相同的方法

在这里插入图片描述

import lombok.extern.slf4j.Slf4j;

/**
 *  synchronized添加到静态方法上可以锁住类对象
 *  但是如果只在一个静态方法上加 synchronized 关键字,类对象只能锁住这个一个方法
 *  同一个类的对象,调用的方法不同是锁不住的,
 *  如果不同的类对象,调用的方法不同还是锁不住。
 */

@Slf4j(topic = "c.Number")
class Number{
    //类锁
    public static synchronized void a() throws Exception {
        Thread.sleep(5000);
        log.debug("1");
    }
    //对象锁
    public synchronized void b() {
        log.debug("2");
    }

    public  void c() {
        log.debug("3");
    }
}



@Slf4j(topic = "c.testThread06")
public class testThread06 {

    public static void main(String[] args) throws Exception {
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(()->{
            log.debug("being");
            try {
                n1.a();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"T1").start();
        new Thread(()->{ log.debug("being");
            //虽然是同一个对象但是锁不住
            n1.b(); },"T2").start();

    }
}



情况六 类锁可以所锁都加了类锁的方法

在这里插入图片描述

import lombok.extern.slf4j.Slf4j;

/**
 *  synchronized添加到静态方法上可以锁住类对象
 */

@Slf4j(topic = "c.Number")
class Number{
    //类锁
    public static synchronized void a() throws Exception {
        Thread.sleep(5000);
        log.debug("1");
    }
    //类锁
    public static synchronized void b() {
        log.debug("2");
    }

    public  void c() {
        log.debug("3");
    }
}



@Slf4j(topic = "c.testThread06")
public class testThread06 {

    public static void main(String[] args) throws Exception {
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(()->{
            log.debug("being");
            try {
                n1.a();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"T1").start();
        new Thread(()->{ log.debug("being");
            //类锁可以锁住
            n1.b(); },"T2").start();

    }
}


情况七 类锁锁不住没有加类锁的方法,即使是不同的类对象

在这里插入图片描述

情况八 类锁可以锁住不同对象的不同方法,因为类只要一个

在这里插入图片描述

四、变量的线程安全分析

在这里插入图片描述

1、局部变量的线程安全分析

在这里插入图片描述
在这里插入图片描述

2、成员变量需要考虑线程安全问题

import java.util.ArrayList;

/**
 * 有可能抛出 IndexOutOfBoundsException 数组下标越界异常
 * 这是因为线程 1还没有往 list集合添加数据,线程 2就去执行删除操作,所以报错了
 */
public class testThread07 {
    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
        ThreadUnsafe test = new ThreadUnsafe();
        //循环创建两个线程
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                test.method1(LOOP_NUMBER);
            }, "Thread" + i).start();
        }
    }
}

class ThreadUnsafe {
    ArrayList<String> list = new ArrayList<>();
    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            // { 临界区, 会产生竞态条件
            method2();
            method3();
            // } 临界区
        }
    }
    private void method2() {
        list.add("1");
    }
    private void method3() {
        list.remove(0);
    }
}

在这里插入图片描述
在这里插入图片描述

3、成员变量变为局部变量可以避免线程安全问题

在这里插入图片描述
在这里插入图片描述

4、局部变量暴露引用后产生的线程安全问题

在这里插入图片描述
不会有线程安全问题,因为如果有其他线程来调用 method 2,传入过来的肯定是另一个 list 集合,和method 1使用的list集合不是同一个list自然没有线程安全问题。

在这里插入图片描述
上图就是局部变量的引用暴露给了其他线程,从而出现了线程安全问题。但如果将父类的访问修饰符设置为private,这时子类去继承父类,却无法访问 private 修饰的方法,就算重写也不会生效,另起线程也就不会造成线程安全问题,因此访问修饰符也在一定程度上帮助完美进行的线程安全的防范 。

在这里插入图片描述

5、常见线程安全类

在这里插入图片描述

5.1、两个线程安全的类方法组合在一起,线程还是不安全

以 hashTable为粒,get方法和 put 方法都是线程安全的,但也是仅限于两个方法的内部,当同时有两个线程操作它们时,就会出现线程安全的问题,线程 1 在get 的时候,线程 2也有可能来操作 get 方法。

如果想让类方法组合还是保证线程安全就需要在调用它们时在外层再加一个同步锁,保证只要一个线程来操作 get put 方法

在这里插入图片描述

5.2、不可变类线程安全性

这些不可变类在进行属性的操作时,并不是改变属性的值,而是 new 了一个新的对象,将需要改变的值赋值给新对象并返回

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6、实例分析

实例一

我们都知道servlet运行在 tomcat服务端,因此会被很多不同的线程同时调用,那么下图的 MyServlet哪些地方是线程安全的,哪些不是?

在这里插入图片描述

实例二

在这里插入图片描述

实例三

实例四

在这里插入图片描述

实例五

在这里插入图片描述

实例六

在这里插入图片描述

实例七

在这里插入图片描述

面试题 :String为什么被设计成 final

因为如果不设置成 final 它的子类很有可能会覆盖掉 String 类自身的行为,不设置成 final 也可能导致破坏了本来的安全性,本来是线程安全的,把引用暴露给子类就破坏了安全性,也就体现出了经典的闭合原则。

1 字符串常量池的需要
字符串常量池是Java堆内存中一块特殊的存储区域,当创建一个string对象时,假如此字符串值已经在常量池中了,则不会创建一个新的对象,而是引用已存在的对象。

2 允许string对象缓存hashCode
Java醒的string对象的hash码被频繁使用,尤其在hashmap体系中,字符串不变保证了hash码的唯一性,因此可以放心的进行缓存,这样也就不必每次都去计算新的hash码了,也一定程度上优化了性能。在string类中有定义如下代码
private int hash;用来缓存hash码

3 安全性
String类被许多Java类用来当做参数,其中难免会有多线程的情况,如果将string类的引用暴露给这些多线程的地方,都去对string进行修改,会导致线程安全问题,将string设计为不可变的也就避免了这些暴露的引用的安全问题。

五、习题

卖票

对象锁解决线程安全问题

在这里插入图片描述

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;

@Slf4j(topic = "c.testThread")
public class testThread {
    public static void main(String[] args) {
        TicketWindow ticketWindow = new TicketWindow(1000);
        List<Thread> list = new ArrayList<>();
        // 用来存储买出去多少张票  放入 list 集合
        List<Integer> sellCount = new Vector<>();
        //循环创建2000个线程
        for (int i = 0; i < 2000; i++) {
            Thread t = new Thread(() -> {
                log.debug("线程启动");
                // 分析这里的竞态条件 这里所以线程都操作这一个 ticketWindow 肯定有安全问题,
                // 所以对 ticketWindow.sell(randomAmount()); 上锁
                int count = ticketWindow.sell(randomAmount());
                log.debug("线程卖了"+count+"张票");
                try {
                    Thread.sleep(randomAmount());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //记录每次卖出的票数,最后求和
                sellCount.add(count);
            },"T"+i);
            list.add(t);
            t.start();
        }
        //让主线程等待2000个线程执行完毕,循环执行join方法
        list.forEach((t) -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });


        // 卖出去的票求和
        log.debug("卖出去的票求和selled count:{}",sellCount.stream().mapToInt(c -> c).sum());
        // 剩余票数
        log.debug("剩余票数remainder count:{}", ticketWindow.getCount());
    }
    // Random 为线程安全
    static Random random = new Random();
    // 随机 1~5
    public static int randomAmount() {
        return random.nextInt(5) + 1;
    }
}

class TicketWindow {
    private int count;
    public TicketWindow(int count) {
        this.count = count;
    }
    public int getCount() {
        return count;
    }
    //加锁保证同时只有一个线程进来
    public synchronized int sell(int amount) {
    //临界区
        if (this.count >= amount) {
            this.count -= amount;
            return amount;
        } else {
            return 0;
        }
    }


}

转账

类锁解决线程安全问题,虽然可以而且简单,但是性能很低,如果涉及该类的多个实例对象,不需要做转账的对象也要进行阻塞式等待,所以效率很低。

在这里插入图片描述

import lombok.extern.slf4j.Slf4j;

import java.util.Random;

/**
 * 类锁
 */
@Slf4j(topic = "c.testThread08")
public class testThread08 {
    public static void main(String[] args) throws InterruptedException {
        //这里多线程操作两个类对象,需要使用类锁
        Account a = new Account(1000);
        Account b = new Account(1000);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                a.transfer(b, randomAmount());
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                b.transfer(a, randomAmount());
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        // 查看转账2000次后的总金额
        log.debug("total:{}",(a.getMoney() + b.getMoney()));
    }
    // Random 为线程安全
    static Random random = new Random();
    // 随机 1~100
    public static int randomAmount() {
        return random.nextInt(100) +1;
    }
}

class Account {
    private int money;
    public Account(int money) {
        this.money = money;
    }
    public int getMoney() {
        return money;
    }
    public void setMoney(int money) {
        this.money = money;
    }
    public void transfer(Account target, int amount) {
        //类锁
        synchronized (Account.class){
        //临界区
            if (this.money > amount) {
                this.setMoney(this.getMoney() - amount);
                target.setMoney(target.getMoney() + amount);
            }
        }

    }


}

六、Monitor 概念

Java对象头

在这里插入图片描述

Mark Word 主要用来存储对象自身的运行时数据
Klass Word 指向Class对象
其中 Mark Word 结构为, 不同对象状态下结构和含义也不同

在这里插入图片描述
在这里插入图片描述

Monitor

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

synchronized工作原理

只要代码跑在 synchronized 就不存在解不了锁的问题,如果线程 1 在执行过程抛出了异常,Monitor会唤醒其他线程拿到锁,将异常信息打印。出现死锁是一个线程一直在运行没有释放锁,而另一个线程需要这个锁,就会形成死锁。如果出现断电程序都直接寄了,别说拿锁了。JUC都是本地锁,不是分布式锁。

在这里插入图片描述
在这里插入图片描述

synchronized优化原理

小故事

在这里插入图片描述
在这里插入图片描述

1、轻量级锁

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里入图片描述

2、膨胀锁

在这里插入图片描述
在这里插入图片描述

3、自旋优化

当轻量级锁发生竞争时,膨胀为重量级锁,这时没有拿到Monitor Owner的线程就会阻塞等待,但是进入阻塞状态后再唤醒线程就会进行线程的上下文切换,这时比较浪费资源的,此时可以让没有拿到锁的线程进行一个等待的循环,也就是自旋等待,等到拿到锁的线程释放锁后,就可以立即执行直接的业务,而不用去阻塞再唤醒上下文切换。另外需要注意,自旋的状态也是一直需要cpu资源的,所以适合cpu的核心数足够多的情况下,如果是单核cpu自旋是没有意义的。

在这里插入图片描述
在这里插入图片描述

4、偏向锁

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.1、偏向锁的状态

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.2、测试偏向锁

在这里插入图片描述

只要不发生线程竞态,也就是没有别的线程来使用调用这个对象,对象头的 id 一直是当前线程,所以偏向锁适合只有一个线程活动的情况,一旦是多线程高并发就没得用了,所以不想使用偏向锁时可以禁用掉。

在这里插入图片描述

利用 jol 第三方工具来查看对象头信息(注意这里up主扩展了 jol 让它输出更为简洁)
代码很简单,重在理解对象头MarkWord
偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,
可以加 VM 参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟

4.3、禁用偏向锁

在这里插入图片描述
在这里插入图片描述

4.4、测试hashCode( )

面试题:为什么轻量级锁和重量级锁调用hashCode不会被禁用掉?

那么为什么轻量级锁和重量级锁调用hashCode不会被禁用掉?
轻量级锁的hashCode会存储在线程栈帧的锁记录里面,重量级锁的hashCode会存储在Monitor对象里面,解锁的时候还会还原回来,而对于偏向锁没有多余的存贮空间了,因此就有这样的一个特点,调用了hashCode就没有办法使用偏向锁了。

在这里插入图片描述

在这里插入图片描述

5、撤销偏向锁

5.1、调用对象的 hashCode()

在这里插入图片描述

5.2、两个线程错开调用对象

撤销(偏向) - 其它线程(错开)使用对象
当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

在这里插入图片描述


private static void test2() throws InterruptedException {
    
    Dog d = new Dog();
    
    Thread t1 = new Thread(() -> {
        
        log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        synchronized (d) {
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
        log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        
        synchronized (TestBiased.class) {
            TestBiased.class.notify();
        }
        // 如果不用 wait/notify 使用 join 必须打开下面的注释
        // 因为:t1 线程不能结束,否则底层线程可能被 jvm 重用作为 t2 线程,底层线程 id 是一样的
        /*try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }*/
    }, "t1");
    t1.start();
    
    Thread t2 = new Thread(() -> {
        synchronized (TestBiased.class) {
            try {
                TestBiased.class.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        synchronized (d) {
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
        log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        
    }, "t2");
    t2.start();
}

在这里插入图片描述

5.3、对象调用 wait/notify

在这里插入图片描述

撤销(偏向) - 调用 wait/notify
重量级锁才支持 wait/notify,所以会直接让偏向和轻量级升级为重量级锁


public static void main(String[] args) throws InterruptedException {
    Dog d = new Dog();
    
    Thread t1 = new Thread(() -> {
        log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        synchronized (d) {
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
            try {
            //对象调用 wait
                d.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
    }, "t1");
    t1.start();
    
    new Thread(() -> {
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (d) {
            log.debug("notify");
             //对象调用 notify
            d.notify();
        }
    }, "t2").start();
}

[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101 
[t2] - notify 
[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010




6、批量重偏向

当多个对象都偏向线程 T1 时,会生成偏向锁,但是如果这时突然来了另一个线程 T2 操作这些对象,这时就会撤销偏向锁升级为 轻量锁,当这种撤销的次数过多时,jvm就会认为是否应该偏向另一个线程,这时就会让这些对象都偏向于 T2 线程,这就是批量重偏向。

总结:批量重偏向会以class为单位,为每个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器+1,当这个值达到重偏向阈值(默认20)时,JVM就认为该class的偏向锁有问题,因此会进行批量重偏向。

在这里插入图片描述

这里略有些不好用文字说明,看例子吧
注意t2-19处的变化: 发生了批量重偏向

private static void test3() throws InterruptedException {
    
    Vector<Dog> list = new Vector<>();
    
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 30; i++) {
            Dog d = new Dog();
            list.add(d);
            synchronized (d) {
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
        }
        synchronized (list) {
            list.notify();
        }
    }, "t1");
    t1.start();

    Thread t2 = new Thread(() -> {
        synchronized (list) {
            try {
                list.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        log.debug("===============> ");
        for (int i = 0; i < 30; i++) {
            Dog d = list.get(i);
            log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            synchronized (d) {
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
            log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
    }, "t2");
    t2.start();
}



//输出

[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - ===============> 
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
//===================================发生批量重偏向,对比线程 id
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101

7、批量撤销

在这里插入图片描述

static Thread t1,t2,t3;
private static void test4() throws InterruptedException {
    Vector<Dog> list = new Vector<>();
    
    int loopNumber = 39;
    t1 = new Thread(() -> {
        for (int i = 0; i < loopNumber; i++) {
            Dog d = new Dog();
            list.add(d);
            synchronized (d) {
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
        }
        LockSupport.unpark(t2);
    }, "t1");
    t1.start();
    
    t2 = new Thread(() -> {
        LockSupport.park();
        log.debug("===============> ");
        for (int i = 0; i < loopNumber; i++) {
            Dog d = list.get(i);
            log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            synchronized (d) {
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
            log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
        LockSupport.unpark(t3);
    }, "t2");
    t2.start();
    
    t3 = new Thread(() -> {
        LockSupport.park();
        log.debug("===============> ");
        for (int i = 0; i < loopNumber; i++) {
            Dog d = list.get(i);
            log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            synchronized (d) {
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
            log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
    }, "t3");
    t3.start();
    
    t3.join();
    log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
}

参考资料
https://github.com/farmerjohngit/myblog/issues/12
https://www.cnblogs.com/LemonFive/p/11246086.html
https://www.cnblogs.com/LemonFive/p/11248248.html
偏向锁论文: https://www.oracle.com/technetwork/java/biasedlocking-oopsla2006-wp-149958.pdf

8、锁消除

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值