1、day01
-
无状态对象一定是线程安全的;
-
竞态条件:在并发编程中,这种由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况。
先检查后执行
读取-修改-写入
-
不可变对象,只能提供一种弱形式的原子性;
当有多个线程访问一个变量时,可以把变量设置为final,更新时,创建新的变量存储。
-
任何线程都可以在不需要额外同步的情况下安全地访问不可变对象,即使这些对象没有使用同步。
不可变对象:状态不可修改,所有域都是final,正确的构造过程。
//将可变对象Date转换为不可变对象 Collections.synchronizedMap(new HashMap<String,Date>());
-
设计线程安全的类
-
找出所有的变量;
-
找出不变性条件;
-
建立并发管理策略;
-
如果一个不变性条件中包含多个变量,那么在修改变量,必须有锁
-
2、day02
-
当在无状态的类中添加一个状态时,如果该状态完全由线程安全的对象来管理,那么这个类仍然是线程安全的。
-
对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个锁来保护。
-
加锁的行为不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作的线程都必须在同一个锁上同步。
-
加锁机制既可以保证可见性,又可以保证原子性;而volatile变量只能保证可见性,并且是一种脆弱的可见性,一般只用于判断状态;
-
**发布:**一个对象的意思是指,对象能够在当前作用域之外的代码中使用。
逸出:某个不该被发布的对象被发布。
-
不要在构造过程中使this引用逸出
-
不可变对象:对象创建后,状态就无法更改,它一定是线程安全的。
对象创建以后,状态无法更改;
对象的所有域都是final类型;
对象是正确创建的,没有构造函数中的this逸出
-
安全的发布对象
在静态初始化函数中初始化一个对象引用;
将对象的引用保存到volatile类型的域或者AtomicReferance对象中;
将对象的引用保存到某个正确构造对象的final类型中
将对象的引用保存到一个由锁保护的域中。
-
如果一个类是由多个独立且线程安全的状态量组成,并且在所有的操作中都不包含无效状态转换,可以将线程安全委托给底层的状态变量。
//线程安全例子,客户端加锁:对于使用某个对象X的客户端代码,使用X本身用于保护其状态的锁来保护这段代码。
public class ListHelper<E>{
public List<E> list = new Collections.synchronizedList(new ArrayList<E>());
public boolean putIfAbsetn(E x){
synchronized(list){
boolean absent = !list.contains(x);
if(absent){
list.add(x);
return absent;
}
}
}
}
3、day03
- 闭锁
public class TestHarness {
public long timeTasks(int nThreads,final Runnable task) throws InterruptedException {
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThreads);
for (int i = 0; i < nThreads; i++) {
Thread t = new Thread(){
@Override
public void run(){
try {
//开始线程阻塞在此处
startGate.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try{
task.run();
}finally {
//结束线程--
endGate.countDown();
}
}
};
t.start();
}
long start = System.nanoTime();
//开始形成开始执行
startGate.countDown();
//结束线程阻塞在此处
endGate.await();
long end = System.nanoTime();
return end-start;
}
public static void main(String[] args) {
TestHarness testHarness = new TestHarness();
}
}
-
中断:一种协作机制,能够使一个线程终止另一个线程的当前工作。
-
中断:它并不会真正的中断一个正在运行的线程,而知识发出了中断请求,然后由线程再下一个合适的时刻中断自己。
-
任务中应该包含取消策略,线程中应该包含中断策略;线程应该只能由其所有者中断。
-
在常规的任务和代码库中都不应该屏蔽中断请求,如果你知道线程将要结束。
-
“毒丸对象”:当得到这个,立即停止。
-
只有当线程本地值的生命周期受限于任务的生命周期时,线程池中的线程使用ThreadLocal才有意义。
-
每当提交一个有依赖性的Executor任务时,要知道可能出现“饥饿死锁”。
-
线程数量:计算密集型:N+1;IO密集型:2N;
-
线程池拒绝策略:调用者执行-caller-runs;调用者去执行这个任务,web项目中,调用者执行任务,导致不会调用accept,tcp队列堆积,继续堆积,tcp丢弃请求,导致服务器性能下降。
-
如果在循环体中包含了一些密集计算或者IO阻塞操作,考虑线程池。
-
如果在持有锁的同时,调用外部方法,可能出现死锁。
-
有界线程池/资源池与相互依赖的任务不能一起使用。
-
活锁:多个相互协作的线程对彼此进行响应修改状态,使得大家都无法继续执行。类似于两个在路上遇到,过于礼貌,都无法前进。
4、day04
-
可伸缩性指的是:当增加计算资源时,程序的吞吐量或者处理能力能相应增加。
-
Amdahl定律:在增加计算资源的情况下,程序在理论上能够实现最高加速比,这个值取决于程序中可并行组件与串行组件所占的比重。
-
有3种方式可以降低锁的竞争程度:
- 减少锁的持有时间;
- 降低锁的请求频率;
- 使用带有协调机制的独占锁-读写锁
-
并发测试:安全性:不发生任何错误的行为;活跃性:某个良好的行为终究会发生。
活跃性测试:性能测试;
吞吐量:一组并发任务中已完成任务所占的比例;
响应性:请求发出到完成的时间;
可伸缩性:增加资源,吞吐量的提升情况。
-
仅当内置锁无法满足时,才考虑使用ReentrantLock
-
在条件等待中存在一种重要的三元关系,加锁、wait方法、和条件
-
每当在等待一个条件时,一定要确保在条件谓词变为真时,发出通知,唤醒等待的线程。
5、day05
-
DCL已经逐渐被抛弃了,以前存在的原因是:无竞争同步的执行速度很慢,以及JVM启动慢,现在延长初始化占位类模式更好。
public class ResourceFactory{ private static ResourceHolder{ public static Resource resoutce = new Resource(); } public static Resource getRource(){ return ResourceHolder.resource; } }
-
不可变对象的初始化安全
public class SafeState{ private final Map<String,String> states; public SafeState(){ states = new HashMap<String,String>(); state.put("ysy","safe"); } public String get(String s){ return states.get(s); } }
初始化安全只能保证通过final域可以达到的值从构造过程完成时开始的可见性。
-
-