<java并发编程实战>阅读总结(b)

来源:互联网 发布:海外网络推广 编辑:程序博客网 时间:2024/06/10 07:50

5、Executor框架

Executor框架是并发集合java.util.concurrent中的一个成员。

Executor为灵活且强大的异步任务执行框架提供了基础,还提供了对生命周期的支持,以及统计信息、应用管理机制和性能监视等机制。
Executor 最早是为了解决生产者-消费者模式而引入的。提交任务相当是生产者,执行任务相当是消费者。

线程池:(翻译的文档):
线程池和工作者队列密切相关,工作者线程的任务:从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。
Executors类里面提供了一些静态工厂,生成一些常用的线程池。
newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
newSingleThreadScheduledExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

线程池Executor任务拒绝策略(翻译的文档):
java.util.concurrent.RejectedExecutionHandler描述的任务操作。
第一种方式直接丢弃(DiscardPolicy)
第二种丢弃最旧任务(DiscardOldestPolicy)
第三种直接抛出异常(AbortPolicy)
第四种任务将有调用者线程去执行(CallerRunsPolicy)


生命周期(翻译的文档):
java.util.concurrent.ExecutorService 接口对象来执行任务,该接口对象通过工具类java.util.concurrent.Executors的静态方法来创建。 Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。
ExecutorService扩展了Executor并添加了一些生命周期管理的方法。一个Executor的生命周期有三种状态,运行 ,关闭 ,终止。
Executor创建时处于运行状态。当调用ExecutorService.shutdown()后,处于关闭状态,isShutdown()方法返回true。这时,
不应该再想Executor中添加任务,所有已添加的任务执行完毕后,Executor处于终止状态,isTerminated()返回true。
shutdown():执行平缓的关闭过程,不再接受新的任务,同时等待已经提交的任务执行完成。
shutdownNow();执行粗暴的关闭过程,尝试取消所有运行中的任务,并且不再启动队列中尚未开始启动的任务。
awaitTermination: 这个方法有两个参数,一个是timeout即超时时间,另一个是unit即时间单位。
这个方法会使线程等待timeout时长,当超过timeout时间后,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。
ExecutorService service = Executors. newFixedThreadPool(3);  
         for ( int i = 0; i < 10; i++) {  
             System. out.println( "创建线程" + i);  
             Runnable run = new Runnable() {  
                 @Override  
                 public void run() {  
                     System. out.println( "启动线程");  
                 }  
             };  
             // 在未来某个时间执行给定的命令  
             service.execute(run);  
         }  
         // 关闭启动线程  
         service.shutdown();  
         // 每隔1秒监测一次ExecutorService的关闭情况.  
         service.awaitTermination(1, TimeUnit. SECONDS);  
         System. out.println( "all thread complete");  

         System. out.println(service.isTerminated());  

6、锁机制引入的死锁、活锁和饥饿
加锁机制的目的是保证线程安全,但如果过度使用锁,会导致死锁等。我们使用线程池和信号量来限制对资源的限制。但这些被限制的行为可能导致死锁。
死锁:进程之间互相争夺资源导致系统无法向前运行的一种僵死状态,如果没有外力作用下系统很难继续向前运行。请参考我的博客多线程。
饥饿:当线程无法访问到它所需要的资源而不能继续执行时,就会发生饥饿。引发饥饿的最常见资源就是CUP的时钟周期。
活锁:liveLock是进程的状态还在发生变化但是不再继续向前运行的状态。


7、线程的开销:
(1)、上下文切换。cpu在做线程切换的时候,需要保存当前线程执行的上下文,并且新调度进来的线程执行上下文设置为当前上下文。
发生越多的上下文切换,增加了调度开销,并因此降低吞吐量。
(2)、内存数据的同步。synchronized发生隐式锁竞争的地方带来的开销会影响其它线程的性能。
(3)、阻塞。当在锁上发生竞争时,竞争失败的线程会阻塞,即所谓的竞态条件。JVM通过循环不断的尝试获取锁,直到成功。或者通过操作系统挂起阻塞的线程。
如果时间短,采用等待方式,如果时间长才适合采用线程挂起的方式。串行操作降低可伸缩性,并行切换上下文也会降低性能。
在锁发生竞争时,会同时导致上面两种问题,因此,减少锁的竞争能够提高性能和收缩性。在并发程序中,
对可伸缩性最主要的威胁就是独占方式的资源锁。两个因素将影响锁上面发生竞争的可能性:锁的请求频率以及每次持有该锁的时间。
如果两者的乘积很小,那么大多数获取锁操作都不会发生竞争。

三种方式可以降低锁的竞争程度:
(1)、降低锁的请求频率。
降低线程请求锁的频率,可以通过锁分解和锁分段等技术来实现。即减小锁的粒度。如果一个锁同时需要保护好几个状态变量,那么可以把这个锁分解成多个锁,并且每个锁只保护一个状态变量,从而提高可伸缩性,并最终降低每个锁的请求频率。但是使用的锁越多,发生死锁的风险也会越高。

(2)、减少锁的持有时间。
减少被锁部分的加锁时间。缩小锁的范围(快进快出),可以将一些与锁无关的代码移出同步代码块,尤其是开销较大的操作,以及可能被阻塞的操作,比如I/O 操作。

(3)、减少使用独占锁,并发容器,读-写锁,不可变对象以及原子变量。

独占锁是一种悲观的技术。它假设最坏的情况发生(如果不加锁,其它线程会破坏对象状态),即使没有发生最坏的情况,仍然用锁保护对象状态

8、原子变量
原子性
采用锁技术会导致对锁的竞争导致系统性能降低。当一个线程正在等待锁时,它不能做任何其他事情。如果一个线程在持有锁的情况下被延迟执行,那么所有需要这个锁的线程都无法执行下去。
原子性是指cpu在执行每一段代码时是不能被中断的。对除了long和double的基本类型的数据的简单操作都是原子的。例如a = 1; return a;
原子变量支持不用锁保护就能原子性更新操作,其底层用CAS实现。
一共有12个原子变量,可分为4组:标量类、更新器类、数组类以及复合变量类。

最常用的原子变量就是标量类:AtomicInteger、AtomicLong、AtomicBoolean以及AtomicReference。所有类型都支持CAS。

JVM对CAS的支持
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
CAS典型使用模式是:首先从V中读取A,并根据A计算新值B,然后再通过CAS以原子方式将V中的值由A变成B(只要在这期间没有任何线程将V的值修改为其他值)。
转载的博文:http://blog.csdn.net/csujiangyu/article/details/44002463
说明比较并交换的行为(而不是性能)的代码:
public class SimulatedCAS {
     private int value;

     public synchronized int getValue() { return value; }

    public synchronized int compareAndSwap(int expectedValue, int newValue) {
         int oldValue = value;
         if (value == expectedValue)
             value = newValue;
         return oldValue;
     }
}
使用比较并交换实现计数器:
public class CasCounter {
    private SimulatedCAS value;
    public int getValue() {
        return value.getValue();
    }
    public int increment() {
        int oldValue = value.getValue();
        while (value.compareAndSwap(oldValue, oldValue + 1) != oldValue)
            oldValue = value.getValue();
        return oldValue + 1;
    }
}

非阻塞同步机制

非阻塞算法的定义:一个线程的失败或者挂起,不会影响其它线程的失败或者挂起。
非阻塞算法提供比synchronized机制更高的性能和可收缩性。可以使多个线程在竞争相同的数据时候不会发生阻塞。
基于锁的算法中可能会出现各种活跃性的障碍,比如I/O 阻塞,导致其它线程都无法进行下去,导致性能下降。
1 0
原创粉丝点击