JAVA并发编程之锁

1、乐观锁和悲观锁

1.1、悲观锁

  1. 认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会加锁,确保数据不会别的线程修改。
  2. synchronized关键字和Lock的实现类都是悲观锁。
  3. 适合写操作多的场景,先加锁可以保证写操作时数据正确,显示的锁定之后再造作同步资源。
  4. 狼性锁

1.2、乐观锁

  1. 认为自己在使用数据时不会有别的线程修改数据或资源,所以不会添加锁。

  2. 在JAVA中是通过使用无锁编程来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果这个数据已经被其它线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等等。

  3. 判断规则:
    (1)版本号机制Version
    (2) 最常采用的是CAS算法,JAVA原子类中的递增操作就通过CAS自旋实现的。

  4. 适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅度提升。

  5. 乐观锁直接去操作同步资源,是一种无锁算法,得之我幸不得我命,再努力就是一句话:佛系锁。
    在这里插入图片描述

2、线程八锁

  1. 标准访问有a、b两个线程,请问先打印邮件还是短信?
import java.util.concurrent.TimeUnit;

/**
 * @Description: 线程8锁
 * @Author: yangyb
 * @Date:2022/9/28 22:25
 * Version: 1.0
 **/

class Phone{
    public synchronized void sendEmail(){
        System.out.println("-------------sendEmail");
    }

    public synchronized void sendSMS(){
        System.out.println("-----------sendSMS");
    }
}

public class Lock8Demo {

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

        new Thread(phone::sendEmail,"a").start();

        //暂停两毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(phone::sendSMS,"b").start();
    }

}

synchronized 加在普通的方法上面,是对象锁,由于a线程先启动,在调用sendEmail()方法时先拿到了这个phone对象的锁,所以肯定先执行此方法,此方法执行完后,a线程释放phone对象锁,然后b线程才能拿到phone对象锁,去执行sendSMS()方法。
在这里插入图片描述
2. sendEmail方法中加入暂停3秒钟,请问先打印邮件还是hello?

import java.util.concurrent.TimeUnit;

/**
 * @Description: 线程8锁
 * @Author: yangyb
 * @Date:2022/9/28 22:25
 * Version: 1.0
 **/

class Phone{
    public synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");
    }

    public synchronized void sendSMS(){
        System.out.println("-----------sendSMS");
    }
}

public class Lock8Demo {

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

        new Thread(phone::sendEmail,"a").start();

        //暂停两毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(phone::sendSMS,"b").start();
    }

}

synchronized 加在普通的方法上面,是对象锁,由于a线程先启动,在调用sendEmail()方法时先拿到了这个phone对象的锁,此时虽然暂停了3秒钟,但phone对象锁一直被a线程持有,所以肯定还是先执行sendEmail()方法,此方法执行完后,a线程释放phone对象锁,然后b线程才能拿到phone对象锁,去执行sendSMS()方法。
在这里插入图片描述
在这里插入图片描述

  1. 添加一个普通的hello方法,请问先打印邮件还是hello?
import java.util.concurrent.TimeUnit;

/**
 * @Description: 线程8锁
 * @Author: yangyb
 * @Date:2022/9/28 22:25
 * Version: 1.0
 **/

class Phone{
    public synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");
    }

    public synchronized void sendSMS(){
        System.out.println("-----------sendSMS");
    }

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

public class Lock8Demo {

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

        new Thread(phone::sendEmail,"a").start();

        //暂停两毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(phone::hello,"b").start();
    }

}

a线程拿到phone对象锁以后,去执行sendEmail()方法,但中间暂停了3秒钟,同时b线程执行的是普通的hello(),所以不需要获取phone对象锁,就可以立即执行hello()方法,而不用等到a线程释放phone对象锁,再去获取phone对象锁。
在这里插入图片描述

  1. 有两个手机对象,请问先打印邮件还是短信?
/**
 * @Description: 线程8锁
 * @Author: yangyb
 * @Date:2022/9/28 22:25
 * Version: 1.0
 **/

class Phone{
    public synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");
    }

    public synchronized void sendSMS(){
        System.out.println("-----------sendSMS");
    }

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

public class Lock8Demo {

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

        new Thread(phone::sendEmail,"a").start();

        //暂停两毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(phoneOne::sendSMS,"b").start();
    }

}

a线程持有的是phone对象的锁,并不会影响到b线程获取phoneOne对象的锁,所以是两个线程拿到了两个不同的对象锁,互不干扰,各自执行各自的。
在这里插入图片描述
在这里插入图片描述

  1. 有两个静态同步方法,有一个手机对象,请问先打印邮件还是短信?
import java.util.concurrent.TimeUnit;

/**
 * @Description: 线程8锁
 * @Author: yangyb
 * @Date:2022/9/28 22:25
 * Version: 1.0
 **/

class Phone{
    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");
    }

    public static synchronized void sendSMS(){
        System.out.println("-----------sendSMS");
    }

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

public class Lock8Demo {

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

        new Thread(()->{
            phone.sendEmail();
        },"a").start();

        //暂停两毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.sendSMS();
        },"b").start();
    }

}

静态方法属于类,在静态方法上加上synchronized关键字,线程a先启动获得的就是类锁,这时候锁住的是整个类的资源,所以线程b必须等待线程a释放类锁以后才能获取到类锁。
在这里插入图片描述
6. 有两个静态同步方法,有两个手机对象,请问先打印邮件还是短信?

import java.util.concurrent.TimeUnit;

/**
 * @Description: 线程8锁
 * @Author: yangyb
 * @Date:2022/9/28 22:25
 * Version: 1.0
 **/

class Phone{
    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");
    }

    public static synchronized void sendSMS(){
        System.out.println("-----------sendSMS");
    }

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

public class Lock8Demo {

    public static void main(String[] args){
        Phone phone = new Phone();
        Phone phoneOne = new Phone();
        new Thread(()->{
            phone.sendEmail();
        },"a").start();

        //暂停两毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phoneOne.sendSMS();
        },"b").start();
    }

}

静态方法属于类,在静态方法上加上synchronized关键字,线程a先启动获得的就是类锁,这时候锁住的是整个类的资源,所以线程b必须等待线程a释放类锁以后才能获取到类锁,即使创建了两个不同的对象,但这两个对象都属于同一个类,所以b线程还是要等待a线程释放整个类锁后,才能获得类锁,再执行响应的资源。
在这里插入图片描述
在这里插入图片描述

  1. 有一个静态同步方法,有一个普通同步方法,有一个手机对象,请问先打印邮件还是短信?
import java.util.concurrent.TimeUnit;

/**
 * @Description: 线程8锁
 * @Author: yangyb
 * @Date:2022/9/28 22:25
 * Version: 1.0
 **/

class Phone {
    public static synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");
    }

    public synchronized void sendSMS() {
        System.out.println("-----------sendSMS");
    }

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

public class Lock8Demo {

    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();

        //暂停两毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone.sendSMS();
        }, "b").start();
    }

}

获取静态同步方法,需要持有类锁,普通同步方法,获取的是对象锁,所以b线程不需要等到a线程释放完类锁,就能能获取到对象锁。
在这里插入图片描述

  1. 有一个静态同步方法,有一个普通同步方法,有两个手机对象,请问先打印邮件还是短信?
import java.util.concurrent.TimeUnit;

/**
 * @Description: 线程8锁
 * @Author: yangyb
 * @Date:2022/9/28 22:25
 * Version: 1.0
 **/

class Phone {
    public static synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");
    }

    public synchronized void sendSMS() {
        System.out.println("-----------sendSMS");
    }

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

public class Lock8Demo {

    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phoneOne = new Phone();
        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();

        //暂停两毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phoneOne.sendSMS();
        }, "b").start();
    }

}

获取静态同步方法,需要持有类锁,普通同步方法,获取的是对象锁,所以b线程不需要等到a线程释放完类锁,就能能获取到对象锁。

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

3、公平锁和非公平锁

3.1、非公平锁

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description: 非公平锁
 * @Author: yangyb
 * @Date:2022/10/5 11:38
 * Version: 1.0
 **/
class Ticket {
    private int number = 20;
    // 默认为非公平锁
    ReentrantLock lock = new ReentrantLock();

    public void sale() {
        lock.lock();
        try {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "售出了第" + number-- + "张票,还剩" + number + "票");
            }
        } finally {
            lock.unlock();
        }

    }
}

public class SaleTicketDemo {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            for (int i = 0; i < 55; i++) {
                ticket.sale();
            }
        }, "a").start();
        new Thread(() -> {
            for (int i = 0; i < 55; i++) {
                ticket.sale();
            }
        }, "b").start();
        new Thread(() -> {
            for (int i = 0; i < 55; i++) {
                ticket.sale();
            }
        }, "c").start();
    }

}

在这里插入图片描述

3.2、公平锁

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description: 公平锁
 * @Author: yangyb
 * @Date:2022/10/5 11:38
 * Version: 1.0
 **/
class Ticket {
    private int number = 20;
    // 公平锁
    ReentrantLock lock = new ReentrantLock(true);

    public void sale() {
        lock.lock();
        try {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "售出了第" + number-- + "张票,还剩" + number + "票");
            }
        } finally {
            lock.unlock();
        }

    }
}

public class SaleTicketDemo {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            for (int i = 0; i < 22; i++) {
                ticket.sale();
            }
        }, "a").start();
        new Thread(() -> {
            for (int i = 0; i < 22; i++) {
                ticket.sale();
            }
        }, "b").start();
        new Thread(() -> {
            for (int i = 0; i < 22; i++) {
                ticket.sale();
            }
        }, "c").start();
    }

}

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

3.3、为什么会有公平锁和非公平锁的设计?为什么默认非公平?

  1. 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从cpu的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU的时间片,尽量减少cpu空闲状态时间。
  2. 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。

3.4、何时用公平锁?何时用非公平锁?

如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;否则就用公平锁,大家公平使用。

4、可重入锁(又名递归锁)

4.1、概念

  • 指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
    例如:如果是一个有symchronized修饰得递归调用方法,程序第二次进入被自己阻塞了,这不成自己阻塞自己了吗?显然就不合常规的处理逻辑了。所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可以一定程度避免死锁。
  • 总结:一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。即自己可以获取自己的内部锁。

4.2、隐式锁

隐式锁(即synchronized关键字使用的锁)默认是可重入锁。指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫可重入锁。简单的lai说就是,在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的。

/**
 * @Description: 隐式锁synchronized,同步代码块
 * @Author: yangyb
 * @Date:2022/10/6 19:08
 * Version: 1.0
 **/
public class ReEntryLockDemo {

    public static void main(String[] args) {
        Object object = new Object();

        new Thread(() -> {
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + "\t-------外层调用");
                synchronized (object) {
                    System.out.println(Thread.currentThread().getName() + "\t-------中层调用");
                    synchronized (object) {
                        System.out.println(Thread.currentThread().getName() + "\t-------内层调用");
                    }
                }
            }
        }, "t1").start();
    }

}

在这里插入图片描述

/**
 * @Description: 隐式锁synchronized,同步方法
 * @Author: yangyb
 * @Date:2022/10/6 19:08
 * Version: 1.0
 **/
public class ReEntryLockDemo {

    public synchronized void m1(){
        System.out.println("---m1 "+Thread.currentThread().getName()+"\t ------come in!");
        m2();
        System.out.println("---m1 "+Thread.currentThread().getName()+"\t ------end m1!");
    }

    public synchronized void m2(){
        System.out.println("---m2 "+Thread.currentThread().getName()+"\t ------come in!");
        m3();
    }

    public synchronized void m3(){
        System.out.println("---m3 "+Thread.currentThread().getName()+"\t ------come in!");
    }

    public static void main(String[] args) {

        ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
        new Thread(reEntryLockDemo::m1,"t1").start();

    }

}

在这里插入图片描述

4.2.1、Synchronized可重入的实现原理
在这里插入图片描述

4.3、显示锁(ReentrantLock)

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

/**
 * @Description: 显示锁ReentrantLock
 * @Author: yangyb
 * @Date:2022/10/6 19:08
 * Version: 1.0
 **/
public class ReEntryLockDemo {

    static Lock lock = new ReentrantLock();

    public static void main(String[] args) {

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t 外层调用lock!");
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "\t 内层调用lock!");
                } finally {
                    // 这里故意注释掉,让实现加锁次数和释放锁的次数不一样
                    // 由于枷锁次数和释放锁次数不一样,第二个线程始终无法获取到锁,导致一直在等待
                    //lock.unlock();
                }
            } finally {
                lock.unlock();
            }
        }, "t1").start();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t 调用lock!");
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}

在这里插入图片描述

5、死锁及排查

5.1、死锁

5.1.1、概念

在这里插入图片描述

5.1.2、代码示例

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

/**
 * @Description: 死锁
 * @Author: yangyb
 * @Date:2022/10/6 20:19
 * Version: 1.0
 **/
public class DeadlockDemo {

    static Lock lockA = new ReentrantLock();

    static Lock lockB = new ReentrantLock();

    public static void main(String[] args) {

        new Thread(() -> {
            lockA.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t 线程持有lockA锁,去尝试持有lockB锁");
                lockB.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "\t 尝试获取并持有了lockB锁");
                } finally {
                    lockB.unlock();
                }

            } finally {
                lockA.unlock();
            }
        }, "A").start();

        new Thread(() -> {
            lockB.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t 线程持有lockB锁,去尝试持有lockA锁");
                lockA.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "\t 尝试获取并持有了lockA锁");
                } finally {
                    lockA.unlock();
                }

            } finally {
                lockB.unlock();
            }
        }, "B").start();

    }
}

在这里插入图片描述

5.1.3、产生死锁的主要原因

  • 系统资源不足
  • 进程运行推进的顺序不合适
  • 资源分配不当

5.2、死锁排查

5.2.1、在终端使用命令行

  1. jps -l
    在这里插入图片描述

  2. jstack 17988
    在这里插入图片描述
    在这里插入图片描述

5.2.2、图形化

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

在这里插入图片描述

6、为什么任何一个对象都可以成为锁?

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

7、LockSupport与线程中断

7.1、线程中断

7.1.1、什么是中断机制?

首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运。所以,Thread.stop,Thread.suspend,Thread.resume都已经被废弃了。
其次,在java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制——中断,即中断标识协商机制。
中断只是一种协作协商机制,Java中没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true,此时究竟该做什么需要你自己写代码实现。
每个线程对象中都有一个中断标识位,用于表示 线程是否被中断;该标识位为true表示中断,为false表示未中断;通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。

7.1.2、中断线程API常见的三大方法
在这里插入图片描述

  1. 如何停止中断允许中的线程?
    (1)同过一个volatile 变量实现
/**
 * @Description: 线程中断
 * @Author: yangyb
 * @Date:2022/10/7 1:40
 * Version: 1.0
 **/
public class InterruptedDemo {

    static volatile boolean isStop = false;

    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (isStop) {
                    System.out.println(Thread.currentThread().getName() + "\t isStop被修改为" + isStop + "线程终止!");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "\t 线程正在执行!");
            }
        }, ":t1").start();
        try {
            TimeUnit.MILLISECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            isStop = true;
        }, "t2").start();
    }

}

在这里插入图片描述
(2)同过AtomicBoolean 实现

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

/**
 * @Description: 线程中断
 * @Author: yangyb
 * @Date:2022/10/7 1:40
 * Version: 1.0
 **/
public class InterruptedDemo {

    static AtomicBoolean atomicBoolean=new AtomicBoolean(false);

    public static void main(String[] args) {

        new Thread(() -> {
            while (true) {
                if (atomicBoolean.get()) {
                    System.out.println(Thread.currentThread().getName() + "\t atomicBoolean被修改为" + atomicBoolean.get() + "线程终止!");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "\t 线程正在执行!");
            }
        }, ":t1").start();
        try {
            TimeUnit.MILLISECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            atomicBoolean.set(true);
        }, "t2").start();

    }

}

在这里插入图片描述
(3)通过Thread类自带的pai来实现

import java.util.concurrent.TimeUnit;

/**
 * @Description: 线程中断
 * @Author: yangyb
 * @Date:2022/10/7 1:40
 * Version: 1.0
 **/
public class InterruptedDemo {

    public static void main(String[] args) {

        Thread t1=new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + "\t isInterrupted()被修改为" + true + "线程终止!");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "\t 线程正在执行!");
            }
        }, ":t1");
        t1.start();
        try {
            TimeUnit.MILLISECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(t1::interrupt, "t2").start();

    }

}

在这里插入图片描述
源码分析:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2. 当前线程的中断标识为true,是不是线程就立刻停止?

import java.util.concurrent.TimeUnit;

/**
 * @Description: 线程中断
 * @Author: yangyb
 * @Date:2022/10/7 1:40
 * Version: 1.0
 **/
public class InterruptedDemo {

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 300; i++) {
                System.out.println("i=" + i);
            }
            System.out.println(Thread.currentThread().getName() + "\t 线程调用t1.interrupt()方法后的中断标识为" + Thread.currentThread().isInterrupted());
        }, ":t1");
        t1.start();
        System.out.println(t1.getName() + "\t 线程中默认的中断标识为" + t1.isInterrupted());//false
        try {
            TimeUnit.MILLISECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();//true
        System.out.println(Thread.currentThread().getName() + "\t 线程调用t1.interrupt()方法后的第一次中断标识为" + t1.isInterrupted());

        try {
            TimeUnit.MILLISECONDS.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 中断标识重置为false,中断不活动的线程不会产生任何影响
        System.out.println(Thread.currentThread().getName() + "\t 线程调用t1.interrupt()方法,并等待t1线程执行完所有的逻辑后中断标识为" + t1.isInterrupted());
    }

}

在这里插入图片描述
在这里插入图片描述
此时t1线程的中断标识已经置为true,然而并没有立即中断线程,而是继续执行了所有逻辑
在这里插入图片描述
注意:
在这里插入图片描述
总结:
中断只是一种协商机制,修改中断标识位仅此而已,不是立刻stop打断。

  1. 静态方法Thread.interrupted()谈谈你的理解?
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    示例:
    在这里插入图片描述

结论:
静态方法interrupted将会清除中断状态(传入的参数ClearInterrupted为true),实例方法isInterrupted则不会(传入的参数ClearInterrupted为false)。

4.三大方法对比总结
在这里插入图片描述

7.2、LockSupport

7.2.1、概念

LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法。归根结底,LockSupport调用的Unsafe中的native代码。

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。LockSupport 提供park()和unpark()方法实现阻塞线程和解除线程阻塞,LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park会消费permit, 也就是将1变成0,同时park立即返回。再次调用park会变成block(因为permit为0了,会阻塞在这里,直到permit变为1), 这时调用unpark会把permit置为1。每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累。

park() 和 unpark()不会有 Thread.suspend 和 Thread.resume 所可能引发的死锁问题,由于许可的存在,调用 park 的线程和另一个试图将其 unpark 的线程之间的竞争将保持活性。

如果调用线程被中断,则park方法会返回。同时park也拥有可以设置超时时间的版本。
在这里插入图片描述

7.2.2、线程等待唤醒机制

  1. 线程等待和唤醒的方法在这里插入图片描述
    (1)Object的wait和notify在这里插入图片描述
    在这里插入图片描述
    (2)Condition的await()和signal()方法

在这里插入图片描述
在这里插入图片描述
(3)LockSupport的park()和unpark()方法
park()和unpark()顺序执行

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

/**
 * @Description: LockSupport
 * @Author: yangyb
 * @Date:2022/10/7 7:38
 * Version: 1.0
 **/
public class LockSupportDemo {

    public static void main(String[] args){

        Thread t1=new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t 线程---come in!");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName()+"\t 线程---被唤醒");
        },"t1");
        t1.start();

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

        new Thread(()->{
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName()+"\t 线程发出通知给t1线程");
        },"t2").start();
    }
}

在这里插入图片描述
先执行unpark(),再执行unpark()

/**
 * @Description: LockSupport
 * @Author: yangyb
 * @Date:2022/10/7 7:38
 * Version: 1.0
 **/
public class LockSupportDemo {

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t 线程---come in!");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t 线程---被唤醒");
        }, "t1");
        t1.start();

        new Thread(() -> {
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + "\t 线程发出通知给t1线程");
        }, "t2").start();
    }
}

在这里插入图片描述
7.2.3、总结
在这里插入图片描述

在这里插入图片描述

7.2.3、面试题
在这里插入图片描述

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

/**
 * @Description: LockSupport
 * @Author: yangyb
 * @Date:2022/10/7 7:38
 * Version: 1.0
 **/
public class LockSupportDemo {

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t 线程---come in!");
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t 线程---被唤醒");
        }, "t1");
        t1.start();

        new Thread(() -> {
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + "\t 线程发出通知给t1线程");
        }, "t2").start();
    }
}

在这里插入图片描述

8、Java内存模型即JMM

8.1、计算机硬件存储体系

在这里插入图片描述

在这里插入图片描述

8.2、JMM

8.2.1、JMM简介

在这里插入图片描述

8.2.2、JMM规范下,三大特性
(1)可见性
指当一个线程修改了某一个共享变量的值,其他线程能否立即知道该变更。JMM规定所有的变量都存储在主内存中。
在这里插入图片描述
在这里插入图片描述
(2)原子性
指一个操作是不可打断的,即多线程环境下,操作不能被其他线程干扰。
(3)有序性
在这里插入图片描述
在这里插入图片描述
8.2.3、JMM规范下,多线程对变量的读写过程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.2.3、JMM规范下,多线程先行发生原则之happens-before
(1)简介
在这里插入图片描述
(2)happens-before总原则
如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行最终的结果一致,那么这种重排序并不非法。
(2)happens-before的8条规则

  1. 次序规则:一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作。
  2. 锁定规则:一个unlock操作先行发生于后面(这里的“后面”是指时间上的先后)对同一个锁的lock操作。
  3. volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的“后面”同样是指时间上的先后。
  4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出来操作A先行发生于操作C。
  5. 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。
  6. 线程中断规则(Thread Interruption Rule):
    在这里插入图片描述
  7. 线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过isAlive()等手段检测线程是否已经终止执行。
  8. 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
    (4)总结
    在这里插入图片描述

9、volatile

9.1、volatile之两大特性

被volatile修饰的变量有两大特点:可见性和有序性

  1. 可见性:保证不同线程对某个变量完成操作后的结果对其它线程及时可见,即该共享变量一旦改变所有线程立即可见。

9.1.1、volatile内存语义
在这里插入图片描述
9.1.2、vlolatile如何保证可见性和有序性?
内存屏障(Memory Barrier)

9.2、内存屏障(Memory Barrier)

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

在这里插入图片描述

9.3、读写屏障之插入策略

在这里插入图片描述

  1. 读屏障

在这里插入图片描述

在这里插入图片描述

  1. 写屏障

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

  1. volatile变量的读写过程:JAVA内存模型中定义的8种每个线程自己的工作内存与主物理内存之间的原子操作。
    在这里插入图片描述
    在这里插入图片描述

9.4、volatile无原子性

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

9.5、指令禁重排

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

9.6、volatile使用场景

  1. 单一赋值可以,但是含复合运算赋值不可以(i++)类
volatile int a=10;
volatile boolean flag=false;
  1. 状态标志,判断业务是否结束
import java.util.concurrent.TimeUnit;

/**
 * @Description: volatile使用:作为一个布尔状态标志,用于指示发生了一个重要的一次性事件,列如完成初始化或任务结束
 * 理由:状态标志并不依赖于程序内任何其他状态,且通常只有一种状态装换
 * 例子:判断业务是否结束
 * @Author: yangyb
 * @Date:2022/10/7 17:22
 * Version: 1.0
 **/
public class UseVolatileDemo {

    private volatile static boolean flog=true;

    public static void main(String[] args){
        new Thread(()->{
            while (flog){
                System.out.println("do something");
            }
        },"t1").start();

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

        new Thread(()->{
            flog=false;
        },"t2").start();
    }

}
  1. 开销较低的读、写锁策略
    /*
    * 使用:当读远多于写,结合使用内部锁和volatile变量来减少同步的开销
    * 理由:利用volatile保证读取操作的可见性,利用synchronized保证复合操作的原子性
    * */

    public class Counter{
        private volatile int value;

        public int getValue(){
            return value; // 利用volatile保证读取操作的可见性
        }

        public synchronized int increment(){
            return value++; // 利用synchronized保证复合操作的原子性
        }
    }
  1. DCL双检锁的发布
    在这里插入图片描述
    单线程:
    在这里插入图片描述
    多线程:
    在这里插入图片描述
    在这里插入图片描述
    解决方法:
    在这里插入图片描述

9.7、总结

在这里插入图片描述

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

10、CAS

10.1、CAS简介

10.1.1、说明

  • compare and swap的缩写,中文翻译成比较并交换,实现并发算法时场用到的一种技术。它包含三个操作数——内存位置、预期原值及更新值。
  • 执行CAS操作的时候,将内存位置的值与预期原值比较:如果相匹配,那么处理器会自动将该位置值更新为新值,如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功。

10.1.2、原理
CAS有三个操作数,位置内存值V,旧的预期值A,要修改的更新值B。当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或重来。
在这里插入图片描述
10.1.2、硬件级别保证
在这里插入图片描述
源码分析
在这里插入图片描述


    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

问题:Unsafe类是什么?

10.2、CAS底层原理-Unsafe类

10.2.1、源码分析

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

在这里插入图片描述

10.2.2、总结
在这里插入图片描述

10.3、CAS之原子引用 AtomicReference

import java.util.concurrent.atomic.AtomicReference;

/**
 * @Description: 原子引用
 * @Author: yangyb
 * @Date:2022/10/7 21:17
 * Version: 1.0
 **/

class User{
    private int age;
    private String name;

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

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

public class AtomicReferenceDemo {

    public static void main(String[] args){

        AtomicReference<User> userAtomicReference = new AtomicReference<>();
        User zhangSan = new User(10, "张三");
        User liSi = new User(12, "李四");

        userAtomicReference.set(zhangSan);
        System.out.println(userAtomicReference.compareAndSet(zhangSan,liSi)+"\t"+userAtomicReference.get().toString());
        System.out.println(userAtomicReference.compareAndSet(zhangSan,liSi)+"\t"+userAtomicReference.get().toString());

    }

}

在这里插入图片描述

10.4、CAS与自旋锁

10.4.1、简介

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

10.4.2、手写自旋锁,借鉴CAS思想

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

/**
 * @Description: 自旋锁实现
 * 目标:实现一个自旋锁,复习CAS思想
 * 自旋锁的好处:循环比较获取没有类似wait的阻塞
 * 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟,B线程随后进来发现当前线程持有锁,
 * 所以只能通过自旋等待,直到A释放锁后B随后抢到
 * @Author: yangyb
 * @Date:2022/10/8 20:50
 * Version: 1.0
 **/
public class SpinLockDemo {

    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void lock() {
        // 获取当前线程
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t 线程come in!");
        while (!atomicReference.compareAndSet(null, thread)) {
            System.out.println(Thread.currentThread().getName() + "\t 线程 自旋中!,等待A线程释放锁");
        }
        System.out.println(Thread.currentThread().getName() + "\t 线程 自旋结束");
    }

    public void unlock() {
        // 获取当前线程
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "\t task over,unlock!");
    }

    public static void main(String[] args) {

        SpinLockDemo spinLockDemo = new SpinLockDemo();
        new Thread(() -> {
            spinLockDemo.lock();
            // 休息5秒钟
            try {
                TimeUnit.MILLISECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.unlock();
        }, "A").start();

        // 休息1秒钟,保证A线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            spinLockDemo.lock();
            // 休息5秒钟
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.unlock();
        }, "B").start();

    }

}

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

10.5、CAS两大缺点

10.5.1、循环时间长,CPU开销大
在这里插入图片描述

10.5.2、ABA问题的产生
在这里插入图片描述

10.6、邮戳AtomicStampedReference

(1)没有用AtomicStampedReference,会发生ABA问题

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

/**
 * @Description: ABADemo
 * @Author: yangyb
 * @Date:2022/10/8 21:48
 * Version: 1.0
 **/
public class ABADemo {

    static AtomicInteger atomicInteger = new AtomicInteger(100);

    public static void main(String[] args) {

        new Thread(() -> {
            atomicInteger.compareAndSet(100, 101);
            try {
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicInteger.compareAndSet(101, 100);
        }, "A").start();


        new Thread(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicInteger.compareAndSet(100, 2022) + "\t" + atomicInteger.get());
        }, "B").start();
    }
}

A线程中间悄无声息的替换了数据,B没有
在这里插入图片描述
(2)加上AtomicStampedReference,解决ABA问题

/**
 * @Description: ABADemo
 * @Author: yangyb
 * @Date:2022/10/8 21:48
 * Version: 1.0
 **/
public class ABADemo {

    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference stampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {

        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号:" + stamp);
            //暂停一下,保证后面的B线程拿到的版本号和我一样
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stampedReference.compareAndSet(100, 101, stampedReference.getStamp(), stampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t" + "2次版本号:" + stampedReference.getStamp());
            stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t" + "3次版本号:" + stampedReference.getStamp());
        }, "A").start();


        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号:" + stamp);
            // 暂停一秒钟,等待上面的t3线程,发生ABA问题
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean b = stampedReference.compareAndSet(100, 202, stamp, stampedReference.getStamp() + 1);
            System.out.println(b + "\t" + stampedReference.getReference() + "\t" + stampedReference.getStamp());
        }, "B").start();
    }
}

在这里插入图片描述

11、原子类

在这里插入图片描述

11.1、基本类型原子类

  1. AtomicInterger
  2. AtomicBoolean
  3. AtomicLong
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Description: TODO
 * @Author: yangyb
 * @Date:2022/10/9 22:00
 * Version: 1.0
 **/

class MyNumber {
    AtomicInteger atomicInteger = new AtomicInteger();

    public void addPlusPlus() {
        atomicInteger.getAndIncrement();
    }
}

public class AtomicIntegerDemo {

    public static final int SIZE = 50;

    public static void main(String[] args) throws InterruptedException {
        MyNumber myNumber = new MyNumber();
        CountDownLatch countDownLatch = new CountDownLatch(SIZE);
        for (int i = 1; i < SIZE; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j < 1000; j++) {
                        myNumber.addPlusPlus();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }

        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t" + "result: " + myNumber.atomicInteger.get());
    }
}

11.2、数组类型原子类

  1. AtomicIntegerArray
  2. AtomicLongArray
  3. AtomicReferenceArray
    在这里插入图片描述

11.3、引用类型原子类

  1. AtomicReference

  2. AtomicStampedReference
    在这里插入图片描述

  3. AtomicMarkableReference

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

11.4、对象的属性修改原子类

  1. AtomicIntegerFieldUpdater
  2. AtomicLongFieldUpdater
  3. AtomicReferenceFieldUpdater
    在这里插入图片描述

在这里插入图片描述

11.5、原子操作增强类

在这里插入图片描述

在这里插入图片描述

12、ThreadLocal

12.1、简介

ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实列的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(列入,用户ID或事物ID)与线程关联起来。
在这里插入图片描述

/**
 * @Description: ThreadLocal
 * @Author: yangyb
 * @Date:2022/10/10 21:58
 * Version: 1.0
 **/
// 资源类
class House{
     int saleCount =0;
     public synchronized void saleHouse(){
         ++saleCount;
     }
     ThreadLocal<Integer> saleVolume=ThreadLocal.withInitial(()->0);
     public void saleVolumeByThreadLocal(){
         saleVolume.set(1+saleVolume.get());
     }
}
public class ThreadLocalDemo {

    public static void main(String[] args){

        House house = new House();
        for(int i=1;i<=5;i++){
            new Thread(()->{
                try {
                    int size=new Random().nextInt(5)+1;
                    for(int j=1;j<=size;j++){
                        house.saleHouse();
                        house.saleVolumeByThreadLocal();
                    }
                    System.out.println(Thread.currentThread().getName()+"\t"+"共计卖出多少套:"+house.saleVolume.get());
                }finally {
                    house.saleVolume.remove();
                }

            },String.valueOf(i)).start();
        }

        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"\t"+"共计卖出多少套:"+house.saleCount);
    }

}

在这里插入图片描述

12.2、ThreadLocal规范要求

在这里插入图片描述

/**
 * @Description: ThreadLocal规范
 * @Author: yangyb
 * @Date:2022/10/10 22:37
 * Version: 1.0
 **/
class MyData{
    ThreadLocal<Integer> threadLocalField=ThreadLocal.withInitial(()->0);
    public void add(){
        threadLocalField.set(1+threadLocalField.get());
    }
}
public class ThreadLocalDemoTwo {

    public static void main(String[] args){

        MyData myData = new MyData();

        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        try {
            for(int i=0;i<10;i++){
                threadPool.submit(()->{
                    Integer beforeInteger = myData.threadLocalField.get();
                    myData.add();
                    Integer afterInteger = myData.threadLocalField.get();
                    System.out.println(Thread.currentThread().getName()+"\t"+"beforeInteger:"+beforeInteger+"\t"+"afterInteger:"+afterInteger);
                });
            }
        }finally {
            threadPool.shutdown();
        }


    }

}

在这里插入图片描述
使用remove()方法以后:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Description: ThreadLocal规范
 * @Author: yangyb
 * @Date:2022/10/10 22:37
 * Version: 1.0
 **/
class MyData{
    ThreadLocal<Integer> threadLocalField=ThreadLocal.withInitial(()->0);
    public void add(){
        threadLocalField.set(1+threadLocalField.get());
    }
}
public class ThreadLocalDemoTwo {

    public static void main(String[] args){

        MyData myData = new MyData();

        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        try {
            for(int i=0;i<10;i++){
                threadPool.submit(()->{
                    try {
                        Integer beforeInteger = myData.threadLocalField.get();
                        myData.add();
                        Integer afterInteger = myData.threadLocalField.get();
                        System.out.println(Thread.currentThread().getName()+"\t"+"beforeInteger:"+beforeInteger+"\t"+"afterInteger:"+afterInteger);
                    }finally {
                        myData.threadLocalField.remove();
                    }

                });
            }
        }finally {
            threadPool.shutdown();
        }


    }

}

在这里插入图片描述

12.3、总结

在这里插入图片描述

12.4、Thread、ThreadLocal、ThreadLocalMap三者的关系

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

在这里插入图片描述

12.5、弱引用的引出

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

12.6、强引用

在这里插入图片描述

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

12.7、软引用

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

12.8、弱引用

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

在这里插入图片描述

12.9、虚引用

在这里插入图片描述

12.10、四中引用总结

在这里插入图片描述

12.11、ThreadLocal为何用弱引用

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

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

在这里插入图片描述

在这里插入图片描述

12.12、总结

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

13、对象内存布局和对象头

13.1、简介

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

13.2、对象头

1、对象标记Mark Word
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总结:
在这里插入图片描述

2、类元信息(又叫类型指针)
在这里插入图片描述
总结:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。

3、对象头有多大?
在这里插入图片描述
在这里插入图片描述

13.3、实例数据和对齐填充

1、实例数据

在这里插入图片描述

2、对齐填充

在这里插入图片描述

13.4、对象分代年龄

在这里插入图片描述

14、synchronized与锁升级

14.1、简介

在这里插入图片描述
synchronized锁:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14.2、synchronized锁升级流程

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

14.3、无锁

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

14.4、偏向锁

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
理论实现:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
案例说明:
在这里插入图片描述
在这里插入图片描述
重要参数说明:
在这里插入图片描述
在这里插入图片描述
代码示例:
在这里插入图片描述

在这里插入图片描述

14.5、偏向锁撤销

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

在这里插入图片描述
偏向锁废弃:
在这里插入图片描述

14.6、轻量级锁

在这里插入图片描述
在这里插入图片描述
轻量级锁的获取:
在这里插入图片描述

在这里插入图片描述
补充:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14.7、重锁

在这里插入图片描述

14.8、锁升级后和hashCode的关系

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

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

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

14.9、各种锁优缺点总结

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

14.10、锁消除

在这里插入图片描述

14.11、锁粗化

在这里插入图片描述

14.12、总结

在这里插入图片描述

15、AQS

15.1、简介

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

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

15.2、AQS作用

15.2.1、源码查看

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

15.3、AQS之state和LCH队列

在这里插入图片描述

15.4、AQS自身属性和Node节点

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

在这里插入图片描述

15.5、AQS源码分析

在这里插入图片描述

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

15.5.1、非公平锁
在这里插入图片描述
在这里插入图片描述

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

15.6、AQS源码总结

。。。。。。。。。。。。。。。。。。。。。。。。。。。

16、读写锁

16.1、简介

在这里插入图片描述

16.2、锁演化

在这里插入图片描述

在这里插入图片描述

16.2.1、读写锁的意义和特点
在这里插入图片描述

16.3、锁降级

在这里插入图片描述

16.4、不可锁升级

在这里插入图片描述
在这里插入图片描述
结论:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

17、stampedLock

17.1、简介

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

17.2、stampedLock锁特点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
缺点:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/1292.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode刷题之回文链表

目录 做题思路 代码实现 1.找到链表的中间节点 2.反转中间节点之后的链表 3.判断倒置的后半部分的链表是否等于前半部分的链表 整体代码展示 总结&#xff1a; 这里是题目链接。 这道题目的意思是&#xff1a;判断该链表中后半部分倒置是否跟前半部分相同&#xff0c;如…

java 每日一练 (8)

文章目录1. 单选题2. 编程题1. 单选题 1. 下列选项中关于 java 中 super 关键字的说法正确的是 () A&#xff1a; super 关键字是在子类对象内部指代父类对象的引用. B &#xff1a; super 关键字不仅可以指代子类的直接父类&#xff0c;还可以直接指代父类的父类. C &#…

API-Server的监听器Controller的List分页失效

前言 最近做项目&#xff0c;还是K8S的插件监听器&#xff08;理论上插件都是通过API-server通信&#xff09;&#xff0c;官方的不同写法居然都能出现争议&#xff0c;争议点就是对API-Server的请求的耗时&#xff0c;说是会影响API-Server。实际上通过源码分析两着有差别&am…

<script>标签在html中书写位置-课后程序(JavaScript前端开发案例教程-黑马程序员编著-第1章-课后作业)

【案例1-1】 <script>标签在html中书写位置 一、案例描述 考核知识点 <script>标签可以放在html中什么位置 练习目标 掌握<script>标签放在页面中不同位置的区别。 需求分析 将JavaScript标识放置<Head>... </Head>在头部之间&#xff0c;使之…

LInux指令之文件目录类

文章目录一、帮助指令二、文件目录类ls指令cd指令 &#xff08;切换目录&#xff09;mkdir指令&#xff08;创建目录&#xff09;rmdir指令&#xff08;删除目录&#xff09;touch指令&#xff08;创建空文件&#xff09;cp指令(拷贝文件)rm指令mv指令cat指令(查看)more指令les…

GEE:计算1990-2021年的指数最大值和最小值,并根据最大最小值对每一副影像归一化

本文记录了在GEE平台上计算影像集合中所有像素的最大值和最小值。并且根据该最大最小值对所有影像进行最大最小值归一化。以SAVI为例,记录了主要函数的使用方法和代码。 结果如图所示, 文章目录 一、计算每一副影像的最大值或者最小值,并将最值保存在 List 中二、计算 Lis…

AD域安全攻防实践(附攻防矩阵图)

以域控为基础架构&#xff0c;通过域控实现对用户和计算机资源的统一管理&#xff0c;带来便利的同时也成为了最受攻击者重点攻击的集权系统。 01、攻击篇 针对域控的攻击技术&#xff0c;在Windows通用攻击技术的基础上自成一套技术体系&#xff0c;将AD域攻防分为信息收集、权…

安装Docker

Docker分为CE和EE两大版本。CE即社区版&#xff08;免费&#xff0c;支持周期7个月&#xff09;&#xff0c;EE即企业版&#xff0c;强调安全&#xff0c;付费使用&#xff0c;支持周期 24 个月。 Docker CE 分为 stable test 和 nightly 三个更新频道。 官方网站上有各种环境…

Nacos 注册中心 - 健康检查机制源码

目录 1. 健康检查介绍 2. 客户端健康检查 2.1 临时实例的健康检查 2.2 永久实例的健康检查 3. 服务端健康检查 3.1 临时实例的健康检查 3.2 永久实例服务端健康检查 1. 健康检查介绍 当一个服务实例注册到 Nacos 中后&#xff0c;其他服务就可以从 Nacos 中查询出该服务…

LeetCode234_234. 回文链表

LeetCode234_234. 回文链表 一、描述 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&…

Day920.结构化日志业务审计日志 -SpringBoot与K8s云原生微服务实践

结构化日志&业务审计日志 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于结构化日志&业务审计日志的内容。 1、什么是结构化日志 结构化日志&#xff08;Structured Logging&#xff09;是一种将日志信息组织为结构化数据的技术。 传统的日志通常是一些文…

UE实现建筑分层抽屉展示效果

文章目录 1.实现目标2.实现过程2.1 基础设置2.2 核心函数3.参考资料1.实现目标 使用时间轴对建筑楼层的位置偏移进行控制,实现分层抽屉的动画展示效果。 2.实现过程 建筑抽屉的实现原理比较简单,即对Actor的位置进行偏移,计算并更新其世界位置即可。这里还是基于ArchVizExp…

Mybatis报BindingException:Invalid bound statement (not found)异常

一、前言 本文的mybatis是与springboot整合时出现的异常&#xff0c;若使用的不是基于springboot&#xff0c;解决思路也大体一样的。 二、从整合mybatis的三个步骤排查问题 但在这之前&#xff0c;我们先要知道整合mybatis的三个重要的工作&#xff0c;如此才能排查&#x…

SDG,ADAM,LookAhead,Lion等优化器的对比介绍

本文将介绍了最先进的深度学习优化方法&#xff0c;帮助神经网络训练得更快&#xff0c;表现得更好。有很多个不同形式的优化器&#xff0c;这里我们只找最基础、最常用、最有效和最新的来介绍。 优化器 首先&#xff0c;让我们定义优化。当我们训练我们的模型以使其表现更好…

MySQL中事务的相关问题

事务 一、事务的概述&#xff1a; 1、事务处理&#xff08;事务操作&#xff09;&#xff1a;保证所有事务都作为一个工作单元来执行&#xff0c;即使出现了故障&#xff0c;都不能改变这种执行方式。当在一个事务中执行多个操作时&#xff0c;要么所有的事务都被提交(commit…

[ROC-RK3568-PC] [Firefly-Android] 10min带你了解Camera的使用

&#x1f347; 博主主页&#xff1a; 【Systemcall小酒屋】&#x1f347; 博主追寻&#xff1a;热衷于用简单的案例讲述复杂的技术&#xff0c;“假传万卷书&#xff0c;真传一案例”&#xff0c;这是林群院士说过的一句话&#xff0c;另外“成就是最好的老师”&#xff0c;技术…

再也不想去字节跳动面试了,6年测开面试遭到这样打击.....

前几天我朋友跟我吐苦水&#xff0c;这波面试又把他打击到了&#xff0c;做了快6年软件测试员。。。为了进大厂&#xff0c;也花了很多时间和精力在面试准备上&#xff0c;也刷了很多题。但题刷多了之后有点怀疑人生&#xff0c;不知道刷的这些题在之后的工作中能不能用到&…

【Python/Opencv】图像权重加法函数:cv2.addWeighted()详解

【Python/Opencv】图像权重加法函数&#xff1a;cv2.addWeighted()详解 文章目录【Python/Opencv】图像权重加法函数&#xff1a;cv2.addWeighted()详解1. 介绍2. API3. 代码示例与效果3.1 代码3.2 效果4. 参考1. 介绍 在OpenCV图像加法cv2.add函数详解详细介绍了图像的加法运…

字符串匹配【BF、KMP算法】

文章目录:star:BF算法代码实现BF的改进思路:star:KMP算法&#x1f6a9;next数组&#x1f6a9;代码实现优化next数组最终代码⭐️BF算法 BF算法&#xff0c;即暴力(Brute Force)算法&#xff0c;是普通的模式匹配算法&#xff0c;BF算法的思想就是将主串S的第一个字符与模式串P…

三、Python 操作 MongoDB ----非 ODM

文章目录一、连接器的安装和配置二、新增文档三、查询文档四、更新文档五、删除文档一、连接器的安装和配置 pymongo&#xff1a; MongoDB 官方提供的 Python 工具包。官方文档&#xff1a; https://pymongo.readthedocs.io/en/stable/ pip安装&#xff0c;命令如下&#xff1…