并发编程的艺术学习笔记 -基础

一:基本基础

1,上下文切换

cpu不停的切换线程执行。那么就有一个问题,让出cpu的线程等下次轮到自己占有cpu的时,如何知道自己之前运行到了哪里,所以在切换上下文时需要保存当前线程的执行现场,当再次执行时根据保存的执行现场信息恢复执行现场。

线程上下文切换的时机:

当前线程的cpu时间片使用完处于就绪状态时,当前线程被其他线程中断时。

减少上下文切换:有无锁并发编程,cas算法,使用最少的线程和使用协程。

2,volatile

volatile是轻量级的synchronized。

定义:java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量,java提供了volatile,在某些情况下比锁更加方便,如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。

实现原则:

1,lock前缀指令会引起处理器缓存回写到内存。

2,一个处理器的缓存回写到内存会导致其他处理器的缓存无效。

3,synchronized锁的状态:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,锁可以升级但不能降级。

 

 

二:java内存模型
两个关键性问题:线程之间如何通信,线程之间如何同步
通信机制:共享内存和消息传递
Java的并发采用的是共享内存模型。
Java线程之间的通信由Java内存模型控制。
线程与主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存存储了该线程读写共享变量的副本。本地内存是一个抽象的概念。
重排序
在执行程序时,为了提高性能,编译器和处理器常常会对指令重排序。
三种重排序:
1,编译器优化的重排序
2,指令级并行的重排序
3,内存系统的重排序

happens-before 简介
与程序员密切相关的happens-before规则如下:
程序顺序规则:一个线程中的每个操作,happens-before与该线程中任意后续操作
监视器锁规则:对一个锁的解锁,happens-before与随后对这个锁的加锁
volatile变量规则:对一个volatile域的写,happens-before与任意后续对这个volatile域的读。
传递性:a-b,b-c,所有a-c。
注意:两个操作之间有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行,happens-before仅仅要求前一个操作的结果对后一个操作可见。且前一个操作按顺序排在第二个操作之前。


as-if-serial语义
不管怎么重排序,(单线程)程序的执行结果不能改变。重排序必须遵守这个语义。
编译器和处理器不会对存在数据依赖关系的操作(读写,写写,写读)做重排序,因为会改变执行结果。

在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果,但在多线程中,对存在控制依赖的操作重排序,会改变程序的执行结果。
顺序一致性模型(理论化参考模型)
数据竞争与顺序一致性:
当程序未正确同步时,就可能会存在数据竞争,java内存模型规范对数据竞争的定义是:
在一个线程中写一个变量,在另外一个线程读同一个变量,而且写和读没有通过同步来排序。
两个特性:
一个线程中所有操作必须要按照程序的顺序来执行
所有线程都只能看到一个单一的操作执行顺序,在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。

对于未同步或未正确同步的多线程程序,jmm只提供最小的安全性:线程执行时读取到的值,要么是之前某个线程写入的值,要么就是默认值,jmm保证线程读取到的值不会无中生有。
jmm不保证未同步程序的执行结果与该线程在顺序一致性模型中的执行结果一致。
未同步程序在jmm模型顺序一致性模型中的差异:
1,顺序一致性模型保证单线程内的操作会按程序的顺序执行,而jmm不保证单线程内的操作会按顺序执行。
2,顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而jmm不保证所有线程能看到一致的操作执行顺序。
3,jmm不保证对64位的long型和double型的写操作具有原子性,而顺序一致性模型保证对所有内存读写具有原子性。

volatile的内存语义
锁的happens-before规则保证了释放锁和获取锁的两个线程之间的内存可见性,这意味着对一个volatile变量的读,总是能看到对这个vilatile变量最后的写入。
volatile特性:可见性,原子性。
volatile变量的写-读可以实现线程之间的通信
从内存语义的角度来说,volatile的写-读与锁的释放-获取友相爱那共同被告的内存效果:volatile写和锁的释放有相同的内存语义,volatile读与锁的获取有相同的内存语义。
3.5 锁的内存语义
锁事java并发编程中最重要的同步机制,锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。
对比锁释放-获取的内存语义与volatile写-读内存语义:锁释放与volatile写有相同的语义,锁获取与volatile读有相同的内存语义。
总结:线程a释放一个锁,实质上是a发出一个消息给下一个线程。
线程b获取一个锁,实质上是b接受了之前某个线程的消息。
a释放,b获取,实质上是a通过主内存向b发送消息。
3.6 final域的内存语义
1,final的重排序规则
对于final,编译器和处理器都要遵守两个重排序规则:
1:在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
2:初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
2,写final域的重排序规则
写final域的重排序规则禁止把final域的写的重排序到构造函数之外。
确保:在对象引用为任意线程可见之前,对象的final域已经正确初始化过了,而普通域不具有这个保障。
3,读final域的重排序规则
读final域的重排序规则是:在一个线程中,初次读对象引用与初次读该对象包含的final域,jmm禁止处理器重排序这两个操作。当然这个规则仅仅针对处理器,编译器会在读final域之前插入一个loadload屏障。
确保:在读一个对象的final域之前,一定会先读包含这个final域的对象的引用。
总结:个人理解就是,我们常常在类中定义一些固定变量用final,这样在这个类中引用这个变量就是我们常说不变的。
4,final域如果是一个对象呢,final域重排序对编译器和处理器加了约束:在构造函数内对一个final引用的对象成员域(对象中的一个成员,字段或者数组中的一个值)的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作不能重排序。
5,还需要一个保证:在构造函数内不,不能让这个被构造对象的引用为其他线程所见,也就是不能溢出。
3.7 jmm设计
基本原则:只要不改变程序的执行结果,编译器和处理器怎么优化都行。
jsr-33使用happens-before的概念来指定两个操作(不管是一个线程还是不同线程)的执行顺序,通过happens-before关系向程序员提供跨线程的内存可见性保证。
定义:
1:如果一个操作happens-bedore另外一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
2:两个操作之间存在happens-before关系,并不意味着java 平台的具体实现必须按照happens-before关系指定的顺序来执行,如果结果一致,那么重排序也可以。
规则:
1:程序顺序规则:一个线程中的每个操作,happens-before与该线程中的任意后续操作。
2:监视器规则:对一个锁的解锁,happens-before与随后对这个锁的加锁
3:volatile变量规则:对一个volatile域的写,h-b与任意后续对于这个volatile域的读
4:传递性:a-b,b-c,那么a-c
5:start()规则:如果线程a执行操作ThreadB.start()(启动线程b),那么a线程的ThreadB.start()操作h-b与线程b中的任意操作。
6:join()规则:如果线程a执行操作ThreadB.join()并成功返回,那么线程b中的任意操作h-b于线程a从htreadB.join()操作成功返回。
3.8 双重检查锁定与延迟初始化
public class DoubleCheckedLocking {                                //1
private static Instance instance;                                    //2
public static Instance getInstance () {                            //3
if(instance == null) {                                                //4 第一次检查
synchronized (DoubleCheckedLocking.class) {        //5加锁
if(instance == null){                                      //6 第二次检查
instance == new Instance();                      //7问题根源出在这里
}
}
return instance;
}
}
}
8.1 基于volatile的解决方案(禁止重排序)
public class DoubleCheckedLocking {                               
private volatile static Instance instance;                            
public static Instance getInstance () {                          
if(instance == null) {                                               
synchronized (DoubleCheckedLocking.class) {     
if(instance == null){                                   
instance == new Instance();                      //instance为volatile,
}
}
return instance;
}
}
}
8.4 基于类初始化的解决方案(允许重排序,但是其他线程看不到)
public class InstanceFactory {
private static class InstanceHolder {
public static Instance instance = new Instance();
}
public static Instance getInstance() {
return InstanceHolder.instance;    //这里将导致InstanceHolder类被初始化
}
}
 




 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嗨!很高兴回答你关于Java并发编程的问题。请问你想知道什么方面的内容呢?我可以分享一些学习笔记和建议给你。 1. 并发编程基础:了解并发编程的基本概念,如线程、进程、锁、同步等。学习Java中的并发编程模型以及相关的API,如Thread、Runnable、Lock、Condition等。 2. 线程安全性:学习如何保证多线程环境下的数据安全性,了解共享资源的问题以及如何使用同步机制来防止数据竞争和并发问题。 3. 线程间的通信:掌握线程间的通信方式,如使用wait/notify机制、Lock/Condition等来实现线程的协调与通信。 4. 并发容器:学习并发容器的使用,如ConcurrentHashMap、ConcurrentLinkedQueue等。了解它们的实现原理以及在多线程环境下的性能特点。 5. 并发工具类:熟悉Java提供的并发工具类,如CountDownLatch、CyclicBarrier、Semaphore等,它们可以帮助你更方便地实现线程间的协作。 6. 并发编程模式:学习一些常见的并发编程模式,如生产者-消费者模式、读者-写者模式、线程池模式等。了解这些模式的应用场景和实现方式。 7. 性能优化与调试:学习如何分析和调试多线程程序的性能问题,了解一些性能优化的技巧和工具,如使用线程池、减少锁竞争、避免死锁等。 这些只是一些基本的学习笔记和建议,Java并发编程是一个庞大而复杂的领域,需要不断的实践和深入学习才能掌握。希望对你有所帮助!如果你有更具体的问题,欢迎继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值