1、乐观锁和悲观锁
1.1、悲观锁
- 认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会加锁,确保数据不会别的线程修改。
-
synchronized关键字和Lock的实现类都是悲观锁。
- 适合写操作多的场景,先加锁可以保证写操作时数据正确,显示的锁定之后再造作同步资源。
- 狼性锁
1.2、乐观锁
-
认为自己在使用数据时不会有别的线程修改数据或资源,所以不会添加锁。
-
在JAVA中是通过使用无锁编程来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果这个数据已经被其它线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等等。
-
判断规则:
(1)版本号机制Version
(2)
最常采用的是CAS算法,JAVA原子类中的递增操作就通过CAS自旋实现的。
-
适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅度提升。
-
乐观锁直接去操作同步资源,是一种无锁算法,得之我幸不得我命,再努力就是一句话:佛系锁。
2、线程八锁
- 标准访问有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()方法。
- 添加一个普通的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对象锁。
- 有两个手机对象,请问先打印邮件还是短信?
/**
* @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对象的锁,所以是两个线程拿到了两个不同的对象锁,互不干扰,各自执行各自的。
- 有两个静态同步方法,有一个手机对象,请问先打印邮件还是短信?
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线程释放整个类锁后,才能获得类锁,再执行响应的资源。
- 有一个静态同步方法,有一个普通同步方法,有一个手机对象,请问先打印邮件还是短信?
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线程释放完类锁,就能能获取到对象锁。
- 有一个静态同步方法,有一个普通同步方法,有两个手机对象,请问先打印邮件还是短信?
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、为什么会有公平锁和非公平锁的设计?为什么默认非公平?
- 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从cpu的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU的时间片,尽量减少cpu空闲状态时间。
- 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当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、在终端使用命令行
-
jps -l
-
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)同过一个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打断。
-
静态方法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)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条规则
- 次序规则:一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作。
- 锁定规则:一个unlock操作先行发生于后面(这里的“后面”是指时间上的先后)对同一个锁的lock操作。
- volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的“后面”同样是指时间上的先后。
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出来操作A先行发生于操作C。
- 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。
-
线程中断规则(Thread Interruption Rule):
- 线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过isAlive()等手段检测线程是否已经终止执行。
-
对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
(4)总结
9、volatile
9.1、volatile之两大特性
被volatile修饰的变量有两大特点:可见性和有序性
- 可见性:保证不同线程对某个变量完成操作后的结果对其它线程及时可见,即该共享变量一旦改变所有线程立即可见。
9.1.1、volatile内存语义
9.1.2、vlolatile如何保证可见性和有序性?
内存屏障(Memory Barrier)
9.2、内存屏障(Memory Barrier)
9.3、读写屏障之插入策略
- 读屏障
- 写屏障
-
volatile变量的读写过程:JAVA内存模型中定义的8种每个线程自己的工作内存与主物理内存之间的原子操作。
9.4、volatile无原子性
9.5、指令禁重排
9.6、volatile使用场景
- 单一赋值可以,但是含复合运算赋值不可以(i++)类
volatile int a=10;
volatile boolean flag=false;
- 状态标志,判断业务是否结束
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();
}
}
- 开销较低的读、写锁策略
/*
* 使用:当读远多于写,结合使用内部锁和volatile变量来减少同步的开销
* 理由:利用volatile保证读取操作的可见性,利用synchronized保证复合操作的原子性
* */
public class Counter{
private volatile int value;
public int getValue(){
return value; // 利用volatile保证读取操作的可见性
}
public synchronized int increment(){
return value++; // 利用synchronized保证复合操作的原子性
}
}
-
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、基本类型原子类
- AtomicInterger
- AtomicBoolean
- 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、数组类型原子类
- AtomicIntegerArray
- AtomicLongArray
-
AtomicReferenceArray
11.3、引用类型原子类
-
AtomicReference
-
AtomicStampedReference
-
AtomicMarkableReference
11.4、对象的属性修改原子类
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
-
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锁特点
缺点: