java高并发程序设计 笔记 2017

  • 葛一鸣 郭超 编著

1.3 并发的级别

根据控制并发的策略,大致可以分为阻塞、无饥饿、无障碍、无锁、无等待。

1.5.4 哪些指令不能重排:Happen-Before规则

  • 程序顺序原则:一个线程内保证语义的串行性
  • volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性
  • 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
  • 传递性:A先于B,B先于C,那么A必然先于C
  • 线程的start()方法先于它的每一个动作
  • 线程的所有操作先于线程的终结(Thread.join())
  • 线程的中断(interrupt())先于被中断线程的代码
  • 对象的构造函数执行、结束先于finalize()方法

2.1 线程的所有状态

public enum State{
    NEW,
    RUNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

2.2.2 终止线程

Thread.stop() 是被废弃的方法,结束线程时,会直接终止线程,并且会立即释放这个线程所持有的锁

2.2.3 线程中断

与线程中断有关的三个方法,这三个方法看起来很像,可能会引起混淆和勿用。

public void Thread.interrupt()   //中断线程
public boolean Thread.isInterrupted()  //判断是否被中断
public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态
  • Thead.sleep() 函数

native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中

public static native void sleep(long millis) throws InterruptedException

2.2.4 wait() notify()

public final void wait() throws InterruptException
public final native void notify()

wait() notify() 之前,必须获得object对象的锁

2.2.5 挂起suspend 继续执行resume

这两个方法都被废弃,suspend() 导致线程在暂停的同时,并不会去释放任何锁资源

3.1.1 重入锁

public static ReentrantLock lock = new ReentrantLock();
// new ReentrantLock(true); 表示是公平锁,性能较低 
lock.lock();
try{
    lock.lockInterruptibly();//可以对中断进行响应
    if(lock.tryLock(5,TimeUnit.SECONDS)){} //限时等待
}catch(InterruptedException e){
    e.printStackTrace();
}finally{
    lock.unlock();
}

3.1.2 重入锁的好搭档 Condition条件

Conditon接口提供的基本方法如下:

void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimesout) throws InterruptedException;
boolean await(long time,TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();

以上方法的含义如下:

  • await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()或者signalAll()方法时,线程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait() 方法相似。
  • awaitUninterruptibly()方法与await()方法基本相同,但是它并不会在等待过程中响应中断。
  • singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程。这和Object.notify()很类似。
public static ReentranLock lock = new ReentranLock();
public static Condition condition = lock.newCondition();
condition.await();

3.1.3 允许多个线程同时访问:信号量(Semaphore)

主要构造函数

public Semaphore(int permits)
public Semaphore(int permits,boolean fair) //第二个参数可以指定是否公平

主要逻辑方法

public void acquire()
public void acquireUninterrptibly()
public boolean tryAcquire()
public boolean tryAcquire(long timeout,TimeUnit unit)
public void release()

3.1.4 ReadWriteLock读写锁

private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock readLock = readWriteLock.writeLock();

3.1.5 倒计时器:CountDownLatch

可以让某个线程等待直到倒计时结束,再开始执行

3.1.6 循环栅栏:CyclicBarrier

public CyclicBarrier(int parties,Runnable barrierAction)

3.1.7 线程阻塞工具类:LockSupport

它可以在线程内任意位置让线程阻塞。和Thread.suspend()相比,它弥补了由于resume()在前发生,导致线程无法继续执行的情况。和Object.wait()相比,它不需要先获得某个对象的锁,也不会抛出InterruptException。

LockSupport.park();
LockSupport.unpark(t1);//t1 为线程。

除了有定时阻塞的功能外,LockSupport.park()还支持中断影响。但是和其他接收中断的函数不一样,LockSupport.park()不会抛出InterruptedException异常。它只会默默的返回。但是可以从Thread.interrupted()等方法获得中断标记。

LockSupport.park();
if(Thread.interrupted()){
    ...
}

3.2.2 线程池

Executor框架提供了各种类型的线程池,主要有以下工厂方法:

public static ExecutorService newFixedThreadPool(int nThreads) 
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor() 
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
  • 计划任务
    ScheduledExecutorService 对象,可以根据时间需要对线程进行调度
public ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);

scheduleAtFixedRate 第一个任务在initialDelay + period执行,第二个在initialDelay + 2 * period执行,以此类推。

scheduleAtFixedDelay 第一个任务在initialDelay执行,指 delay上一个任务和下一个任务的延时

3.2.3核心线程池的内部实现

线程池都是使用了ThreadPoolExecutor实现。其构造函数

public ThreadPoolExecutor(int corepoolSize,int maxinumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)

3.2.4 拒绝策略

ThreadPoolExecutor 最后一个参数指定了拒绝策略,JDK内置的拒绝策略

  • AbortPolicy策略:该策略会直接报错异常,阻止系统正常工作
  • CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不好真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
  • DiscardOledestPolicy策略:改策略将丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
  • DiscardPolicy策略:该策略默默地丢弃无法处理的任务,不予任何处理。

自定义策略:实现RejectedExecutionHandler

3.2.7

java中可以通过下面的方式获得可用的cpu数量

Runtime.getRuntime().availableProcessors()

3.2.8

自定义TraceThreadPoolExecutor,打印错误的堆栈信息

public class TraceThreadPoolExecutor extends ThreadPoolExecutor{

	public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
			BlockingQueue<Runnable> workQueue) {
		super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
	}

	@Override
	public void execute(Runnable task) {
		super.execute(wrap(task, clientTrace(), Thread.currentThread().getName()));
	}
	@Override
	public Future<?> submit(Runnable task){
		return super.submit(wrap(task, clientTrace(), Thread.currentThread().getName()));
	}
	
	private Exception clientTrace(){
		return new Exception("Client stack trace");
	}
	private Runnable wrap(final Runnable task,final Exception clientStack,String clientThreadname){
		return new Runnable() {
			
			@Override
			public void run() {
				try {
					task.run();
				} catch (Exception e) {
					clientStack.printStackTrace();
					throw e;
				}
				
			}
		};
	}
	
	public static void main(String[] args) {
		ThreadPoolExecutor pools = new TraceThreadPoolExecutor(0, Integer.MAX_VALUE, 0L, 
				TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
		for(int i=0;i<5;i++){
			pools.execute(new Task(100,i));
		}
	}

}

3.2.9 分而治之:Fork/Join框架

3.3.1 并发集合

ConcurrentHashMap
CopyOnWriteArrayList
ConcurrentLinkedQueue
BlockingQueue
ConcurrentSkipListMap ConcurrentSkipListSet

1.BlockingQueue定义的常用方法如下

抛出异常 返回特殊值 一直阻塞 超时退出
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take() poll(time,unit)
检查 element() peek() 不可用 不可用
  • 抛出异常:是指当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException("Queue full")异常。当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常 。
  • 返回特殊值:插入方法会返回是否成功,成功则返回true。移除方法,则是从队列里拿出一个元素,如果没有则返回null
  • 一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到拿到数据,或者响应中断退出。当队列空时,消费者线程试图从队列里take元素,队列也会阻塞消费者线程,直到队列可用。
  • 超时退出:当阻塞队列满时,队列会阻塞生产者线程一段时间,如果超过一定的时间,生产者线程就会退出。
  1. add(anObject):把anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则抛出异常
  2. offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.
  3. put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
  4. poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null
  5. take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止

其中:BlockingQueue 不接受null 元素。试图add、put 或offer 一个null 元素时,某些实现会抛出NullPointerException。null 被用作指示poll 操作失败的警戒值。

2、BlockingQueue的几个注意点

【1】BlockingQueue 可以是限定容量的。它在任意给定时间都可以有一个remainingCapacity,超出此容量,便无法无阻塞地put 附加元素。没有任何内部容量约束的BlockingQueue 总是报告Integer.MAX_VALUE 的剩余容量。

【2】BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持Collection 接口。因此,举例来说,使用remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常不 会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。

【3】BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操作(addAll、containsAll、retainAll 和removeAll)没有 必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。

【4】BlockingQueue 实质上不支持使用任何一种“close”或“shutdown”操作来指示不再添加任何项。这种功能的需求和使用有依赖于实现的倾向。例如,一种常用的策略是:对于生产者,插入特殊的end-of-stream 或poison 对象,并根据使用者获取这些对象的时间来对它们进行解释。

3.3.2 线程安全的HashMap

性能不高,一般使用ConcurrentHashMap

public static Map m = Collection.synchronizedMap(new HashMap());

4.2 Java虚拟机对锁优化所做的努力

4.2.1 锁偏向

4.2.2 轻量级锁

4.2.3 自旋锁

4.2.4 锁消除

4.4.4 无锁的对象引用:AtomicReference

4.4.5 带有时间戳的对象引用:AtomicStamedReference

4.4.6 数组也能无锁:AtomicIntegerArray

4.4.7 让普通变量也享受原子操作:AtomicIntegerFieldUpdater

4.4.8 挑战无锁算法:无锁的Vector实现

private final AtomicReferenceArray<AtomicReferrenceArray<E>> buckets;

4.5 有关死锁问题

  • 使用jps命令得到java进程的id,接着用jstack得到线程的线程堆栈。
jps
jstack 3992

第5章 并行模式和算法

高效的单例模式(无锁,使用了延迟加载)

public class StaticSingleton {
	private StaticSingleton(){
		System.out.println("StaticSingleton is create");
	}
	private static class SingletonHolder{
		private static StaticSingleton instance = new StaticSingleton();
	}
	public static StaticSingleton getInstance(){
		return SingletonHolder.instance;
	}

}

5.4

  • BlockingQueue使用锁和阻塞来实现线程间的同步,高并发下性能不高。
  • ConcurrentLinkedQueue是一个高性能的队列

5.4.1 无锁的缓存框架:Disruptor

posted @ 2020-02-28 21:05  my_flash  阅读(189)  评论(0)    收藏  举报