1、什么是线程安全?
当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。
2、什么是原子性:
假设有操作A和B,如果从执行A的线程的角度看,当其他线程执行B时,要么B全部执行完成,要么一点都没有执行,这样A和B互为原子参照。一个原子操作是指:该操作对于所有的操作,包括它自己,都满足前面描述的状态。
3、发布和逸出:
发布一个对象的意思是使它能够被当前范围之外的代码所使用。
一个对象在尚未准备好时就将它发布,这种情况称为逸出。
4、线程封闭:
访问共享的、可变的数据要求使用同步。一个可以避免同步的方式就是不共享数据。如果数据仅在单线程中被访问,就不需要任何同步。
5、线程限制:
维护线程限制性的任务全部落在实现上的这种情况。因为没有可见性修饰符与本地变量等语言特性协助将对象限制在目标线程上。所以这种方式是非常容易出错的。事实上,对于像GUI应用中的可视化组件或者数据模型这些线程限制对象,对他们的引用通常是公共域、
6、栈限制:
栈限制是线程限制的一种特例,在栈限制中,只能通过本地变量才可以触及对象。正如封装使不变约束更容易被保持,本地变量使对象更容易被限制在线程本地中。他们存在于执行线程栈。提前线程无法访问这个栈。
7、不可变性:
创建后状态不能被修改的对象叫做不可变对象。不可变对象永远是线程安全的。
8、安全发布的模式:
为了安全地发布对象,对象的引用以及对象的状态必须同时对其他线程课件。一个正确创建的对象可以通过下列条件安全的发布。
a、通过静态初始化器初始化对象的引用
b、将它的引用存储到volatile域或AtomicReference
c、将它的引用存储到正确创建的对象的Final域中
d、或者将它的引用存储到由锁正确保护的域中。
9、设计线程安全的类:
构建线程安全类的技术,包括将线程安全性委托给现有的线程安全类。在实践中,委托是创建线程安全类最有效的策略之一。只需要用已有的线程安全类来管理所有状态即可。
10、同步容器
同步容器类有Vector和Hashtable。
同步容器类都是线程安全的。
11、并发容器:
同步容器通过对容器的所有状态进行串行访问,从而实现了它们的线程安全,这样做的代价是削弱了并发性,当多个线程共同竞争容器级的锁时,吞吐量就会降低。
Java5.0添加了ConcurrentHashMap来替代同步的HashMap。
使用并发容器替换同步容器,这种做法以有很小的风险带来了可扩展性显著的提高。
ConcurrentHashMap使用一个更加细化的锁机制,名为分离锁。这个机制允许更深层次的共享访问。任意数量的读线程可以并发访问Map,读者和写者也可以并发访问Map,并且有限数量的写线程还可以并发修改Map,结果是,为并发访问带来更高的吞吐量,同时几乎没有损失单个线程访问的性能。
12、小结
所有并发问题都归结为如何协调访问并发状态。可变状态越少,保证线程安全就越容易。
尽量将域声明为final类型,除非它们的需要是可变的。
不可变对象天生是线程安全的。
不可变对象极大的减轻了并发编程的压力,他们简单而且安全,可以在没有锁或者防御性复制的情况下自有的共享。
封装使管理复杂度变得更可行。
你固然可以用存储于全局变量的数据来写一个线程安全类,但是你为什么要这样做?在对象中封装数据,让他们能够更加容易的保存不变,在对象中封装同步,使它能够更容易的遵守同步策略。
用锁来守护每一个可变变量。
对同一不变约束中的所有变量都使用相同的锁。
在运行复合操作期间持有锁
在非同步的多线程情况下,访问可变变量的程序是存在隐患的。
不要依赖于可以需要同步的小聪明
在设计过程中就考虑线程安全,或者在文档中明确地说明它不是线程安全的。
13、顺序地执行任务
一次只能处理一个请求,因此在生产环境中的执行效率很差。
14、显示地为任务创建线程
为了提供更好的响应性,可以为每个服务请求创建一个新的线程。在中等强度的负载水平下,该方式对顺序化执行的良好改进,只要请求的到达速度尚未超过服务器的请求处理能力,那么这种方法可以同时带来更快的响应性和更大的吞吐量。
15、无限制创建线程的缺点
线程生命周期的开销
资源消耗量
稳定性
16、Executor框架
任务是逻辑上的工作单元,线程是使任务异步执行的机制。我们已经分析了两种线程执行任务的策略。所有任务在单一的线程中顺序执行,以及每个任务在自己的线程中执行。每一种策略都有严重的局限性,顺序执行会产生糟糕的响应性和吞吐量,每个任务每个线程会给资源管理带来麻烦。
Executor是一个简单的接口,这个框架可以用于异步任务执行。Executor基于生产者、消费者模式,提交任务的执行者是生产者,执行任务的线程是消费者。
可以考虑使用Executor来代替Thead.
17、线程池
在线程池中执行任务线程,这种方法有很多无法比拟的优势。重用存在的线程,而不是创建新的线程,这可以在处理多请求时抵消线程创建、消亡产生的开销。且在请求到达时,工作者线程通常已经存在,用于创建线程的等待事件并不会延迟任务的执行。因此提高了响应性。
18、创建线程池的几种方式:
newFixedThreadPool 创建一个定长的线程池
newCacheThreadPool 创建一个可缓存的线程池
newSingleThreadExecutor 创建一个单线程化的Executor
newScheduledThreadPool 创建一个定长的线程池
19、Executor的生命周期:
运行、关闭、终止
20、任务取消
启动任务和线程都很容易,大多数时候,我们通常允许他们在结束任务后自行停止。但是有时候我们希望在任务或线程自然结束之前就停止他们。JAVA没有提供任何机制,来安全地强迫线程停止手头的工作。它提供中断,使一个线程能够要求另一个线程停止当前的工作。
21、中断
我们对中断最好的理解应该是:它并不会真正中断一个正在运行的线程。仅仅发出中断请求。线程自己会在下一个方便的时刻中断。