Java多线程学习笔记 (2)

如果不依赖循环的具体次数或者中间状态, 可以通过设置标志位的方式来控制

public class ThreadFinished { private static volatile boolean flag = true; public static void main(String[] args) throws InterruptedException { // 推荐方式:设置标志位 Thread t3 = new Thread(() -> { long i = 0L; while (flag) { i++; } System.out.println("count sum i = " + i); }); t3.start(); TimeUnit.SECONDS.sleep(1); flag = false; } }

如果要依赖循环的具体次数或者中间状态, 则可以用interrupt方式

public class ThreadFinished { public static void main(String[] args) throws InterruptedException { // 推荐方式:使用interrupt Thread t4 = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { } System.out.println("t4 end"); }); t4.start(); TimeUnit.SECONDS.sleep(1); t4.interrupt(); } }

示例代码: ThreadFinished.java

并发编程的三大特性 可见性

每个线程会保存一份拷贝到线程本地缓存,使用volatile,可以保持线程之间数据可见性。

如下示例: ThreadVisible.java

public class ThreadVisible { static volatile boolean flag = true; public static void main(String[] args) throws InterruptedException { Thread t = new Thread(()->{ while(flag) { // 如果这里调用了System.out.println() // 会无论flag有没有加volatile,数据都会同步 // 因为System.out.println()背后调用的synchronized // System.out.println(); } System.out.println("t end"); }); t.start(); TimeUnit.SECONDS.sleep(3); flag = false; // volatile修饰引用变量 new Thread(a::m,"t2").start(); TimeUnit.SECONDS.sleep(2); a.flag = false; // 阻塞主线程,防止主线程直接执行完毕,看不到效果 new Scanner(System.in).next(); } private static volatile A a = new A(); static class A { boolean flag = true; void m() { System.out.println("m start"); while(flag){} System.out.println("m end"); } } }

代码说明:

如在上述代码的死循环中增加了System.out.println(), 则会强制同步flag的值,无论flag本身有没有加volatile。

如果volatile修饰一个引用对象,如果对象的属性(成员变量)发生了改变,volatile不能保证其他线程可以观察到该变化。

关于三级缓存

3_cache

如上图,内存读出的数据会在L3,L2,L1上都存一份。所谓线程数据的可见性,指的就是内存中的某个数据,假如第一个CPU的一个核读取到了,和其他的核读取到这个数据之间的可见性。

在从内存中读取数据的时候,根据的是程序局部性的原理,按块来读取,这样可以提高效率,充分发挥总线CPU针脚等一次性读取更多数据的能力。

所以这里引入了一个缓存行的概念,目前一个缓存行多用64个字节来表示。

如何来验证CPU读取缓存行这件事,我们可以通过一个示例来说明:

public class CacheLinePadding { public static T[] arr = new T[2]; static { arr[0] = new T(); arr[1] = new T(); } public static void main(String[] args) throws Exception { Thread t1 = new Thread(() -> { for (long i = 0; i < 1000_0000L; i++) { arr[0].x = i; } }); Thread t2 = new Thread(() -> { for (long i = 0; i < 1000_0000L; i++) { arr[1].x = i; } }); final long start = System.nanoTime(); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println((System.nanoTime() - start) / 100_0000); } private static class Padding { public volatile long p1, p2, p3, p4, p5, p6, p7; } private static class T /**extends Padding*/ { public volatile long x = 0L; } }

说明:以上代码,T这个类extends Padding与否,会影响整个流程的执行时间,如果继承了,会减少执行时间,因为继承Padding后,arr[0]和arr[1]一定不在同一个缓存行里面,所以不需要同步数据,速度就更快一些了。

jdk1.8增加了一个注解:@Contended,标注了以后,不会在同一缓存行, 仅适用于jdk1.8
还需要增加jvm参数

-XX:-RestrictContended

CPU为每个缓存行标记四种状态(使用两位)

Exclusive

Invalid

Shared

Modified

参考:【并发编程】MESI--CPU缓存一致性协议

有序性

为什么会出现乱序执行呢?因为CPU为了提高效率,可能在执行某些指令的时候,不按顺序执行(指令前后没有依赖关系的时候)

乱序存在的条件是:不影响单线程的最终一致性(as - if - serial)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppdzy.html