1.什么是线程安全?线程安全会带来哪些底层问题?
2.分析保证线程安全的三个性质-原子性、可见性、有序性
3.多场景剖析未保证原子性带来的问题
package imooc.atomic;public class AtomicTest {public static void main(String[] args) throws InterruptedException {BankAccount bankAccount = new BankAccount(1000);
// Object lock = new Object();//存钱的线程Thread thread = new Thread(() -> {for (int i = 0; i < 3000; i++) {
// synchronized (lock){bankAccount.deposit(100);
// }}});//消费的线程Thread thread1 = new Thread(() -> {for (int i = 0; i < 3000; i++) {
// synchronized (lock){bankAccount.withdraw(100);
// }}});thread.start();thread1.start();thread.join();thread1.join();System.out.println(bankAccount.getBalance());}
}class BankAccount{private int balance;public BankAccount(int balance) {this.balance = balance;}/*** 存钱* @param amount*/public void deposit(int amount){System.out.println();balance+=amount;}/*** 消费* @param amount*/public void withdraw(int amount){balance-=amount;}public int getBalance() {return balance;}
}
期望:
最终还是1000
运行结果:
每次运行结果不一样。
并发执行:
4.如何保证原子性操作?
注意:Atomic类不能完全替代synchronized。
5.synchronized关键字可在哪些地方将代码“锁”住?
解释:
synchronized后面的小括号里,可以放任意对象,叫做锁对象。
代码示例:
package imooc.atomic.lock;public class Executor {Object lock = new Object();public void method1(){synchronized (lock){String threadName = Thread.currentThread().getName();for (int i = 0; i < 5; i++) {System.out.println("Thread "+threadName+" 正在执行");
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }}}}
// public synchronized static void method2(){
// String threadName = Thread.currentThread().getName();
// for (int i = 0; i < 5; i++) {
// System.out.println("Thread "+threadName+" 正在执行");
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// }
// }
}
package imooc.atomic.lock;public class ThreadTestSync extends Thread{Executor executor;
// boolean flag = false;public ThreadTestSync(Executor executor,boolean flag) {this.executor = executor;
// this.flag = flag;}@Overridepublic void run() {
// if(flag){
// executor.method1();
// }else{
// executor.method2();
// }executor.method1();}
}
package imooc.atomic.lock;public class TestCase {public static void main(String[] args) {Executor executor = new Executor();ThreadTestSync testSync = new ThreadTestSync(executor,true);ThreadTestSync testSync1 = new ThreadTestSync(executor,false);testSync.start();testSync1.start();}
}
运行结果:
锁this,也就是当前实例:
运行结果:
不同实例:
运行结果:
锁class对象:所有实例公用一把锁
运行结果:
比如,只需要锁住第一、二、三步:
比如,第三步不需要锁:
如果第三步非常非常耗时间,我们把他提出来,如果第三步只是普通计算,还不如直接放到锁里,因为加锁、解锁非常的耗费性能。
同一实例,锁加在方法上:
运行结果:
不同实例:
同步代码块锁对象是this时,和同步方法使用的是同一把锁:
运行结果:
代码块的锁对象为class时,和修饰静态方法是同一把锁:
运行结果:
6.使用wait和notify方法实现生产者消费者模式
在没有拿到锁的情况下,调用wait()会怎么样?
package imooc.waitnotify;public class TestWaitAndNotify {public static void main(String[] args) throws InterruptedException {Object o = new Object();
// o.wait();o.notify();}}
运行工结果:
所以,我们不能在没拿到锁的情况下,直接调用wait()或notify()。
更好的方法:
具体实现:
运行结果:
7.使用synchronized关键字实现的锁内存存储原理深入剖析
为什么需要Monitor?
java早期为了实现方便,直接使用操作系统提供的能力(互斥锁,也叫重量级锁),如果需要使用重量级锁,就需要从用户态转为内核态。
Monitor实现重量级锁。
java对象是怎么关联Monitor?
对齐填充 - java对象的数据大小,如果刚好能被8整除,就需要对齐填充数据,否则需要补充对齐填充数据,能够被8整除。
代码演示:
package imooc.atomic.lock;import org.openjdk.jol.info.ClassLayout;/*** @Author: Alfred* @ModuleOwner: Alfred* @Description:*/
public class TestSync {public static void main(String[] args) {Object lock = new Object();/*** 使用ClassLayout,* 需要引入* <dependency>* <groupId>org.openjdk.jol</groupId>* <artifactId>jol-core</artifactId>* <version>0.9</version>* </dependency>*//*** System.out.println(ClassLayout.parseInstance(lock).toPrintable());* 运行结果:* "C:\Program Files\Java\jdk-17.0.5\bin\java.exe" "-javaagent:C:\Program_Files\JetBrains\IntelliJ IDEA 2023.2.5\lib\idea_rt.jar=53687:C:\Program_Files\JetBrains\IntelliJ IDEA 2023.2.5\bin" -Dfile.encoding=UTF-8 -classpath E:\git-repository\multipleThread-master\multiplethread\target\classes;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-web\3.0.4\spring-boot-starter-web-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter\3.0.4\spring-boot-starter-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-logging\3.0.4\spring-boot-starter-logging-3.0.4.jar;C:\Users\miloq\.m2\repository\ch\qos\logback\logback-classic\1.4.5\logback-classic-1.4.5.jar;C:\Users\miloq\.m2\repository\ch\qos\logback\logback-core\1.4.5\logback-core-1.4.5.jar;C:\Users\miloq\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.19.0\log4j-to-slf4j-2.19.0.jar;C:\Users\miloq\.m2\repository\org\apache\logging\log4j\log4j-api\2.19.0\log4j-api-2.19.0.jar;C:\Users\miloq\.m2\repository\org\slf4j\jul-to-slf4j\2.0.6\jul-to-slf4j-2.0.6.jar;C:\Users\miloq\.m2\repository\org\yaml\snakeyaml\1.33\snakeyaml-1.33.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-json\3.0.4\spring-boot-starter-json-3.0.4.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.14.2\jackson-databind-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.14.2\jackson-annotations-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.14.2\jackson-core-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.14.2\jackson-datatype-jdk8-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.14.2\jackson-datatype-jsr310-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.14.2\jackson-module-parameter-names-2.14.2.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-web\6.0.6\spring-web-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-beans\6.0.6\spring-beans-6.0.6.jar;C:\Users\miloq\.m2\repository\io\micrometer\micrometer-observation\1.10.4\micrometer-observation-1.10.4.jar;C:\Users\miloq\.m2\repository\io\micrometer\micrometer-commons\1.10.4\micrometer-commons-1.10.4.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-webmvc\6.0.6\spring-webmvc-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-aop\6.0.6\spring-aop-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-context\6.0.6\spring-context-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-expression\6.0.6\spring-expression-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-devtools\3.0.4\spring-boot-devtools-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot\3.0.4\spring-boot-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\3.0.4\spring-boot-autoconfigure-3.0.4.jar;C:\Users\miloq\.m2\repository\org\projectlombok\lombok\1.18.26\lombok-1.18.26.jar;C:\Users\miloq\.m2\repository\jakarta\annotation\jakarta.annotation-api\2.1.1\jakarta.annotation-api-2.1.1.jar;C:\Users\miloq\.m2\repository\org\slf4j\slf4j-api\2.0.6\slf4j-api-2.0.6.jar;C:\Users\miloq\.m2\repository\org\hamcrest\hamcrest\2.2\hamcrest-2.2.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-core\6.0.6\spring-core-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-jcl\6.0.6\spring-jcl-6.0.6.jar;C:\Users\miloq\.m2\repository\com\alibaba\csp\sentinel-core\1.8.6\sentinel-core-1.8.6.jar;C:\Users\miloq\.m2\repository\com\lmax\disruptor\3.4.2\disruptor-3.4.2.jar;C:\Users\miloq\.m2\repository\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar;C:\Users\miloq\.m2\repository\com\alibaba\dubbo\2.5.3\dubbo-2.5.3.jar;C:\Users\miloq\.m2\repository\org\javassist\javassist\3.15.0-GA\javassist-3.15.0-GA.jar;C:\Users\miloq\.m2\repository\org\jboss\netty\netty\3.2.5.Final\netty-3.2.5.Final.jar;C:\Users\miloq\.m2\repository\junit\junit\4.13.2\junit-4.13.2.jar;C:\Users\miloq\.m2\repository\org\hamcrest\hamcrest-core\2.2\hamcrest-core-2.2.jar;C:\Users\miloq\.m2\repository\com\google\guava\guava\32.1.2-jre\guava-32.1.2-jre.jar;C:\Users\miloq\.m2\repository\com\google\guava\failureaccess\1.0.1\failureaccess-1.0.1.jar;C:\Users\miloq\.m2\repository\com\google\guava\listenablefuture\9999.0-empty-to-avoid-conflict-with-guava\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;C:\Users\miloq\.m2\repository\com\google\code\findbugs\jsr305\3.0.2\jsr305-3.0.2.jar;C:\Users\miloq\.m2\repository\org\checkerframework\checker-qual\3.33.0\checker-qual-3.33.0.jar;C:\Users\miloq\.m2\repository\com\google\errorprone\error_prone_annotations\2.18.0\error_prone_annotations-2.18.0.jar;C:\Users\miloq\.m2\repository\com\google\j2objc\j2objc-annotations\2.8\j2objc-annotations-2.8.jar;C:\Users\miloq\.m2\repository\org\apache\commons\commons-lang3\3.13.0\commons-lang3-3.13.0.jar imooc.atomic.lock.TestSync* # WARNING: Unable to get Instrumentation. Dynamic Attach failed. You may add this JAR as -javaagent manually, or supply -Djdk.attach.allowAttachSelf* # WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.memory.Universe.getNarrowOopBase()* java.lang.Object object internals:* OFFSET SIZE TYPE DESCRIPTION VALUE* 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) - markword* 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) - markword* 8 4 (object header) 58 0d 00 00 (01011000 00001101 00000000 00000000) (3416) - classpointer* 12 4 (loss due to the next object alignment) - 对齐填充* Instance size: 16 bytes* Space losses: 0 bytes internal + 4 bytes external = 4 bytes total*** Process finished with exit code 0** 第一行、第二行都是markword* 第三行是classpointer* 因为没有数据,最后一行是对齐填充,markword和classpointer总共是12个byte,不能被8整除,所以对齐填充补充了4个byte*/System.out.println(ClassLayout.parseInstance(lock).toPrintable());
// lock = new Order();
// System.out.println(ClassLayout.parseInstance(lock).toPrintable());
// synchronized (lock){
// System.out.println(ClassLayout.parseInstance(lock).toPrintable());
// }
// Order o = (Order)lock;
// o.setId(1);
// System.out.println(ClassLayout.parseInstance(lock).toPrintable());}
}class Order{private int id;public int getId() {return id;}public void setId(int id) {this.id = id;}
}
现在将lock对象换成Order看一下会有什么变化?
package imooc.atomic.lock;import org.openjdk.jol.info.ClassLayout;/*** @Author: Alfred* @ModuleOwner: Alfred* @Description:*/
public class TestSync {public static void main(String[] args) {Object lock = new Object();/*** 使用ClassLayout,* 需要引入* <dependency>* <groupId>org.openjdk.jol</groupId>* <artifactId>jol-core</artifactId>* <version>0.9</version>* </dependency>*//*** System.out.println(ClassLayout.parseInstance(lock).toPrintable());* 运行结果:* "C:\Program Files\Java\jdk-17.0.5\bin\java.exe" "-javaagent:C:\Program_Files\JetBrains\IntelliJ IDEA 2023.2.5\lib\idea_rt.jar=53687:C:\Program_Files\JetBrains\IntelliJ IDEA 2023.2.5\bin" -Dfile.encoding=UTF-8 -classpath E:\git-repository\multipleThread-master\multiplethread\target\classes;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-web\3.0.4\spring-boot-starter-web-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter\3.0.4\spring-boot-starter-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-logging\3.0.4\spring-boot-starter-logging-3.0.4.jar;C:\Users\miloq\.m2\repository\ch\qos\logback\logback-classic\1.4.5\logback-classic-1.4.5.jar;C:\Users\miloq\.m2\repository\ch\qos\logback\logback-core\1.4.5\logback-core-1.4.5.jar;C:\Users\miloq\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.19.0\log4j-to-slf4j-2.19.0.jar;C:\Users\miloq\.m2\repository\org\apache\logging\log4j\log4j-api\2.19.0\log4j-api-2.19.0.jar;C:\Users\miloq\.m2\repository\org\slf4j\jul-to-slf4j\2.0.6\jul-to-slf4j-2.0.6.jar;C:\Users\miloq\.m2\repository\org\yaml\snakeyaml\1.33\snakeyaml-1.33.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-json\3.0.4\spring-boot-starter-json-3.0.4.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.14.2\jackson-databind-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.14.2\jackson-annotations-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.14.2\jackson-core-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.14.2\jackson-datatype-jdk8-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.14.2\jackson-datatype-jsr310-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.14.2\jackson-module-parameter-names-2.14.2.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-web\6.0.6\spring-web-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-beans\6.0.6\spring-beans-6.0.6.jar;C:\Users\miloq\.m2\repository\io\micrometer\micrometer-observation\1.10.4\micrometer-observation-1.10.4.jar;C:\Users\miloq\.m2\repository\io\micrometer\micrometer-commons\1.10.4\micrometer-commons-1.10.4.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-webmvc\6.0.6\spring-webmvc-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-aop\6.0.6\spring-aop-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-context\6.0.6\spring-context-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-expression\6.0.6\spring-expression-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-devtools\3.0.4\spring-boot-devtools-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot\3.0.4\spring-boot-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\3.0.4\spring-boot-autoconfigure-3.0.4.jar;C:\Users\miloq\.m2\repository\org\projectlombok\lombok\1.18.26\lombok-1.18.26.jar;C:\Users\miloq\.m2\repository\jakarta\annotation\jakarta.annotation-api\2.1.1\jakarta.annotation-api-2.1.1.jar;C:\Users\miloq\.m2\repository\org\slf4j\slf4j-api\2.0.6\slf4j-api-2.0.6.jar;C:\Users\miloq\.m2\repository\org\hamcrest\hamcrest\2.2\hamcrest-2.2.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-core\6.0.6\spring-core-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-jcl\6.0.6\spring-jcl-6.0.6.jar;C:\Users\miloq\.m2\repository\com\alibaba\csp\sentinel-core\1.8.6\sentinel-core-1.8.6.jar;C:\Users\miloq\.m2\repository\com\lmax\disruptor\3.4.2\disruptor-3.4.2.jar;C:\Users\miloq\.m2\repository\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar;C:\Users\miloq\.m2\repository\com\alibaba\dubbo\2.5.3\dubbo-2.5.3.jar;C:\Users\miloq\.m2\repository\org\javassist\javassist\3.15.0-GA\javassist-3.15.0-GA.jar;C:\Users\miloq\.m2\repository\org\jboss\netty\netty\3.2.5.Final\netty-3.2.5.Final.jar;C:\Users\miloq\.m2\repository\junit\junit\4.13.2\junit-4.13.2.jar;C:\Users\miloq\.m2\repository\org\hamcrest\hamcrest-core\2.2\hamcrest-core-2.2.jar;C:\Users\miloq\.m2\repository\com\google\guava\guava\32.1.2-jre\guava-32.1.2-jre.jar;C:\Users\miloq\.m2\repository\com\google\guava\failureaccess\1.0.1\failureaccess-1.0.1.jar;C:\Users\miloq\.m2\repository\com\google\guava\listenablefuture\9999.0-empty-to-avoid-conflict-with-guava\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;C:\Users\miloq\.m2\repository\com\google\code\findbugs\jsr305\3.0.2\jsr305-3.0.2.jar;C:\Users\miloq\.m2\repository\org\checkerframework\checker-qual\3.33.0\checker-qual-3.33.0.jar;C:\Users\miloq\.m2\repository\com\google\errorprone\error_prone_annotations\2.18.0\error_prone_annotations-2.18.0.jar;C:\Users\miloq\.m2\repository\com\google\j2objc\j2objc-annotations\2.8\j2objc-annotations-2.8.jar;C:\Users\miloq\.m2\repository\org\apache\commons\commons-lang3\3.13.0\commons-lang3-3.13.0.jar imooc.atomic.lock.TestSync* # WARNING: Unable to get Instrumentation. Dynamic Attach failed. You may add this JAR as -javaagent manually, or supply -Djdk.attach.allowAttachSelf* # WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.memory.Universe.getNarrowOopBase()* java.lang.Object object internals:* OFFSET SIZE TYPE DESCRIPTION VALUE* 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) - markword* 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) - markword* 8 4 (object header) 58 0d 00 00 (01011000 00001101 00000000 00000000) (3416) - classpointer* 12 4 (loss due to the next object alignment) - 对齐填充* Instance size: 16 bytes* Space losses: 0 bytes internal + 4 bytes external = 4 bytes total*** Process finished with exit code 0** 第一行、第二行都是markword* 第三行是classpointer* 因为没有数据,最后一行是对齐填充,markword和classpointer总共是12个byte,不能被8整除,所以对齐填充补充了4个byte*/System.out.println(ClassLayout.parseInstance(lock).toPrintable());lock = new Order();
// System.out.println(ClassLayout.parseInstance(lock).toPrintable());synchronized (lock){System.out.println(ClassLayout.parseInstance(lock).toPrintable());}
// Order o = (Order)lock;
// o.setId(1);
// System.out.println(ClassLayout.parseInstance(lock).toPrintable());}
}class Order{private int id;public int getId() {return id;}public void setId(int id) {this.id = id;}
}
运行结果:
"C:\Program Files\Java\jdk-17.0.5\bin\java.exe" "-javaagent:C:\Program_Files\JetBrains\IntelliJ IDEA 2023.2.5\lib\idea_rt.jar=55021:C:\Program_Files\JetBrains\IntelliJ IDEA 2023.2.5\bin" -Dfile.encoding=UTF-8 -classpath E:\git-repository\multipleThread-master\multiplethread\target\classes;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-web\3.0.4\spring-boot-starter-web-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter\3.0.4\spring-boot-starter-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-logging\3.0.4\spring-boot-starter-logging-3.0.4.jar;C:\Users\miloq\.m2\repository\ch\qos\logback\logback-classic\1.4.5\logback-classic-1.4.5.jar;C:\Users\miloq\.m2\repository\ch\qos\logback\logback-core\1.4.5\logback-core-1.4.5.jar;C:\Users\miloq\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.19.0\log4j-to-slf4j-2.19.0.jar;C:\Users\miloq\.m2\repository\org\apache\logging\log4j\log4j-api\2.19.0\log4j-api-2.19.0.jar;C:\Users\miloq\.m2\repository\org\slf4j\jul-to-slf4j\2.0.6\jul-to-slf4j-2.0.6.jar;C:\Users\miloq\.m2\repository\org\yaml\snakeyaml\1.33\snakeyaml-1.33.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-json\3.0.4\spring-boot-starter-json-3.0.4.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.14.2\jackson-databind-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.14.2\jackson-annotations-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.14.2\jackson-core-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.14.2\jackson-datatype-jdk8-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.14.2\jackson-datatype-jsr310-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.14.2\jackson-module-parameter-names-2.14.2.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-web\6.0.6\spring-web-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-beans\6.0.6\spring-beans-6.0.6.jar;C:\Users\miloq\.m2\repository\io\micrometer\micrometer-observation\1.10.4\micrometer-observation-1.10.4.jar;C:\Users\miloq\.m2\repository\io\micrometer\micrometer-commons\1.10.4\micrometer-commons-1.10.4.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-webmvc\6.0.6\spring-webmvc-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-aop\6.0.6\spring-aop-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-context\6.0.6\spring-context-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-expression\6.0.6\spring-expression-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-devtools\3.0.4\spring-boot-devtools-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot\3.0.4\spring-boot-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\3.0.4\spring-boot-autoconfigure-3.0.4.jar;C:\Users\miloq\.m2\repository\org\projectlombok\lombok\1.18.26\lombok-1.18.26.jar;C:\Users\miloq\.m2\repository\jakarta\annotation\jakarta.annotation-api\2.1.1\jakarta.annotation-api-2.1.1.jar;C:\Users\miloq\.m2\repository\org\slf4j\slf4j-api\2.0.6\slf4j-api-2.0.6.jar;C:\Users\miloq\.m2\repository\org\hamcrest\hamcrest\2.2\hamcrest-2.2.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-core\6.0.6\spring-core-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-jcl\6.0.6\spring-jcl-6.0.6.jar;C:\Users\miloq\.m2\repository\com\alibaba\csp\sentinel-core\1.8.6\sentinel-core-1.8.6.jar;C:\Users\miloq\.m2\repository\com\lmax\disruptor\3.4.2\disruptor-3.4.2.jar;C:\Users\miloq\.m2\repository\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar;C:\Users\miloq\.m2\repository\com\alibaba\dubbo\2.5.3\dubbo-2.5.3.jar;C:\Users\miloq\.m2\repository\org\javassist\javassist\3.15.0-GA\javassist-3.15.0-GA.jar;C:\Users\miloq\.m2\repository\org\jboss\netty\netty\3.2.5.Final\netty-3.2.5.Final.jar;C:\Users\miloq\.m2\repository\junit\junit\4.13.2\junit-4.13.2.jar;C:\Users\miloq\.m2\repository\org\hamcrest\hamcrest-core\2.2\hamcrest-core-2.2.jar;C:\Users\miloq\.m2\repository\com\google\guava\guava\32.1.2-jre\guava-32.1.2-jre.jar;C:\Users\miloq\.m2\repository\com\google\guava\failureaccess\1.0.1\failureaccess-1.0.1.jar;C:\Users\miloq\.m2\repository\com\google\guava\listenablefuture\9999.0-empty-to-avoid-conflict-with-guava\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;C:\Users\miloq\.m2\repository\com\google\code\findbugs\jsr305\3.0.2\jsr305-3.0.2.jar;C:\Users\miloq\.m2\repository\org\checkerframework\checker-qual\3.33.0\checker-qual-3.33.0.jar;C:\Users\miloq\.m2\repository\com\google\errorprone\error_prone_annotations\2.18.0\error_prone_annotations-2.18.0.jar;C:\Users\miloq\.m2\repository\com\google\j2objc\j2objc-annotations\2.8\j2objc-annotations-2.8.jar;C:\Users\miloq\.m2\repository\org\apache\commons\commons-lang3\3.13.0\commons-lang3-3.13.0.jar imooc.atomic.lock.TestSync
# WARNING: Unable to get Instrumentation. Dynamic Attach failed. You may add this JAR as -javaagent manually, or supply -Djdk.attach.allowAttachSelf
# WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.memory.Universe.getNarrowOopBase()
java.lang.Object object internals:OFFSET SIZE TYPE DESCRIPTION VALUE0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) 58 0d 00 00 (01011000 00001101 00000000 00000000) (3416)12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes totalimooc.atomic.lock.Order object internals:OFFSET SIZE TYPE DESCRIPTION VALUE0 4 (object header) 00 f4 bf ef (00000000 11110100 10111111 11101111) (-272632832)4 4 (object header) 0e 00 00 00 (00001110 00000000 00000000 00000000) (14)8 4 (object header) 08 d6 c1 00 (00001000 11010110 11000001 00000000) (12703240)12 4 int Order.id 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes totalProcess finished with exit code 0
可以看到,markword发生了变化,指向了Monitor对象的一个指针。
现在给Order的id赋值,看下有什么变化?
package imooc.atomic.lock;import org.openjdk.jol.info.ClassLayout;/*** @Author: Alfred* @ModuleOwner: Alfred* @Description:*/
public class TestSync {public static void main(String[] args) {Object lock = new Object();/*** 使用ClassLayout,* 需要引入* <dependency>* <groupId>org.openjdk.jol</groupId>* <artifactId>jol-core</artifactId>* <version>0.9</version>* </dependency>*//*** System.out.println(ClassLayout.parseInstance(lock).toPrintable());* 运行结果:* "C:\Program Files\Java\jdk-17.0.5\bin\java.exe" "-javaagent:C:\Program_Files\JetBrains\IntelliJ IDEA 2023.2.5\lib\idea_rt.jar=53687:C:\Program_Files\JetBrains\IntelliJ IDEA 2023.2.5\bin" -Dfile.encoding=UTF-8 -classpath E:\git-repository\multipleThread-master\multiplethread\target\classes;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-web\3.0.4\spring-boot-starter-web-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter\3.0.4\spring-boot-starter-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-logging\3.0.4\spring-boot-starter-logging-3.0.4.jar;C:\Users\miloq\.m2\repository\ch\qos\logback\logback-classic\1.4.5\logback-classic-1.4.5.jar;C:\Users\miloq\.m2\repository\ch\qos\logback\logback-core\1.4.5\logback-core-1.4.5.jar;C:\Users\miloq\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.19.0\log4j-to-slf4j-2.19.0.jar;C:\Users\miloq\.m2\repository\org\apache\logging\log4j\log4j-api\2.19.0\log4j-api-2.19.0.jar;C:\Users\miloq\.m2\repository\org\slf4j\jul-to-slf4j\2.0.6\jul-to-slf4j-2.0.6.jar;C:\Users\miloq\.m2\repository\org\yaml\snakeyaml\1.33\snakeyaml-1.33.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-json\3.0.4\spring-boot-starter-json-3.0.4.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.14.2\jackson-databind-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.14.2\jackson-annotations-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.14.2\jackson-core-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.14.2\jackson-datatype-jdk8-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.14.2\jackson-datatype-jsr310-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.14.2\jackson-module-parameter-names-2.14.2.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-web\6.0.6\spring-web-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-beans\6.0.6\spring-beans-6.0.6.jar;C:\Users\miloq\.m2\repository\io\micrometer\micrometer-observation\1.10.4\micrometer-observation-1.10.4.jar;C:\Users\miloq\.m2\repository\io\micrometer\micrometer-commons\1.10.4\micrometer-commons-1.10.4.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-webmvc\6.0.6\spring-webmvc-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-aop\6.0.6\spring-aop-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-context\6.0.6\spring-context-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-expression\6.0.6\spring-expression-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-devtools\3.0.4\spring-boot-devtools-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot\3.0.4\spring-boot-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\3.0.4\spring-boot-autoconfigure-3.0.4.jar;C:\Users\miloq\.m2\repository\org\projectlombok\lombok\1.18.26\lombok-1.18.26.jar;C:\Users\miloq\.m2\repository\jakarta\annotation\jakarta.annotation-api\2.1.1\jakarta.annotation-api-2.1.1.jar;C:\Users\miloq\.m2\repository\org\slf4j\slf4j-api\2.0.6\slf4j-api-2.0.6.jar;C:\Users\miloq\.m2\repository\org\hamcrest\hamcrest\2.2\hamcrest-2.2.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-core\6.0.6\spring-core-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-jcl\6.0.6\spring-jcl-6.0.6.jar;C:\Users\miloq\.m2\repository\com\alibaba\csp\sentinel-core\1.8.6\sentinel-core-1.8.6.jar;C:\Users\miloq\.m2\repository\com\lmax\disruptor\3.4.2\disruptor-3.4.2.jar;C:\Users\miloq\.m2\repository\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar;C:\Users\miloq\.m2\repository\com\alibaba\dubbo\2.5.3\dubbo-2.5.3.jar;C:\Users\miloq\.m2\repository\org\javassist\javassist\3.15.0-GA\javassist-3.15.0-GA.jar;C:\Users\miloq\.m2\repository\org\jboss\netty\netty\3.2.5.Final\netty-3.2.5.Final.jar;C:\Users\miloq\.m2\repository\junit\junit\4.13.2\junit-4.13.2.jar;C:\Users\miloq\.m2\repository\org\hamcrest\hamcrest-core\2.2\hamcrest-core-2.2.jar;C:\Users\miloq\.m2\repository\com\google\guava\guava\32.1.2-jre\guava-32.1.2-jre.jar;C:\Users\miloq\.m2\repository\com\google\guava\failureaccess\1.0.1\failureaccess-1.0.1.jar;C:\Users\miloq\.m2\repository\com\google\guava\listenablefuture\9999.0-empty-to-avoid-conflict-with-guava\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;C:\Users\miloq\.m2\repository\com\google\code\findbugs\jsr305\3.0.2\jsr305-3.0.2.jar;C:\Users\miloq\.m2\repository\org\checkerframework\checker-qual\3.33.0\checker-qual-3.33.0.jar;C:\Users\miloq\.m2\repository\com\google\errorprone\error_prone_annotations\2.18.0\error_prone_annotations-2.18.0.jar;C:\Users\miloq\.m2\repository\com\google\j2objc\j2objc-annotations\2.8\j2objc-annotations-2.8.jar;C:\Users\miloq\.m2\repository\org\apache\commons\commons-lang3\3.13.0\commons-lang3-3.13.0.jar imooc.atomic.lock.TestSync* # WARNING: Unable to get Instrumentation. Dynamic Attach failed. You may add this JAR as -javaagent manually, or supply -Djdk.attach.allowAttachSelf* # WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.memory.Universe.getNarrowOopBase()* java.lang.Object object internals:* OFFSET SIZE TYPE DESCRIPTION VALUE* 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) - markword* 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) - markword* 8 4 (object header) 58 0d 00 00 (01011000 00001101 00000000 00000000) (3416) - classpointer* 12 4 (loss due to the next object alignment) - 对齐填充* Instance size: 16 bytes* Space losses: 0 bytes internal + 4 bytes external = 4 bytes total*** Process finished with exit code 0** 第一行、第二行都是markword* 第三行是classpointer* 因为没有数据,最后一行是对齐填充,markword和classpointer总共是12个byte,不能被8整除,所以对齐填充补充了4个byte*/System.out.println(ClassLayout.parseInstance(lock).toPrintable());lock = new Order();
// System.out.println(ClassLayout.parseInstance(lock).toPrintable());
// synchronized (lock){
// System.out.println(ClassLayout.parseInstance(lock).toPrintable());
// }Order o = (Order)lock;o.setId(1);System.out.println(ClassLayout.parseInstance(lock).toPrintable());}
}class Order{private int id;public int getId() {return id;}public void setId(int id) {this.id = id;}
}
运行结果:
解释:
线程1尝试获取锁,如果获取到了锁,会把自己设置为Monitor对象的owner变量,代表Thread1线程正在执行,如果其他线程来获取锁,就获取不到锁,会进入到EnterList里边,如果某个线程在执行的时候,调用了join()或wait()方法,那么线程会进入到Monitor对象的WaitSet里,当一个线程执行完毕,退出并释放Monitor锁。
解释:
java对象里的markword的指针,指向的就是对象关联的Monitor。
通过运行并反编译下面代码查看:
package imooc.atomic.lock;import org.openjdk.jol.info.ClassLayout;/*** @Author: Alfred* @ModuleOwner: Alfred* @Description:*/
public class TestSync {public static void main(String[] args) {Object lock = new Object();/*** 使用ClassLayout,* 需要引入* <dependency>* <groupId>org.openjdk.jol</groupId>* <artifactId>jol-core</artifactId>* <version>0.9</version>* </dependency>*//*** System.out.println(ClassLayout.parseInstance(lock).toPrintable());* 运行结果:* "C:\Program Files\Java\jdk-17.0.5\bin\java.exe" "-javaagent:C:\Program_Files\JetBrains\IntelliJ IDEA 2023.2.5\lib\idea_rt.jar=53687:C:\Program_Files\JetBrains\IntelliJ IDEA 2023.2.5\bin" -Dfile.encoding=UTF-8 -classpath E:\git-repository\multipleThread-master\multiplethread\target\classes;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-web\3.0.4\spring-boot-starter-web-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter\3.0.4\spring-boot-starter-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-logging\3.0.4\spring-boot-starter-logging-3.0.4.jar;C:\Users\miloq\.m2\repository\ch\qos\logback\logback-classic\1.4.5\logback-classic-1.4.5.jar;C:\Users\miloq\.m2\repository\ch\qos\logback\logback-core\1.4.5\logback-core-1.4.5.jar;C:\Users\miloq\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.19.0\log4j-to-slf4j-2.19.0.jar;C:\Users\miloq\.m2\repository\org\apache\logging\log4j\log4j-api\2.19.0\log4j-api-2.19.0.jar;C:\Users\miloq\.m2\repository\org\slf4j\jul-to-slf4j\2.0.6\jul-to-slf4j-2.0.6.jar;C:\Users\miloq\.m2\repository\org\yaml\snakeyaml\1.33\snakeyaml-1.33.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-starter-json\3.0.4\spring-boot-starter-json-3.0.4.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.14.2\jackson-databind-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.14.2\jackson-annotations-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.14.2\jackson-core-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.14.2\jackson-datatype-jdk8-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.14.2\jackson-datatype-jsr310-2.14.2.jar;C:\Users\miloq\.m2\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.14.2\jackson-module-parameter-names-2.14.2.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-web\6.0.6\spring-web-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-beans\6.0.6\spring-beans-6.0.6.jar;C:\Users\miloq\.m2\repository\io\micrometer\micrometer-observation\1.10.4\micrometer-observation-1.10.4.jar;C:\Users\miloq\.m2\repository\io\micrometer\micrometer-commons\1.10.4\micrometer-commons-1.10.4.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-webmvc\6.0.6\spring-webmvc-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-aop\6.0.6\spring-aop-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-context\6.0.6\spring-context-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-expression\6.0.6\spring-expression-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-devtools\3.0.4\spring-boot-devtools-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot\3.0.4\spring-boot-3.0.4.jar;C:\Users\miloq\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\3.0.4\spring-boot-autoconfigure-3.0.4.jar;C:\Users\miloq\.m2\repository\org\projectlombok\lombok\1.18.26\lombok-1.18.26.jar;C:\Users\miloq\.m2\repository\jakarta\annotation\jakarta.annotation-api\2.1.1\jakarta.annotation-api-2.1.1.jar;C:\Users\miloq\.m2\repository\org\slf4j\slf4j-api\2.0.6\slf4j-api-2.0.6.jar;C:\Users\miloq\.m2\repository\org\hamcrest\hamcrest\2.2\hamcrest-2.2.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-core\6.0.6\spring-core-6.0.6.jar;C:\Users\miloq\.m2\repository\org\springframework\spring-jcl\6.0.6\spring-jcl-6.0.6.jar;C:\Users\miloq\.m2\repository\com\alibaba\csp\sentinel-core\1.8.6\sentinel-core-1.8.6.jar;C:\Users\miloq\.m2\repository\com\lmax\disruptor\3.4.2\disruptor-3.4.2.jar;C:\Users\miloq\.m2\repository\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar;C:\Users\miloq\.m2\repository\com\alibaba\dubbo\2.5.3\dubbo-2.5.3.jar;C:\Users\miloq\.m2\repository\org\javassist\javassist\3.15.0-GA\javassist-3.15.0-GA.jar;C:\Users\miloq\.m2\repository\org\jboss\netty\netty\3.2.5.Final\netty-3.2.5.Final.jar;C:\Users\miloq\.m2\repository\junit\junit\4.13.2\junit-4.13.2.jar;C:\Users\miloq\.m2\repository\org\hamcrest\hamcrest-core\2.2\hamcrest-core-2.2.jar;C:\Users\miloq\.m2\repository\com\google\guava\guava\32.1.2-jre\guava-32.1.2-jre.jar;C:\Users\miloq\.m2\repository\com\google\guava\failureaccess\1.0.1\failureaccess-1.0.1.jar;C:\Users\miloq\.m2\repository\com\google\guava\listenablefuture\9999.0-empty-to-avoid-conflict-with-guava\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;C:\Users\miloq\.m2\repository\com\google\code\findbugs\jsr305\3.0.2\jsr305-3.0.2.jar;C:\Users\miloq\.m2\repository\org\checkerframework\checker-qual\3.33.0\checker-qual-3.33.0.jar;C:\Users\miloq\.m2\repository\com\google\errorprone\error_prone_annotations\2.18.0\error_prone_annotations-2.18.0.jar;C:\Users\miloq\.m2\repository\com\google\j2objc\j2objc-annotations\2.8\j2objc-annotations-2.8.jar;C:\Users\miloq\.m2\repository\org\apache\commons\commons-lang3\3.13.0\commons-lang3-3.13.0.jar imooc.atomic.lock.TestSync* # WARNING: Unable to get Instrumentation. Dynamic Attach failed. You may add this JAR as -javaagent manually, or supply -Djdk.attach.allowAttachSelf* # WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.memory.Universe.getNarrowOopBase()* java.lang.Object object internals:* OFFSET SIZE TYPE DESCRIPTION VALUE* 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) - markword* 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) - markword* 8 4 (object header) 58 0d 00 00 (01011000 00001101 00000000 00000000) (3416) - classpointer* 12 4 (loss due to the next object alignment) - 对齐填充* Instance size: 16 bytes* Space losses: 0 bytes internal + 4 bytes external = 4 bytes total*** Process finished with exit code 0** 第一行、第二行都是markword* 第三行是classpointer* 因为没有数据,最后一行是对齐填充,markword和classpointer总共是12个byte,不能被8整除,所以对齐填充补充了4个byte*/
// System.out.println(ClassLayout.parseInstance(lock).toPrintable());
// lock = new Order();
// System.out.println(ClassLayout.parseInstance(lock).toPrintable());synchronized (lock){
// System.out.println(ClassLayout.parseInstance(lock).toPrintable());}
// Order o = (Order)lock;
// o.setId(1);
// System.out.println(ClassLayout.parseInstance(lock).toPrintable());}
}class Order{private int id;public int getId() {return id;}public void setId(int id) {this.id = id;}
}
java虚拟机对应代码:
ObjectMonitor对象:
这里就有我们刚才提到的owner、WaitSet以及EntryList。
现在回到前面的inflake方法处:
enter方法:
如果设置失败了,会调用EnterI():
EnterI()就是把自己线程放到EnterList里边。
8.如何实现锁升级?
上卫生间,给卫生间上锁,这就是使用重量级锁的过程。
但是,如果屋子里只有自己一个人,上卫生间,这个时候再给卫生间门上加把锁,就是非常麻烦了。
如果房间里还有其他人,他可以给卫生间门锁贴个字条,某人专用,这个就像是偏向锁。
现在有两人同时要上卫生间,两个人进去的条件是卫生间没人。
A要进去的时候,卫生间门口牌子上没有写有人在使用,A就在门口牌子写上自己的名字,并进去了。
B要进去的时候,发现卫生间门口牌子上写有A的名字,B这个时候就不能进去了。
为了节省空间,有些位置是公用的,
因为位置是公用的,无锁状态31bit的hashcode,在转为偏向锁后54bit的threadId是冲突的,所以计算过hashcode,偏向线程ID和当前线程ID不一致,则当前线程无法进入。
大白话偏向锁原理:
java 偏向锁原理
Java 偏向锁(Biased Locking)是 Java 6 引入的一项优化技术,旨在减少无竞争同步的同步开销。这项技术主要在 HotSpot JVM 中实现,并被用在锁的内部实现中,以提高多线程环境下程序的性能。偏向锁的引入是为了解决轻量级锁带来的性能问题,特别是在大多数情况下没有竞争发生的场景下。偏向锁的工作原理
检测锁状态:当一个线程访问同步代码块时,首先会检查锁对象的状态。如果锁对象处于无锁状态(未被任何线程锁定),那么偏向锁机制就会尝试将这个锁对象的所有权偏向于第一个访问该同步块的线程。偏向设置:当第一个线程访问同步块时,JVM 会将对象头中的 Mark Word 设置为偏向模式,并且使用 CAS 操作将偏向的线程 ID 记录到对象的 Mark Word 中。这样,这个线程在接下来的执行过程中就可以快速地进入同步块,而不需要再进行重量级的同步操作。撤销偏向:如果有第二个线程尝试进入这个同步块,JVM 会检测到 Mark Word 中的偏向模式,并且发现偏向的线程 ID 与当前线程不匹配。这时,JVM 会撤销偏向模式,恢复到无锁状态或者升级为轻量级锁(如果检测到有竞争发生)。这个过程称为“撤销偏向”(Revoking Bias)。轻量级锁到重量级锁的升级:如果在运行过程中检测到多个线程竞争同一个锁对象,JVM 会将这些轻量级锁升级为重量级锁(使用操作系统级别的互斥量),以支持多线程之间的竞争。偏向锁的优势
减少延迟:在大多数情况下,只有一个线程访问同步块的场景中,偏向锁可以显著减少线程获取锁的开销。降低开销:相比于轻量级锁和重量级锁,偏向锁的开销更小,因为偏向模式只需要在第一次访问时设置一次。偏向锁的适用场景
单线程环境:在只有一个线程频繁访问同步块的场景中,偏向锁可以提供更好的性能。多线程低竞争环境:在多线程但竞争不激烈的情况下,偏向锁可以减少线程间的切换和同步开销。注意事项
撤销偏向的开销:虽然偏向锁可以带来性能优势,但撤销偏向的开销(尤其是在高竞争环境下)可能会抵消其优势。因此,JVM 提供了一个参数 -XX:-UseBiasedLocking 来禁用偏向锁。动态检测:JVM 会动态地监控锁的使用情况,如果检测到某个锁长时间不被撤销偏向,JVM 可能会自动关闭偏向锁功能以避免不必要的开销。通过理解和合理使用偏向锁,可以在 Java 应用程序中获得更好的性能表现。
新来的C想使用卫生间,看到卫生间门牌写着A专用,现在要把卫生间的门牌去掉,取消A的专用状态,这就是偏向锁的解锁,恢复到无锁状态。
取消掉门牌后,C一直围着卫生间转圈,等待A从卫生间出来,这就是轻量级锁,轻量级锁是相对于重量级锁来说的。
LockRecord是每个线程独有的,将锁对象的markword复制到每个线程的LockRecord里面,然后都尝试,把锁对象的markword替换为指向自己线程的LockRecord的指针,哪个线程替换成功了,哪个线程就获取到了这个线程的轻量级锁。
那为什么要把锁对象的markword替换到线程的LockRecord中,再用CAS操作更新指针,不能直接把锁对象的markword直接更新为线程自己ide指针吗?
轻量级锁加锁时,中间有62bit,存储的是LockRecord指针,如果直接使用CAS替换为指针,那锁对象的hashcode、分代年龄等这些信息就丢失了,所以需要找一个地方暂存起来,比如,轻量级锁拿到后,调用hashcode,就需要根据存储在锁对象的LockRecord指针,找到对象的LockRecord,再从LockRecord中获取锁对象的hashcode,锁标志位00,代表的是轻量级锁。
解释:
轻量级锁的解锁过程,就是反向操作,将线程对象的LockRecord中的markword替换回锁对象的过程。
自适应自旋锁:
自适应意味着自旋的时间不再是固定的,而是由上一次在同一个锁上自旋的时间以及锁的拥有者的状态来决定的。
如果在同一个对象上,刚刚成功获得锁,并且持有锁的线程正在运行中,那么JVM就会认为这次自旋很大可能也会成功,那么JVM会选择更多次数的自旋。
如果对于某个锁,自旋很少成功获得过,可以直接省掉自旋的过程,以避免浪费处理器资源。
参考:java中自旋详细介绍_java 自旋锁-CSDN博客
重量级锁,就是一开始提到的Monitor。
轻量级锁调用了 wait 与 notify 方法后还是轻量级锁吗?
在Java中,轻量级锁(Thin Lock)是一种优化机制,用于减少多线程竞争时的开销。然而,一旦调用了wait()
或notify()
方法,锁的状态会发生变化。
具体来说:
-
调用
wait()
方法:当一个线程调用wait()
时,它会释放持有的锁,并进入等待状态。此时,锁会从轻量级锁升级为重量级锁(Heavyweight Lock),因为wait()
和notify()
机制依赖于底层的监视器(Monitor),而监视器的实现通常涉及操作系统级别的资源管理,这会增加锁的开销。 -
调用
notify()
或notifyAll()
方法:这些方法用于唤醒等待中的线程。虽然它们本身不会直接改变锁的状态,但由于它们通常与wait()
方法配合使用,锁的状态可能已经因为wait()
的调用而升级为重量级锁。
因此,调用了wait()
或notify()
方法后,锁通常不再是轻量级锁,而是会升级为重量级锁。这种升级是为了支持更复杂的线程同步机制,确保线程能够正确地进行等待和唤醒操作。
9.什么场景下使用Java提供的原子类AtomicInteger保证原子类?
package imooc.atomic;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;public class TestAtomicInteger {public static void main(String[] args) throws InterruptedException {int count = 0;count++;AtomicInteger atomicInteger = new AtomicInteger(5);System.out.println(atomicInteger.getAndIncrement());//i++,返回的是原始值5System.out.println(atomicInteger.get());//返回操作后(即现在)的值System.out.println(atomicInteger.incrementAndGet());//++i, 返回++i之后的值System.out.println(atomicInteger.decrementAndGet());//--iSystem.out.println(atomicInteger.getAndDecrement());//i--System.out.println(atomicInteger.get());//返回操作后(即现在)的值
// AtomicInteger count = new AtomicInteger();
// List<Thread> list = new ArrayList<>();
// for (int i = 0; i < 100; i++) {
// Thread thread = new Thread(() -> {
// for (int j = 0; j < 100; j++) {
// count.incrementAndGet();
// }
// });
// list.add(thread);
// }
// for (Thread thread : list) {
// thread.start();
// }
// for (Thread thread : list) {
// thread.join();
// }
// System.out.println(count.get());}
}
运行结果:
下面多线程自增测试下AtomicInteger:
package imooc.atomic;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;public class TestAtomicInteger {public static void main(String[] args) throws InterruptedException {
// int count = 0;
// count++;
// AtomicInteger atomicInteger = new AtomicInteger(5);
// System.out.println(atomicInteger.getAndIncrement());//i++,返回的是原始值5
// System.out.println(atomicInteger.get());//返回操作后(即现在)的值
// System.out.println(atomicInteger.incrementAndGet());//++i, 返回++i之后的值
// System.out.println(atomicInteger.decrementAndGet());//--i
// System.out.println(atomicInteger.getAndDecrement());//i--
// System.out.println(atomicInteger.get());//返回操作后(即现在)的值AtomicInteger count = new AtomicInteger();List<Thread> list = new ArrayList<>();for (int i = 0; i < 100; i++) {Thread thread = new Thread(() -> {for (int j = 0; j < 100; j++) {count.incrementAndGet();}});list.add(thread);}//批量启动线程for (Thread thread : list) {thread.start();}//确保所有线程都运行结束,再进行控制台打印输出for (Thread thread : list) {thread.join();}System.out.println(count.get());}
}
运行结果:
package imooc.atomic;import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;public class TestAtomicInteger2 {public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(5);//初始值5
// atomicInteger.getAndAdd(10);//先返回再加10
// System.out.println(atomicInteger);
// System.out.println(atomicInteger.addAndGet(5));//先加再返回int i = updateAndGet(atomicInteger);
// int i = updateAndGet(atomicInteger,x->x/5);
// int i = atomicInteger.updateAndGet(x -> x * 5);System.out.println(i);}public static int updateAndGet(AtomicInteger atomicInteger){while (true){int cur = atomicInteger.get();int next = cur * 5;
// int next = intUnaryOperator.applyAsInt(cur);/*** Compare-and-Swap (CAS) 是另一种常见的原子指令,* 它允许 CPU 在一次操作中比较内存中的值并与预期值进行对比,如果匹配,则更新内存中的值;如果不匹配,则返回当前值* cur 当前值* next 待更新的值*/if(atomicInteger.compareAndSet(cur,next)){return next;}}}
}
运行结果:
但是这么写不太通用,稍加改造:
package imooc.atomic;import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;public class TestAtomicInteger2 {public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(5);//初始值5
// atomicInteger.getAndAdd(10);//先返回再加10
// System.out.println(atomicInteger);
// System.out.println(atomicInteger.addAndGet(5));//先加再返回// int i = updateAndGet(atomicInteger);int i = updateAndGet(atomicInteger,x->x/5);
// int i = atomicInteger.updateAndGet(x -> x * 5);System.out.println(i);}/**** @param atomicInteger* @param intUnaryOperator Java1.8引入的函数式变成,提供一个函数式表达式进行计算* @return*/public static int updateAndGet(AtomicInteger atomicInteger, IntUnaryOperator intUnaryOperator){while (true){int cur = atomicInteger.get();
// int next = cur * 5;int next = intUnaryOperator.applyAsInt(cur);/*** Compare-and-Swap (CAS) 是另一种常见的原子指令,* 它允许 CPU 在一次操作中比较内存中的值并与预期值进行对比,如果匹配,则更新内存中的值;如果不匹配,则返回当前值* cur 当前值* next 待更新的值*/if(atomicInteger.compareAndSet(cur,next)){return next;}}}
}
运行结果:IntUnaryOperator类
参考:Java函数式IntUnaryOperator接口介绍、应用场景和示例代码_java intunaryoperator-CSDN博客
如果担心自己写的方法,写错了,可以直接使用JDK提供的方法updateAndGet():
package imooc.atomic;import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;public class TestAtomicInteger2 {public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(5);//初始值5
// atomicInteger.getAndAdd(10);//先返回再加10
// System.out.println(atomicInteger);
// System.out.println(atomicInteger.addAndGet(5));//先加再返回// int i = updateAndGet(atomicInteger);//自己实现的方法
// int i = updateAndGet(atomicInteger,x->x/5);//JDK提供的方法int i = atomicInteger.updateAndGet(x -> x * 5);System.out.println(i);}/**** @param atomicInteger* @param intUnaryOperator Java1.8引入的函数式变成,提供一个函数式表达式进行计算* @return*/public static int updateAndGet(AtomicInteger atomicInteger, IntUnaryOperator intUnaryOperator){while (true){int cur = atomicInteger.get();
// int next = cur * 5;int next = intUnaryOperator.applyAsInt(cur);/*** Compare-and-Swap (CAS) 是另一种常见的原子指令,* 它允许 CPU 在一次操作中比较内存中的值并与预期值进行对比,如果匹配,则更新内存中的值;如果不匹配,则返回当前值* cur 当前值* next 待更新的值*/if(atomicInteger.compareAndSet(cur,next)){return next;}}}
}
解释:
1.AtomicInteger只能保证一个变量的原子性,如果要保证多个变量之间复合操作的原子性还是要使用锁;
2.ABA问题 - 初值1,被别的线程更新成0,又更新成1,但是此1非彼1,这种情况不能使用Atomic;
3.竞争非常激烈,自旋时间可能过长- CAS要在循环中不断更新这个值,自旋时间可能会很长。
为什么竞争过于激烈的场景,为什么不能使用AtomicInteger?
在竞争过于激烈的场景中,使用 `AtomicInteger` 可能会遇到性能问题,原因如下:1. **CAS 自旋开销**:- `AtomicInteger` 依赖 CAS(Compare-And-Swap)操作,在高并发下,多个线程可能反复尝试更新同一个变量,导致大量线程处于自旋状态,增加 CPU 开销,降低性能。2. **缓存一致性压力**:- 频繁的 CAS 操作会导致缓存行在多核 CPU 之间频繁失效和同步,增加缓存一致性协议的开销,进一步影响性能。3. **伪共享**:- 如果多个 `AtomicInteger` 实例存储在相邻内存位置,一个实例的更新可能导致其他实例的缓存行失效,即使它们没有被修改,这会增加不必要的缓存同步开销。4. **线程争用**:- 高并发下,多个线程争用同一个 `AtomicInteger` 实例,可能导致某些线程长时间无法成功更新,增加等待时间,降低吞吐量。### 替代方案
在竞争激烈的场景中,可以考虑以下替代方案:1. **LongAdder**:- `LongAdder` 通过分段计数减少争用,适合高并发场景,性能优于 `AtomicInteger`。2. **ConcurrentHashMap**:- 如果需要维护多个计数器,可以使用 `ConcurrentHashMap`,每个线程更新不同的键,减少争用。3. **锁**:- 在某些情况下,使用锁(如 `ReentrantLock`)可能比 CAS 更高效,尤其是在临界区操作较复杂时。### 总结
在竞争激烈的场景中,`AtomicInteger` 的 CAS 操作可能导致性能下降。选择 `LongAdder` 或 `ConcurrentHashMap` 等替代方案,可以有效减少争用,提升性能。
10.原子类是如何利用CAS(比较并交换)保证线程安全的
不同的操作系统实现不同:
参考linux_x86:
解释:
1.如果是单核的,就不加lock指令,如果是多核CPU,需要加lock指令后,调用cmpxchgg汇编指令;
2.1中提到的lock指令,有什么作用呢,可以参考Inter架构的操作手册
可以看到就是锁总线。
总结:使用CAS可以提高并发效率,但是并不一定会比synchronized效率更高。
11.高并发下AtomicInteger的性能 VS synchronized的性能
package imooc.atomic;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;public class CompareLockAndAtomic {public static void main(String[] args) throws InterruptedException {//写一个方法,传入线程数,分别测试不同线程数的情况下atomoic和synchronized的运行速度testLock(100);testAtomic(100);testLock(1000);testAtomic(1000);testLock(3000);testAtomic(3000);}private static void testAtomic(int threadNum) throws InterruptedException {long startTime = System.currentTimeMillis();AtomicInteger atomicInteger = new AtomicInteger();List<Thread> list = new ArrayList<>();for (int i = 0; i < threadNum; i++) {list.add(new Thread(()->{for (int j = 0; j < 3000; j++) {atomicInteger.incrementAndGet();}}));}for (Thread thread : list) {thread.start();}for (Thread thread : list) {thread.join();}long endTime = System.currentTimeMillis();System.out.println("atomic用时 "+(endTime-startTime)+" count :"+atomicInteger.get()+" 线程数 :"+threadNum);}private static void testLock(int threadNum) throws InterruptedException {long startTime = System.currentTimeMillis();List<Thread> list = new ArrayList<>();for (int i = 0; i < threadNum; i++) {Thread thread = new IncrementThread();list.add(thread);}for (Thread thread : list) {thread.start();}for (Thread thread : list) {thread.join();}long endTime = System.currentTimeMillis();System.out.println("lock用时 "+(endTime-startTime)+" count :"+IncrementThread.count+" 线程数 :"+threadNum);IncrementThread.count=0;}
}
class IncrementThread extends Thread{static int count = 0;@Overridepublic void run() {for (int i = 0; i < 3000; i++) {synchronized (IncrementThread.class){count++;}}}
}
运行结果:
结论:
在高并发场景下,使用synchroized可能比Atomic效率更高。
12.高并发下AtomicBoolean和AtomicLong的用法
AtomlicBoolean - 一般在框架里用的比较多
使用示例:
package imooc.atomic;import java.util.concurrent.atomic.AtomicBoolean;public class TestAtomicBoolean {public static void main(String[] args) {AtomicBoolean atomicBoolean = new AtomicBoolean(false);System.out.println(atomicBoolean.getAndSet(true));//falseSystem.out.println(atomicBoolean.get());//true}
}
运行结果:
AtomicLong使用方法: 与AtomicInteger类似
package imooc.atomic;import java.util.concurrent.atomic.AtomicLong;public class TestAtomicLong {public static void main(String[] args) {AtomicLong atomicLong = new AtomicLong(5);
// System.out.println(atomicLong.getAndSet(6));//5 先get再赋值
// System.out.println(atomicLong.get());//6 获取操作后(即现在)的值
// System.out.println(atomicLong.incrementAndGet());//7 先自增再get
// System.out.println(atomicLong.getAndIncrement());//7 先get再自增
// System.out.println(atomicLong.get());//8 获取操作后(即现在)的值while (true){long pre = atomicLong.get();long next = pre+5;if(atomicLong.compareAndSet(pre,next)) {break;}}System.out.println(atomicLong.get());}
}
13.高并发下AtomicIntegerArray和AtomicReference的用法
使用示例:
package imooc.atomic;import java.util.concurrent.atomic.AtomicIntegerArray;public class TestAtomicIntegerArray {public static void main(String[] args) {//构造一个长度为10的原子整形数组AtomicIntegerArray array = new AtomicIntegerArray(10);
// System.out.println(array.addAndGet(0, 5)); //更新第0个元素,更新值为5while (true){int pre = array.get(5); //获取原子整形数组第5个元素int next = pre+10;if(array.compareAndSet(5,pre,next)){break;}}System.out.println(array.get(5));}
}
运行结果:
使用示例:
package imooc.atomic;import java.util.concurrent.atomic.AtomicReference;public class TestAtomicReference {public static void main(String[] args) {//如果AtomicReference定义的类型是Integer,那其实就是和AtomicInteger一样的类
// AtomicReference<Integer> reference = new AtomicReference<>();Order order = new Order("1","1");AtomicReference<Order> reference = new AtomicReference<>(order);Order order1 = new Order("2","2");System.out.println(reference.getAndSet(order1));System.out.println(reference.get());while (true){Order order2 = reference.get();String id = order2.getId();String name = order2.getName();id = id+"end";name = name +"end";Order order3 = new Order(id,name);if(reference.compareAndSet(order1,order3)){break;}}System.out.println(reference.get());}
}class Order{private String id;private String name;public Order(String id, String name) {this.id = id;this.name = name;}@Overridepublic String toString() {return "Order{" +"id='" + id + '\'' +", name='" + name + '\'' +'}';}public String getId() {return id;}public String getName() {return name;}public void setId(String id) {this.id = id;}public void setName(String name) {this.name = name;}}
运行结果:
总结:
原子类一般只能使用一个字段,当我们有多个字段的时候,是不能使用一般的原子类的,这是我们可以使用AtomicReference操作多个字段。
14.剖析未可见性问题
package imooc.visibility;/*** @Description: 库存类*/
public class Inventory {//库存数量private Integer count;//商品名称private String product;//用户下单用public void deCount(){count--;}//管理员补充库存用public void addCount(int num){count+=num;}public Inventory(Integer count, String product) {this.count = count;this.product = product;}public Integer getCount() {return count;}public void setCount(Integer count) {this.count = count;}public String getProduct() {return product;}public void setProduct(String product) {this.product = product;}//判断是否有库存,有库存可以下单public boolean hasInventory(){return count>0;}
}
package imooc.visibility;/*** @Description: 测试下单和补库存*/
public class TestInventory {public static void main(String[] args) throws InterruptedException {//初始化库存Inventory inventory = new Inventory(100,"苹果");//模拟用户下单new Thread(()->{while (true){//疯狂下单if(inventory.hasInventory()){//有库存 扣库存inventory.deCount();System.out.println("开始购物,库存还有 :"+inventory.getCount());}}}).start();//等待用户下单结束Thread.sleep(3000);//管理员加库存 10个inventory.addCount(100);}
}
运行结果:
分析:
一开始设置库存10,每个线程的缓存10个,当进行补货后,结果还是无法继续下单。
优化:
运行结果:
15.volatile可见性剖析
package imooc.visibility;/*** @Description:*/
public class TestVolatile {public static volatile int i =0;
//public static void testVolatile1(){i = 1;}public static void main(String[] args) {testVolatile1();}
}
查看汇编:
在 Java 中,`volatile` 关键字用于确保多线程环境下变量的可见性和有序性。为了实现这些语义,JVM 和底层硬件(如 CPU)会使用一些机制,其中**总线嗅探(Bus Snooping)**是一个与缓存一致性相关的重要概念。---### 什么是总线嗅探?
总线嗅探是一种**缓存一致性协议**的实现方式,用于在多核 CPU 系统中维护多个核心的缓存一致性。它的工作原理如下:1. **总线的监听**:- 每个 CPU 核心都会监听总线上的内存操作(如读、写)。- 当一个核心修改了某个共享变量的值,这个修改操作会通过总线广播给其他核心。2. **缓存行的失效**:- 如果其他核心的缓存中有该变量的副本,总线嗅探机制会将这些副本标记为**失效**(Invalid)。- 这样,其他核心在读取该变量时,会从主内存中重新加载最新的值,而不是使用本地缓存中的旧值。3. **保证可见性**:- 通过总线嗅探,`volatile` 变量的修改能够立即被其他线程看到,从而实现了**可见性**。---### `volatile` 和总线嗅探的关系
在 Java 中,`volatile` 变量的读写会触发以下操作:1. **写操作**:- 当一个线程修改 `volatile` 变量时,JVM 会确保该修改立即写入主内存,而不是仅仅停留在本地缓存。- 同时,总线嗅探机制会通知其他核心,使它们缓存中的该变量副本失效。2. **读操作**:- 当一个线程读取 `volatile` 变量时,JVM 会强制从主内存中加载最新的值,而不是使用本地缓存中的旧值。通过总线嗅探机制,`volatile` 实现了以下特性:
- **可见性**:一个线程对 `volatile` 变量的修改对其他线程立即可见。
- **有序性**:JVM 会禁止指令重排序,确保 `volatile` 写操作之前的操作不会被重排序到写操作之后,读操作之后的操作不会被重排序到读操作之前。---### 总线嗅探的缺点
虽然总线嗅探是实现缓存一致性的一种有效方式,但它也有一些性能上的缺点:
1. **总线带宽压力**:- 每次 `volatile` 写操作都会触发总线广播,增加了总线的通信压力。- 在高并发场景下,频繁的 `volatile` 写操作可能导致总线带宽成为瓶颈。2. **缓存行失效**:- 总线嗅探会导致其他核心的缓存行失效,增加了缓存未命中(Cache Miss)的概率,从而影响性能。---### 总线嗅探的替代方案
在现代 CPU 中,除了总线嗅探,还有其他缓存一致性协议,例如:
- **MESI 协议**(Modified, Exclusive, Shared, Invalid):- 一种更高效的缓存一致性协议,通过状态机来管理缓存行的状态,减少总线通信的开销。---### 总结
- 总线嗅探是实现 `volatile` 可见性的底层机制之一,它通过监听总线上的内存操作来维护缓存一致性。
- `volatile` 通过总线嗅探和内存屏障(Memory Barrier)来保证可见性和有序性。
- 虽然总线嗅探有效,但在高并发场景下可能会带来性能开销,因此需要谨慎使用 `volatile`。
框架中的使用:
解释:
一个long类型8byte,RhsPadding中7个long类型、LhsPadding中1个long类型,刚好填充满缓存行64个byte。
16.单例的创建方式及原理深入剖析
代码示例:
package imooc.singleton;/*** @Description: 单利模式的类*/
public class Singleton {public static volatile Singleton singleton ;/*** 私有化构造方法 ,外部不能调用*/private Singleton() {}public static Singleton getSingleton(){if(singleton==null){synchronized (Singleton.class){//如果singleton是null,初始化if(singleton==null){singleton = new Singleton();//1 开辟空间 0: new//2 初始化字段内容 invokespecial #1//3 指向堆空间 astore_1}}}return singleton;}//test case用public static void reset(){singleton=null;}public static void main(String[] args) {}
}
package imooc.singleton;import com.alibaba.dubbo.common.utils.ConcurrentHashSet;import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;/*** @Description:*/
public class TestSingleton {public static void main(String[] args) throws InterruptedException {while (true){Set<Integer> set = new ConcurrentHashSet<>();List<Thread> list = new ArrayList<>();CountDownLatch countDownLatch = new CountDownLatch(1);for (int i = 0; i < 10; i++) {Thread thread = new Thread(() -> {try {//让所有线程启动后阻塞(准备完毕),一起并发countDownLatch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}Singleton singleton = Singleton.getSingleton();set.add(singleton.hashCode());});list.add(thread);}for (Thread thread : list) {thread.start();}//主线程将技术减为0,让所有线程一起往下执行并发countDownLatch.countDown();for (Thread thread : list) {thread.join();}if(set.size()>1){System.out.println("fail to create singleton");}Singleton.reset();}}
}
期望:
如果控制台未输出信息,证明为单例。
运行结果:
注意:
这里要双重判断singleton==null,如果同时有两个线程同时进入第一个singleton==null判断,其中一个获得了锁,并初始化singleton,然后释放锁,第二个线程获取锁,到第二个singleton==null判断,直接跳出if判断。
(1)在堆中开辟空间;
(2)init对象中的属性;
(3)对象指针指向堆中的内存地址。
(1)在堆中开辟空间;
(3)对象指针指向堆中的内存地址。
(2)init对象中的属性;
结合前面singten的单例初始化代码:
(1)在堆中开辟空间; - 正常
(3)对象指针指向堆中的内存地址。 - 这个时候就产生问题了,如果第一个线程进入到这里,在开辟空间后,先将对象指针指向了堆空间中的内存地址,其他线程此时获取对象,获取到的是一个没有初始化完的对象,所以我们要禁止这种重排序的现象。
17.volatile禁止重排序的原理
package imooc.singleton;import java.util.concurrent.CountDownLatch;/*** @Description: 测试重排序* 创建两个线程,测试线程内重排序现象*/
public class TestReorder {//禁止重排序 加volatilestatic volatile int x = 0;static volatile int y = 0;static volatile int a = 0;static volatile int b = 0;public static void main(String[] args) throws InterruptedException {int count = 0;while (true){CountDownLatch countDownLatch = new CountDownLatch(1);Thread thread = new Thread(() -> {try {countDownLatch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}//操作1a = 1;//操作2x = b;System.out.println("TestLog: "+a+" b: "+b);});Thread thread1 = new Thread(() -> {try {countDownLatch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}//操作1b = 1;//操作2y = a;System.out.println("TestLog: "+a+" b: "+b);});thread.start();thread1.start();countDownLatch.countDown();thread.join();thread1.join();//三种情况 (1,1)(1,0),(0,1)//如果两个线程都发生了重排序,结果是(0,0)System.out.println("x: "+ x+",y: "+y);//如果重排序,退出if(x==0&&y==0){System.out.println("x=0&y=0,count="+count);break;}count++;x=0;y=0;a=0;b=0;System.out.println("=============");}}
}
、
、
package imooc.singleton;/*** @Description:编译器级别重排序*/
public class TestReorder1 {static volatile int a = 0;static int b = 0;public static void main(String[] args) {//第一个操作 普通变量的读写b = 10;//第二个操作,volatile变量的写a = 10;}
}
编译器转为字节码,最后转换为汇编代码的时候,还会有一个处理器级别的重排序
解释:
- LoadLoad(读读屏障):先执行屏障前的 读,后执行屏障后的 读。
- LoadStore(读写屏障):先执行屏障前的 读,后执行屏障后的 写。
- StoreLoad(写读屏障):先执行屏障前的 写,后执行屏障后的 读。
- StoreStore(写写屏障):先执行屏障前的 写,后执行屏障后的 写。
参考:Java四种内存屏障详解,LoadLoad、LoadStore、StoreLoad、StoreStore - Yfeil - 博客园
18.volatile在dubbo和秒杀中的应用
package imooc.test;
/*** @Author: Alfred* @ModuleOwner: Alfred* @Description: 修改温度的范围*/
//线程不安全
public class TemperatureChanger {//兔子能适应的最低温度private volatile int lower = 16;//兔子能适应的最高温度private volatile int upper = 25;//限制value不能高于upper 16 -25public void setLower(int value){if(value>upper){throw new RuntimeException("温度不能高于上限");}this.lower = value;}//限制value不能低于lowerpublic void setUpper(int value){if(value<lower){throw new RuntimeException("温度不能低于下限");}this.upper = value;}public int getLower() {return lower;}public int getUpper() {return upper;}public static void main(String[] args) {TemperatureChanger temperatureChanger = new TemperatureChanger();new Thread(()->{temperatureChanger.setLower(23);}).start();new Thread(()->{temperatureChanger.setUpper(20);}).start();//23-20}
}