认为自己在使用数据时不会有别的线程修改数据或资源,所以不会添加锁。
在JAVA中是通过使用无锁编程来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果这个数据已经被其它线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等等。
判断规则:
(1)版本号机制Version
(2) 最常采用的是CAS算法,JAVA原子类中的递增操作就通过CAS自旋实现的。
适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅度提升。
乐观锁直接去操作同步资源,是一种无锁算法,得之我幸不得我命,再努力就是一句话:佛系锁。
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()方法。
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线程释放完类锁,就能能获取到对象锁。
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();}}
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();}}
如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;否则就用公平锁,大家公平使用。
隐式锁(即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可重入的实现原理
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.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.1、在终端使用命令行
jps -l
jstack 17988
5.2.2、图形化
7.1.1、什么是中断机制?
首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运。所以,Thread.stop,Thread.suspend,Thread.resume都已经被废弃了。
其次,在java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制——中断,即中断标识协商机制。
中断只是一种协作协商机制,Java中没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true,此时究竟该做什么需要你自己写代码实现。
每个线程对象中都有一个中断标识位,用于表示 线程是否被中断;该标识位为true表示中断,为false表示未中断;通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
7.1.2、中断线程API常见的三大方法
/*** @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());//falsetry {TimeUnit.MILLISECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}t1.interrupt();//trueSystem.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打断。
结论:
静态方法interrupted将会清除中断状态(传入的参数ClearInterrupted为true),实例方法isInterrupted则不会(传入的参数ClearInterrupted为false)。
4.三大方法对比总结
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、线程等待唤醒机制
(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.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条规则
被volatile修饰的变量有两大特点:可见性和有序性
9.1.1、volatile内存语义
9.1.2、vlolatile如何保证可见性和有序性?
内存屏障(Memory Barrier)
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保证复合操作的原子性}}
10.1.1、说明
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.1、源码分析
10.2.2、总结
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;}@Overridepublic String toString() {return "User{" +"age=" + age +", name='" + name + '\'' +'}';}
}public class AtomicReferenceDemo {public static void main(String[] args){AtomicReference 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.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 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.1、循环时间长,CPU开销大
10.5.2、ABA问题的产生
(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();}
}
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());}
}
AtomicReference
AtomicStampedReference
AtomicMarkableReference
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 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);}}
/*** @Description: ThreadLocal规范* @Author: yangyb* @Date:2022/10/10 22:37* Version: 1.0**/
class MyData{ThreadLocal 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 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();}}}
1、对象标记Mark Word
总结:
2、类元信息(又叫类型指针)
总结:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。
3、对象头有多大?
1、实例数据
2、对齐填充
synchronized锁:
理论实现:
案例说明:
重要参数说明:
代码示例:
偏向锁废弃:
轻量级锁的获取:
补充:
15.2.1、源码查看
15.5.1、非公平锁
。。。。。。。。。。。。。。。。。。。。。。。。。。。
16.2.1、读写锁的意义和特点
结论:
缺点: