Java并发编程艺术读书笔记

1、多线程在CPU切换过程中,由于需要保存线程之前状态和加载新线程状态,成为上下文切换,上下文切换会造成消耗系统内存。所以,可合理控制线程数量。 如何控制:

  (1)使用ps -ef|grep appname,查找appname的pid;如1111

  (2)使用jstack 1111 > /home/ibethfy/dump1,将dump信息追加到dump1

  (3)使用grep java.lang.Thread.State /home/ibethfy/dump1 | awk '{print $2$3$4$5}' | sort | uniq -c,统计查找线程状态个数。分析是否WAITING的线程过多。

  (4)查看这些WAITING的线程时怎么产生的,如某个服务创建线程池中线程过多,分析后对应修改。

  (5)重启后重新统计。

2、避免死锁几种常用方式

  (1)避免一个线程同时获取多个锁。

  (2)避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。

  (3)尝试使用定时锁,lock.tryLock(timeout)来替代内部锁机制。

  (4)对于数据库锁,加锁和解锁必须在同一个连接内,不然会造成解锁失败的情况。

3、volatile保证共享资源对其他线程的可见性。volatile修饰共享变量时,共享变量进行写操作时,会在汇编中多一个lock前缀的指令,该指定表示要将该变量从当前处理器的缓存行数据写到系统内存,写会内存操作会使其他CPU缓存了该内存地址的数据失效。其他CPU去获取该数据时,会从系统内存重新获取。

4、synchronized,JVM通过进入和退出Monitor对象来实现代码块和方法同步。代码块同步使用的是monitorenter和monitorexit来实现。在编译时,会将这两个指令分别插入到代码块开始和结束(包括正常和异常结束),在执行这两句指令时,会去进去和退出monitor对象。

5、锁在哪?Java的锁,是存在对象头的Mark Word中。存储一个锁地址,指向C++里面的一个ObjectMonitor对象,该对象存储了当前占用锁的线程,等待的线程,该线程重入的次数,锁状态等字段。

6、JMM,Java内存模型,用于控制Java线程之间的通信,和计算机多线程模型类似,也是为每个线程分配了类似的内存缓存,变量存在主内存中,要保证线程之间的通信,必须要保证线程A将数据从本地缓存刷新到主内存,线程B识别到数据更新,从主内存中取得最新数据。JMM主要就是控制主内存和线程本地内存之间的交互等。

7、JMM模型中,主要是解决有序性(顺序一致性),可见性,原子性。

  (1)由于多线程中,每个线程有自己的本地缓存,且在执行代码后,不知道何时回把缓存刷新到主内存,所以,线程的操作对其他线程是不可见的。这时不同线程获取共享变量就会出现不一致的问题。

  (2)指令的重排序:重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。数据依赖性:某一行代码依赖上一行代码,这样不会进行这两条代码的重排序,多线程系统不会考虑该问题。顺序一致性:指令重排序后,最终执行结果要和顺序执行结果一致。

  (3)多线程之间进行了正确的数据同步,JMM保证多个线程的顺序一致性。

  (4)顺序一致性模型:一个程序的操作必须按照程序顺序执行。不管程序是否同步,所有线程都能按照单一的操作执行顺序。即多个线程的操作可以并行执行,但对于单个线程来看,他的指令时顺序执行的,且每条指令都对其他线程可见。

  (5)JMM不保证顺序一致性,即未同步的线程,执行顺序是无序的,且可能对其他内存不可见。这个时候,就需要同步来实现顺序一致性模型。synchronized修饰的代码块,在获得锁后才能执行,从而保证了多个线程的整体顺序执行,但同步块局部不依赖的指令,也可进行重排序(尽量给编译器或者优化器优化提供入口)。为什么JMM不保证未同步的线程的顺序一致性呢?主要是要保证会禁止大量的编译器等优化,严重影响性能,所以就提供同步方法来实现。

  (6)JMM内存可见性的保证:单线程不会出现内存可见性问题,编译器,runtime,处理器会共同保证期执行结果和顺序一致性模型结果一致;多线程,正确同步的多线程将具有顺序一致性,JMM通过限制编译器和处理器的重排序来提供内存可见性的保证。未同步或未正确同步的多线程,JMM为他们提供了最小安全性的保证,线程读取到的值,要么是之前线程写入的值,要么是初始值。

  (7)volatile,当写volatile时,其他读写指令在其之后执行,保证了有序性;当写时,会把新的缓存中的值写入到主内存;另一线程读时,会将本地缓存地址置为无效,从主内存中获取该值。

  (8)synchronized,当某线程获得锁时,其余线程等待,保证了有序性。当某线程释放锁时,会将本地缓存写入主内存,当另一线程获取锁时,会将本地缓存置为无效,从主内存获取数据。所以保证了多线程的顺序一致性和可见性。

  (9)happens-before规则。

8、wait和notify:WaitThread首先获取了对象的锁,然后调用锁对象的wait()方法,从而放弃了锁并进入了对象的等待队列WaitQueue中,进入等待状态。由于WaitThread释放了对象的锁,NotifyThread随后获取了对象的锁,并调用锁对象的notify()方法,将WaitThread从WaitQueue移到SynchronizedQueue中,此时WaitThread的状态变为阻塞状态。NotifyThread释放了锁之后,WaitThread再次获取到锁并从wait()方法返回继续执行。金典案例如下: synchronized(对象) {        

  while(条件不满足) {              

     对象.wait();      

    }      

    对应的处理逻辑

}

synchronized(对象) {

       改变条件        

  对象.notifyAll();

}

9、ThreadLocal:ThreadLocal,即线程变量,是一个以ThreadLocal线程对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。

10、CountDownLatch:

  (1)、初始化CountDownLatch cdl  = new CountDownLatch(5);

  (2)、同一线程或多个线程调用cdl.countDown(),使计数器减一,通过入参传入cdl;等待线程调用cdl.await(),阻塞等待cdl计数器等于0后,执行。

11、CyclicBarrier:

  (1)、初始化CyclicBarrier cb = new CyclicBarrier();

  (2)、其余线程调用cb.await(),告诉屏障我已到达,然后等待指定数量的线程都已到达后,然后统一往后执行。

  (3)、构造函数可传入另一线程,表示到达屏障的数量满足后,优先执行该方法。

12、Semaphore:Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。Semaphore的用法也很简单,首先线程使用Semaphore的acquire()方法获取一个许可证,使用完之后调用release()方法归还许可证。还可以用tryAcquire()方法尝试获取许可证。

13、Exchanger:用于线程间数据交换。

14、线程池技术:好处

  (1)降低系统消耗,重复利用线程

  (2)提高响应速度,不用重新创建线程

  (3)提高可管理性,可以实现对线程的管理。

线程池流程:

  (1)新来一个任务,如果没有超过核心线程数,则创建一个核心线程,执行任务,不管是否有闲置的核心线程,也会创建新的核心线程执行任务。

  (2)如果核心线程都处于运行状态,且数量已达上限,则判断工作队列是否已满,如果未满,则将任务加入工作队列,排队等待核心线程空闲后执行。

  (3)如果核心线程都在运行任务和工作队列都满了,则创建新的额外线程来执行任务。(额外线程由最大线程决定,且无用后会被回收)

  (4)额外线程也满了且都在执行的话,就采用拒绝策略拒绝请求。

线程池创建参数:

  (1)corePoolSize,核心线程数量。

  (2)runnableTaskQueue:任务队列,有ArrayBlockingQueue,基于数组的有界阻塞队列,FIFO;LinkedBlockingQueue,基于链表实现的阻塞队列,FIFO,吞吐量通常高于ArrayBlockingQueue,静态工厂方法Executors.newFixedThreadPool()使用了这个队列;SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列;

  (3)maximumPoolSize:线程池最大线程数,线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没什么效果。

  (4)ThreadFactory:用于设置创建线程的工厂;

  (5)RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下四种策略: ·AbortPolicy:直接抛出异常。 ·CallerRunsPolicy:只用调用者所在线程来运行任务。 ·DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。 ·DiscardPolicy:不处理,丢弃掉

  (6)keepAliveTime(线程活动保持时间):线程池的工作线程空闲后(非核心线程会回收,allowCoreThreadTimeOut(true),设置这个属性,核心线程在超时未用后,也会被回收),保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。

  (7)TimeUnit(线程活动保持时间的单位);

猜你喜欢

转载自www.cnblogs.com/ibethfy/p/10119329.html