《实战Java高并发程序设计》学习总结(2)

第3章  JDK并发包

1 synchronized的功能扩展:重入锁。使用java.util.concurrent.locks.ReentrantLock类来实现。

import java.util.concurrent.locks.ReentrantLock;

public class ReenterLock implements Runnable {

	public static ReentrantLock lock = new ReentrantLock();
	public static int i = 0;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int j=0;j<100000000;j++){
			lock.lock();
			try{
				i++;
			}finally{
				lock.unlock();
			}
		}
	}
	public static void main(String[] args) throws InterruptedException {
		ReenterLock tl = new ReenterLock();
		Thread t1 = new Thread(tl);
		Thread t2 = new Thread(tl);
		t1.start();t2.start();
		t1.join();t2.join();
		System.out.println(i);
	}

}

重入锁比synchronized灵活外,还提供一些高级功能

  • 中断响应。synchronized必须要等到锁才能继续执行,而重入锁是支持线程可以被中断。
import java.util.concurrent.locks.ReentrantLock;

public class IntLock implements Runnable{

	public static ReentrantLock lock1 = new ReentrantLock();
	public static ReentrantLock lock2 = new ReentrantLock();
	int lock;
	
	public IntLock(int lock){
		this.lock = lock;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try{
			if(lock == 1){
				lock1.lockInterruptibly();
				try{
					Thread.sleep(500);
				}catch(InterruptedException e){
					
				}
				lock2.lockInterruptibly();
			}else{
				lock2.lockInterruptibly();
				try{
					Thread.sleep(500);
				}catch(InterruptedException e){
					
				}
				lock1.lockInterruptibly();
			}
		}catch(InterruptedException e){
			e.printStackTrace();
		}finally{
			if(lock1.isHeldByCurrentThread())
				lock1.unlock();
			if(lock2.isHeldByCurrentThread())
				lock2.unlock();
			System.out.println(Thread.currentThread().getId()+":线程退出");
		}
	}
	public static void main(String[] args) throws InterruptedException{
		IntLock r1 = new IntLock(1);
		IntLock r2 = new IntLock(2);
		Thread t1 = new Thread(r1);
		Thread t2 = new Thread(r2);
		t1.start();t2.start();
		Thread.sleep(1000);
		t2.interrupt();
	}
	
}
  • 锁申请等待限时,使用tryLock()
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class TimeLock implements Runnable {

	public static ReentrantLock lock = new ReentrantLock();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try{
			if(lock.tryLock(5,TimeUnit.SECONDS)){  //5秒内无法申请到锁则失败
				Thread.sleep(6000);
			}else{
				System.out.println("get lock failed");
			}
		}catch(InterruptedException e){
			e.printStackTrace();
		}finally{
			if(lock.isHeldByCurrentThread())
				lock.unlock();
		}
	}
	public static void main(String[] args){
		TimeLock tl = new TimeLock();
		Thread t1 = new Thread(tl);
		Thread t2 = new Thread(tl);
		t1.start();
		t2.start();
	}

}
  • 公平锁,当参数fair为true时表示锁为公平。但公平锁的实现成本高性能相对不佳,默认锁是非公平的

                  public ReentrantLock( boolean fair )

import java.util.concurrent.locks.ReentrantLock;

public class FairLock implements Runnable{

	public static ReentrantLock fairLock = new ReentrantLock(true);
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			try{
				fairLock.lock();
				System.out.println(Thread.currentThread().getName()+" 获得锁");
			}finally{
				fairLock.unlock();
			}
		}
	}
	public static void main(String[] args) throws InterruptedException{
		FairLock fl = new FairLock();
		Thread t1 = new Thread(fl,"Thread_t1");
		Thread t2 = new Thread(fl,"Thread_t2");
		t1.start();t2.start();
	}

}

输出 (交替获取)

Thread_t1 获得锁

Thread_t2 获得锁

Thread_t1 获得锁

Thread_t2 获得锁

Thread_t1 获得锁

Thread_t2 获得锁

Thread_t1 获得锁

Thread_t2 获得锁

Thread_t1 获得锁

Thread_t2 获得锁

Thread_t1 获得锁

Thread_t2 获得锁

Thread_t1 获得锁

Thread_t2 获得锁

Thread_t1 获得锁

Thread_t2 获得锁

Thread_t1 获得锁

2  重入锁ReentrantLock的几个重要方法:

  • lock( ) :获得锁,如果锁已经被占用则等待。
  • lockInterruptibly( ):获得锁,但优先响应中断
  • tryLock( ):尝试获得锁,如果成功返回true,失败返回false。不等待立即返回
  • tryLock( long time , TimeUnit unit ):在给定时间内尝试获得锁 

3 重入锁的好搭档:Condition条件。通过Lock接口的Condition newCondition( )方法可以生成一个与当前重入锁绑定的Condition实例。利用Condition对象,可以让线程在合适的时间等待或得到通知继续执行。接口基本方法如下

  • void await( )  throws InterruptedException    // 当前线程等待,释放锁,其他线程用signal()或signalAll()时,线程重新获取锁继续执行。或当线程被中断时也能跳出等待
  • void awaitUninterruptibly( ) // 跟await()基本相同,只是它不会在等待过程中响应中断
  • void awaitNanos(long nanosTimeout) throws InterruptedException 
  • boolean await(long time , TimeUnit unit) throws InterruptedException
  • boolean awaitUntil(Date deadline) throws InterruptedException
  • void signal( )   // 用于唤醒一个在等待中的线程
  • void signalAll( )
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockCondition implements Runnable {

	public static ReentrantLock lock = new ReentrantLock();
	public static Condition condition = lock.newCondition();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try{
			lock.lock();
			condition.await();    // 等待通知
			System.out.println("Thread is going on");
		}catch(InterruptedException e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public static void main(String[] args) throws InterruptedException{
		ReentrantLockCondition tl = new ReentrantLockCondition();
		Thread t1 = new Thread(tl);
		t1.start();
		Thread.sleep(2000);
		lock.lock();    // 调用signal()前要求当前线程先获取相关的锁
		condition.signal();
		lock.unlock();  // 调用signal()后要求释放当前锁谦让给唤醒的线程
	}

}

注:调用signal()前要求当前线程先获得相关的锁,调用后也要释放锁给唤醒的线程。

4 允许多个线程同时访问:信号量(Semaphore) ,可以指定多个线程同时访问某一个资源。函数如下

  • public Semaphore(int permits)  // 必须指定信号量的准入数,每个线程只能申请一个许可。
  • public Semaphore(int permits , boolean fair)    // 第二个参数指定是否公平
  • public void acquire( )   // 尝试获得一个准入许可,无法获得则等待,直到有线程释放一个许可或当前线程被中断
  • public void acquireUninterruptibly( )  // 跟acquire()基本一样,只是不响应中断
  • public boolean tryAcquire( )  // 尝试获得一个准入许可,获得则返回true,失败则返回false。不会等待立即返回
  • public boolean tryAcquire(long timeout , TimeUnit unit)  
  • public void release( ) // 用于在线程访问资源结束后释放一个许可

5 ReadWriteLock 读写锁,要比重入锁或内部锁更针对

6 倒计时器:CountDownLatch  ,是一个非常实用的多线程控制工具类。通常用来控制线程等待,让一个某个线程等待直到倒计时结束再开始执行。构造函数如下

        public  CountDownLatch(int count)    // count为计数器的计数个数

public class CountDownLatchDemo implements Runnable {

    // 设置计数10的倒计时器
	static final CountDownLatch end = new CountDownLatch(10);
	static final CountDownLatchDemo demo = new CountDownLatchDemo();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try{
			Thread.sleep(new Random().nextInt(10)*1000);
			System.out.println("check complete");
            // 倒计时器倒数一次
			end.countDown();
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
	public static void main(String[] args) throws InterruptedException{
		ExecutorService exec = Executors.newFixedThreadPool(10);
		for(int i=0;i<10;i++){
			exec.submit(demo);
		}
        // 倒计时开始,一直等待,只有倒计时器倒数完成后才会往下执行
		end.await();
		System.out.println("Fire!");
		exec.shutdown();
	}

}

7 循环栅栏:CyclicBarrier,是一种多线程并发控制实用工具。跟CountDownLatch类似,但更复杂强大。构造函数如下

         public CyclicBarrier( int parties , Runnable barrierAction )  // parties为计数总数,barrierAction为计数完成后要去执行的任务

public class CyclicBarrierDemo {

	public static class Soldier implements Runnable {
		private String soldier;
		private final CyclicBarrier cyclic;
		Soldier(CyclicBarrier cyclic,String soldierName){
			this.soldier = soldierName;
			this.cyclic = cyclic;
		}
		@Override
		public void run() {
			// TODO Auto-generated method stub
			try{
                // 调用一次倒计时一次,够计数总数后,cyclic会自动调用BarrierRun,继续执行下面的操作,即doWork()
				cyclic.await();
				doWork();
                // 又重新倒计时,调用一次倒计时一次,够计数总数后,cyclic会自动调用BarrierRun,继续执行下面的操作,即doWork()
				cyclic.await();
			}catch(InterruptedException e){
				e.printStackTrace();
			}catch(BrokenBarrierException e){
				e.printStackTrace();
			}
		}
		void doWork(){
			try{
				Thread.sleep(Math.abs(new Random().nextInt()%10000));
			}catch(InterruptedException e){
				e.printStackTrace();
			}
			System.out.println(soldier+": 任务完成");
		}
		
	}
	public static class BarrierRun implements Runnable{

		boolean flag;
		int N;
		public BarrierRun(boolean flag,int N){
			this.flag = flag;
			this.N = N;
		}
		@Override
		public void run() {
			// TODO Auto-generated method stub
			if(flag){
				System.out.println("司令: [士兵"+N+"个,任务完成!]");
			}else{
				System.out.println("司令: [士兵"+N+"个,集合完毕!]");
				flag = true;
			}
		}
		
	}
	public static void main(String[] args) throws InterruptedException {
		final int N = 10;
		Thread[] allSoldier = new Thread[N];
		boolean flag = false;
		CyclicBarrier cyclic = new CyclicBarrier(10,new BarrierRun(flag,N));
		System.out.println("集合队伍!");
		for(int i=0;i<N;i++){
			System.out.println("士兵"+i+" 报道!");
			allSoldier[i] = new Thread(new Soldier(cyclic,"士兵i"));
			allSoldier[i].start();
		}
	}
}

8 线程阻塞工具类:LockSupport,是一种非常方便实用的线程阻塞工具。可以在线程内任意位置让线程阻塞。

public class LockSupportDemo {

	public static Object u = new Object();
	static ChangeObjectThread t1 = new ChangeObjectThread("t1");
	static ChangeObjectThread t2 = new ChangeObjectThread("t2");
	
	public static class ChangeObjectThread extends Thread{
		public ChangeObjectThread(String name){
			super.setName(name);
		}

		@Override
		public void run() {
			// TODO Auto-generated method stub
			synchronized(u){
				System.out.println("in "+getName());
				LockSupport.park();  // 阻塞当前线程
			}
		}
	}
	public static void main(String[] args) throws InterruptedException{
		t1.start();
		Thread.sleep(100);
		t2.start();
		LockSupport.unpark(t1);  // 继续执行
		LockSupport.unpark(t2);  // 继续执行
		t1.join();
		t2.join();
	}
}

LockSupport类为每个线程准备了一个许可,如果许可可用,park()函数会立即返回并消费这个许可,即标记该许可为不可用;如果不可用则会阻塞。unpack()则是将许可变成可用。这样保证unpack()和pack()的顺序总是相对稳定的。

pack(Object)函数还可以为当前线程设置一个阻塞对象。

LockSupport.pack()支持中断,而且还不会抛出InterruptedException异常。如下

public static class ChangeObjectThread extends Thread{
		public ChangeObjectThread(String name){
			super.setName(name);
		}

		@Override
		public void run() {
			// TODO Auto-generated method stub
			synchronized(u){
				System.out.println("in "+getName());
				LockSupport.park();
				if(Thread.interrupted()){
					System.out.println(getName()+" 被中断了");
				}
			}
			System.out.println(getName()+" 执行结束");
		}
	}
	public static void main(String[] args) throws InterruptedException{
		t1.start();
		Thread.sleep(100);
		t2.start();
		t1.interrupt();
		LockSupport.unpark(t2);
	}
}

9 线程池为了避免系统频繁地创建和销毁线程,并且复用创建地线程。在线程池中,有几个活跃地线程,当需要线程时从中取一个空闲的线程。完成任务后再退回到线程池中JDK提供了一套Executor框架帮助线程控制,其本质就是一个线程池

以上成员均在java.util.concurrent包中,是JDK并发包的核心类。其中ThreadPoolExecutor表示一个线程池。Executors类则扮演线程池工厂的角色。通过Executors可以取得一个拥有特定功能的线程池。

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

  • public static ExecutorService newFixedThreadPool( int nThreads)  //返回一个固定线程数量的线程池,该线程池中的线程数量固定不变。有新任务时,池中若有空闲线程则立即执行。否则存在任务队列中排队等候
  • public static ExecutorService newSingleThreadExecutor()  //该方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会保存到任务队列排队执行
  • public static ExecutorService newCachedThreadPool()  // 该方法返回一个可根据实际情况调整线程数量的线程池。线程数量不确定。如有空闲线程可复用优先使用。如没有空闲则创建新的线程执行任务。
  • public static ScheduledExecutorService newSingleThreadScheduledThreadPool()  // 该方法返回一个ScheduledExecutorService对象,线程池大小为1。
  • public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize )  // 该方法返回一个ScheduledExecutorService对象,但该线程池可以指定线程数量

例子

public class ThreadPoolDemo {

	public static class MyTask implements Runnable{

		@Override
		public void run() {
			// TODO Auto-generated method stub
			System.out.println(System.currentTimeMillis()+": Thread ID: "+Thread.currentThread().getId());
			try{
				Thread.sleep(1000);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		
	}
	public static void main(String[] args){
		MyTask task = new MyTask();
		ExecutorService es = Executors.newFixedThreadPool(5);
		//ExecutorService es = Executors.newSingleThreadExecutor();
		//ExecutorService es = Executors.newCachedThreadPool();
		for(int i=0;i<10;i++){
			es.submit(task);
		}
	}
}

// 当使用newFixedThreadPool(5),输出如下,5个线程复用
1535423404418: Thread ID: 9
1535423404418: Thread ID: 12
1535423404418: Thread ID: 11
1535423404418: Thread ID: 10
1535423404418: Thread ID: 8
1535423405420: Thread ID: 8
1535423405420: Thread ID: 9
1535423405420: Thread ID: 10
1535423405420: Thread ID: 11
1535423405420: Thread ID: 12

// 当使用newSingleThreadExecutor(),只有一个线程复用,输出如下
1535423548649: Thread ID: 8
1535423549655: Thread ID: 8
1535423550656: Thread ID: 8
1535423551660: Thread ID: 8
1535423552665: Thread ID: 8
1535423553667: Thread ID: 8
1535423554669: Thread ID: 8
1535423555673: Thread ID: 8
1535423556678: Thread ID: 8
1535423557683: Thread ID: 8

// 当使用newCachedThreadPool(),线程不够会创建新的,输出如下
1535423614559: Thread ID: 8
1535423614559: Thread ID: 12
1535423614559: Thread ID: 9
1535423614559: Thread ID: 10
1535423614560: Thread ID: 14
1535423614559: Thread ID: 11
1535423614560: Thread ID: 13
1535423614560: Thread ID: 15
1535423614560: Thread ID: 16
1535423614560: Thread ID: 17

10 计划任务,newScheduledThreadPool(),它返回一个ScheduledExecutorService对象,可以根据时间需要对线程进行调度,主要方法如下

public ScheduledFuture<?> schedule(Runnable command , long delay , TimeUnit unit )   // 会在给定时间对任务进行一次调度

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command , long initialDelay , long period , TimeUnit unit )  // 会对任务进行周期性调度调度的频率是一定的。是以上一个任务开始执行时间为起点,之后的period时间调度下一次任务

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command , long initialDelay , long delay , TimeUnit unit ) // 会对任务进行周期性调度,在上一个任务结束后再经过delay时间进行任务调度

跟其他线程池不用,ScheduledExecutorService并不一定会立即执行任务,它其实是起计划任务的作用。它会在指定的时间,对任务进行调度

  • 如下代码,任务会每隔2秒执行,但因为scheduleAtFixedRate()是以上一个任务执行开始时间来算,如果任务执行时间长于调度时间,周期会无效。
public class ScheduledExecutorServiceDemo {

	public static void main(String[] args){
		ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
		ses.scheduleAtFixedRate(new Runnable(){

			@Override
			public void run() {
				// TODO Auto-generated method stub
				try{
					Thread.sleep(1000);
					System.out.println(System.currentTimeMillis()/1000);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
			
		}, 0, 2, TimeUnit.SECONDS);
	}
}
  • 如下代码,scheduleWithFixedDelay()是以上一个任务的结束开始算,所以任务间都间隔2秒
public class ScheduledExecutorServiceDemo {

	public static void main(String[] args){
		ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
		ses.scheduleWithFixedDelay(new Runnable(){

			@Override
			public void run() {
				// TODO Auto-generated method stub
				try{
					Thread.sleep(1000);
					System.out.println(System.currentTimeMillis()/1000);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
			
		}, 0, 2, TimeUnit.SECONDS);
	}
}

11 无论newFixedThreadPool,newSingleThreadExecutor,newCachedThreadPool()都只是ThreadPoolExecutor类的封装。ThreadPoolExecutor的构造函数如下

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

  • corePoolSize 指定了线程池中的线程数量
  • maximumPoolSize 指定了线程池中的最大线程数量
  • keepAliveTime 当线程池线程数量超过了corePoolSize时,多余的空闲线程的存活时间
  • unit 是keepAliveTime的单位
  • workQueue 任务队列,被提交但尚未被执行的任务,是一个BlockingQueue接口对象,仅用于存放Runnable对象。
  • threadFactory 线程工厂,用于创建线程
  • handler 拒绝策略,当任务太多来不及处理,如何拒绝任务

在ThreadPoolExecutor的构造函数中可使用如下几种BlockingQueue

  • 直接提交的队列,该功能由SynchronousQueue对象提供。SynchronousQueue是一个特殊的BlockingQueue。SynchronousQueue没有容量,每个插入操作都要等待一个相应的删除操作。如果使用SynchronousQueue,提交的任务不会被真实的保存,而总是将新任务提交给线程执行,没有空闲的线程则尝试创建新的进程。当进程数据达到最大值则执行拒绝策略。所以使用SynchronousQueue队列,通常要设置很大的maximumPoolSize值否则很容易执行拒绝策略。
  • 有界的任务队列,可以用ArrayBlockingQueue,它必须带有一个表示队列最大容量的参数。有新任务时,线程池的实际线程数小于corePoolSize,则优先创建新的线程;若大于则将新任务加入等待队列。队列若已满,则在总线程数不大于maximumPoolSize的前提下创建新的线程执行任务。若大于则执行拒绝策略。
  • 无界的任务队列,可以用LinkedBlockingQueue类实现。除非系统资源耗尽,否则无界的任务队列不会存在任务入列失败。有新任务时,线程池的实际线程数小于corePoolSize,则优先创建新的线程;若大于则将新任务加入等待队列。
  • 优先任务队列,可以用PriorityBlockingQueue类实现。它是一个无界队列,可以根据任务自身的优先级顺序先后执行。

JDK内置提供了四种拒绝策略,这些策略都实现了RejectedExecutionHandler接口

  • AbortPolicy策略:该策略直接抛出异常,阻止系统正常工作。
  • CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。
  • DiscardOledestPolicy策略:丢弃即将被执行的任务,尝试再次提交当前任务
  • DiscardPolicy策略:丢弃无法处理的任务

12 ThreadPoolExecutor也是一个可以扩展的线程池,提供了beforeExecute(),afterExecute()和terminated()三个接口对线程池进行控制。

13 优化线程池线程数量

14 在线程池中寻找堆栈

public class DivTask implements Runnable {
	int a , b;
	public DivTask(int a, int b){
		this.a = a;
		this.b = b;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		double re = a/b;
		System.out.println(re);
	}
	public static void main(String[] args) throws InterruptedException,ExecutionException{
		ThreadPoolExecutor pools = new ThreadPoolExecutor(0,Integer.MAX_VALUE,
				0L,TimeUnit.SECONDS,
				new SynchronousQueue<Runnable>());
		for(int i=0;i<5;i++){
            pools.submit(new DivTask(100,i));    // 第一种,出现异常不会打印异常错误
			//pools.execute(new DivTask(100,i));  // 第二种,出现异常会打印部分堆栈信息
            /*  第三种,出现异常会打印部分堆栈信息
			Future re = pools.submit(new DivTask(100,i));
			re.get();
            */
		}
	}
}

15 分而治之:Fork / Join框架。在JDK中,有一个ForkJoinPool线程池,对于fork()方法并不急于开启线程,而是提交给ForkJoinPool线程池进行处理。

ForkJoinPool有一个重要的接口

     public  <T>  ForkJoinTask<T>  submit(ForkJoinTask<T> task)

可以向ForkJoinPool线程池提交一个ForkJoinTask任务,该任务就是支持fork()分解以及join()等待的任务。ForkJoinTask有两个重要的子类RecursiveAction和RecursiveTask,分别表示没有返回值的任务和可以携带返回值的任务。

16 JDK的并发容器,大部分在java.util.concurrent包中

  • ConcurrentHashMap:高效的并发HashMap,可以理解为线程安全的HashMap
  • CopyOnWriteArrayList:在读多写少的场合,其性能要远比Vector好
  • ConcurrentLinkedQueue:高效的并发队列,使用链表实现。可以看作一个线程安全的LinkedList
  • BlockingQueue:这是一个接口,JDK内部通过链表,数组等方式实现这个接口。表示阻塞队列,非常适合用于数据共享的通道
  • ConcurrentSkipListMap:跳表的实现。这是一个Map,使用跳表数据结构进行快速查找。

java.util下的Vector是线程安全,另外Collections工具类可以帮助我们将任意集合包装成线程安全的集合。

17 如果需要一个线程安全的HashMap,一种可行的方法是使用Collections.synchronizedMap()方法包装我们的HashMap。如下

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

另一种更专业的并发HashMap是ConcurrentHashMap,它更适合多线程的场合。

18 ArrayList和Vector都是使用数组作为内部实现,而Vector是线程安全,ArrayList不是

第4章  锁的优化及注意事项

1 有助于提高“锁”性能的几点建议

  • 减少锁持有时间,有助于降低锁冲突的可能性
  • 减小锁粒度,即缩小锁定对象的范围
  • 读写分离锁来替换独占锁
  • 锁分离,如读写分离
  • 锁粗化,即将所有的锁操作整合为锁的一次请求,减少对锁的请求同步次数。

2 JDK内部的几种”锁“优化策略

  • 锁偏向,一个线程获得了锁,那么锁就进入偏向模式。当这个线程再次请求锁时无须再做任何同步操作。
  • 轻量级锁,只是简单地将对象头部作为指针,指向持有锁的线程堆栈的内部,来判断一个线程是否持有对象锁
  • 自旋锁,让当前线程做几个空循环(即自旋),如果可以得到锁则进入临界区,否则挂起
  • 锁清除,去掉不可能存在共享资源竞争的锁

3 ThreadLocal,可以包装非线程安全的对象

public class Test1 {

	static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();
	public static class ParseDate implements Runnable{

		int i = 0;
		public ParseDate(int i){
			this.i = i;
		}
		@Override
		public void run() {
			// TODO Auto-generated method stub
			try{
				if(tl.get() == null){
					tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
				}
				Date t = tl.get().parse("2018-08-30 02:38:"+i%60);
				System.out.println(i+": "+t);
			}catch(ParseException e){
				e.printStackTrace();
			}
		}
		
	}
	public static void main(String[] args){
		ExecutorService es = Executors.newFixedThreadPool(10);
		for(int i=0;i<1000;i++){
			es.execute(new ParseDate(i));
		}
	}
}

4 ThreadLocal模式下会比多线程共享一个对象要快很多很多,如下例子快了30多倍

public class Test2 {

	public static final int GEN_COUNT =  10000000;
	public static final int THREAD_COUNT = 4;
	static ExecutorService es = Executors.newFixedThreadPool(THREAD_COUNT);
	public static Random rnd = new Random(123);
	
	public static ThreadLocal<Random> tRnd = new ThreadLocal<Random>(){
		
		protected Random initialValue(){
			return new Random(123);
		}
	};
	public static class RndTask implements Callable<Long>{

		private int mode = 0;
		public RndTask(int mode){
			this.mode = mode;
		}
		public Random getRandom(){
			if(mode == 0)
				return rnd;
			else if(mode == 1)
				return tRnd.get();
			else
				return null;
		}
		@Override
		public Long call() throws Exception {
			// TODO Auto-generated method stub
			long b = System.currentTimeMillis();
			for(long i=0;i<GEN_COUNT;i++)
				getRandom().nextInt();
			long e = System.currentTimeMillis();
			System.out.println(Thread.currentThread().getName()+" spend "+(e-b)+" ms");
			return e-b;
		}
		
	}
	public static void main(String[] args) throws InterruptedException,ExecutionException{
		Future<Long>[] futs = new Future[THREAD_COUNT];
		for(int i =0 ;i<THREAD_COUNT;i++){
			futs[i] = es.submit(new RndTask(0));
		}
		long totalTime = 0;
		for(int i=0;i<THREAD_COUNT;i++){
			totalTime += futs[i].get();
		}
		System.out.println("多线程访问同一个Random实例:"+totalTime+" ms");
		for(int i=0;i<THREAD_COUNT;i++){
			futs[i] = es.submit(new RndTask(1));
		}
		totalTime = 0;
		for(int i=0;i<THREAD_COUNT;i++){
			totalTime += futs[i].get();
		}
		System.out.println("使用ThreadLocal包装Random实例:"+totalTime+" ms");
		es.shutdown();		
	}
}

输出结果

pool-1-thread-1 spend 4911 ms

pool-1-thread-4 spend 4959 ms

pool-1-thread-3 spend 4961 ms

pool-1-thread-2 spend 4962 ms

多线程访问同一个Random实例:19793 ms

pool-1-thread-2 spend 130 ms

pool-1-thread-4 spend 131 ms

pool-1-thread-3 spend 133 ms

pool-1-thread-1 spend 134 ms

使用ThreadLocal包装Random实例:528 ms

5 一种无锁的策略:比较交换的技术(CAS  Compare And Swap) 来鉴别线程冲突,一旦检测到冲突产生就重试当前操作直到没有冲突为止。

CAS算法的过程:共三个参数CAS(V , E , N)。V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V值设为N。如果V值和E值不同则说明已经有其他线程做了更新,而当前线程什么都不做。最后CAS返回当前V的真实值。

6 JDK并发包有一个atomic包,有个最常用的类AtomicInteger,它是可变且线程安全。还有AtomicLong,AtomicBoolean,AtomicReference。

public class AtomicIntegerDemo {

	static AtomicInteger i = new AtomicInteger();
	public static class AddThread implements Runnable{

		@Override
		public void run() {
			// TODO Auto-generated method stub
			for(int k=0;k<10000;k++)
				i.incrementAndGet();
		}
	}
	public static void main(String[] args) throws InterruptedException{
		Thread[] ts = new Thread[10];
		for(int k=0;k<10;k++){
			ts[k] = new Thread(new AddThread());
		}
		for(int k=0;k<10;k++)
			ts[k].start();
		for(int k=0;k<10;k++)
			ts[k].join();
		System.out.println(i);
	}
}

AtomicInteger.incrementAndGet()采用CAS操作给自己加1,同时返回当前值。

7 AtomicReference是指对象引用,它可以保证在修改对象引用时的线程安全性

8 原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

9 可以让普通变量也享受原子操作:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater

public class AtomicIntegerFieldUpdaterDemo {

	public static class Candidate{
		int id;
		volatile int score;
	}
	public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
	public static AtomicInteger allScore = new AtomicInteger(0);
	public static void main(String[] args) throws InterruptedException{
		final Candidate stu = new Candidate();
		Thread[] t = new Thread[10000];
		for(int i=0;i<10000;i++){
			t[i] = new Thread(){
				public void run(){
					if(Math.random() > 0.4){
						scoreUpdater.incrementAndGet(stu);
						allScore.incrementAndGet();
					}
				}
			};
			t[i].start();
		}
		for(int i=0;i<10000;i++)
			t[i].join();
		System.out.println("score="+stu.score);
		System.out.println("allScore = "+allScore);
	}
}

注意事项:

  • Updater只能修改可见氛围的变量,如score为private则无法修改
  • 为了确保变量被正确的读取,必须是volatile类型
  • 由于CAS操作会通过对象实例的偏移量直接进行赋值,所以它不支持static字段

第5章 并行模式与算法

1 单例模式是一种对象创建模式,用于产生一个对象的具体实例,确保系统中一个类只产生一个实例。

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

注意:

  • 构造函数被定义为private,无法随便创建这个实例。
  • instance对象必须是private并且static
  • getInstance()定义为静态函数

上面有一个明显不足,Singleton构造函数或者是Singleton实例在什么时候创建是不受控制。如下是改善后的代码

public class Singleton {

	public static int k = 0;
	private Singleton(){
		System.out.println("Singleton is created");
	}
	private static class SingletonHolder{
		private static Singleton instance = new Singleton();
	}
	
	public static Singleton getInstance(){
		return SingletonHolder.instance;
	}
	
	public static void main(String[] args){
		//Singleton t = Singleton.getInstance();
		System.out.println(Singleton.k);
	}
}

2 不变模式天生就是多线程友好的,它的核心思想是一个对象一旦被创建,则它的内部状态将永远不会发生改变。不变模式的主要使用场景需要满足以下2个条件:

  • 当对象创建后,其内部状态和数据不再发生任何变化
  • 对象需要被分享,被多线程频繁访问

不变模式的实现很简单,只需要注意以下4点:

  • 去除setter方法以及所有修改自身属性的方法
  • 将所有属性设置为私有,并用final标记确保其不可修改
  • 确保没有子类可以重载修改它的行为
  • 有一个可以创建完整对象的构造函数
public final class Product {    // 确保无子类

	private final String no;
	private final String name;
	private final double price;
	
	public Product(String no,String name,double price){
		this.no = no;
		this.name = name;
		this.price = price;
	}
	public String getNo(){
		return no;
	}
	public String getName(){
		return name;
	}
	public double getPrice(){
		return price;
	}
}

不变模式应用很广泛,最典型的就是java.lang.String类。此外所有的元数据类包装类都是不变模式实现的。如java.lang.String,java.lang.Boolean,java.lang.Byte,java.lang.Character,java.lang.Double,java.lang.Float,java.lang.Integer,java.lang.Long,java.lang.Short

3 生产者-消费者模式是一个经典的多线程设计模式。生产者线程负责提交用户请求,消费者线程则负责具体处理生产者提交的任务。生产者和消费者之间则通过共享内存缓冲区进行通信。生产者-消费者模式的核心组件是共享内存缓冲区

PCData对象表示一个生产任务,或者相关任务数据。生产者对象和消费者对象均引用同一个BlockingQueue实例。生产者负责创建PCData对象,并将它加入BlockingQueue中,消费者则从BlockingQueue队列中获取PCData。

public final class PCData {

	private final int intData;
	public PCData(int d){
		intData = d;
	}
	public PCData(String d){
		intData = Integer.valueOf(d);
	}
	public int getData(){
		return intData;
	}
	public String toString(){
		return "data:"+intData;
	}
}


//生产者
public class Producer implements Runnable {

	private volatile boolean isRunning = true;
	private BlockingQueue<PCData> queue;
	private static AtomicInteger count = new AtomicInteger();
	private static final int SLEEPTIME = 1000;
	
	public Producer(BlockingQueue<PCData> queue){
		this.queue = queue;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		PCData data = null;
		Random r = new Random();
		System.out.println("start producer id="+Thread.currentThread().getId());
		try{
			while(isRunning){
				Thread.sleep(r.nextInt(SLEEPTIME));
				data = new PCData(count.incrementAndGet());
				System.out.println(data+" is put into queue");
				if(!queue.offer(data,2,TimeUnit.SECONDS)){
					System.out.println("failed to put data: " +data);
				}
			}
		}catch(InterruptedException e){
			e.printStackTrace();
			Thread.currentThread().interrupt();
		}
	}
	public void stop(){
		isRunning = false;
	}

}


//消费者
public class Consumer implements Runnable {

	private BlockingQueue<PCData> queue;
	private static final int SLEEPTIME = 1000;
	
	public Consumer(BlockingQueue<PCData> queue){
		this.queue = queue;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("start Consumer id="+Thread.currentThread().getId());
		Random r = new Random();
		try{
			while(true){
				PCData data = queue.take();
				if(null != data){
					int re = data.getData() * data.getData();
					System.out.println(MessageFormat.format("{0}*{1}={2}", data.getData(),data.getData(),re));
					Thread.sleep(r.nextInt(SLEEPTIME));
				}
			}
		}catch(InterruptedException e){
			e.printStackTrace();
			Thread.currentThread().interrupt();
		}
	}

}


public class Main {

	public static void main(String[] args) throws InterruptedException {
		BlockingQueue<PCData> queue = new LinkedBlockingQueue<PCData>(10);
		Producer p1 = new Producer(queue);
		Producer p2 = new Producer(queue);
		Producer p3 = new Producer(queue);
		Consumer c1 = new Consumer(queue);
		Consumer c2 = new Consumer(queue);
		Consumer c3 = new Consumer(queue);
		ExecutorService es = Executors.newCachedThreadPool();
		es.execute(p1);
		es.execute(p2);
		es.execute(p3);
		es.execute(c1);
		es.execute(c2);
		es.execute(c3);
		Thread.sleep(10*1000);
		p1.stop();
		p2.stop();
		p3.stop();
		Thread.sleep(3000);
		es.shutdown();
	}
}

4 BlockingQueue是使用锁和阻塞等待来实现线程间的同步。对于高并发场合,它的性能并不是最优越。而ConcurrentLinkedQueue是一个高性能的队列,它使用了无锁的CAS操作

5 Disruptor是个无锁的缓存框架,是由LMAX公司开发的一款高效的无锁内存队列。它使用无锁的方式实现了一个环形队列(RingBuffer),非常适合实现生产者和消费者模式。

public class PCData2 {

	private long value;
	public void set(long value){
		this.value = value;
	}
	public long get(){
		return value;
	}
}


// 工厂
public class PCDataFactory implements EventFactory<PCData2> {

	public PCData2 newInstance(){
		return new PCData2();
	}
}


//生产者
public class Producer2 {

	private final RingBuffer<PCData2> ringBuffer;
	public Producer2(RingBuffer<PCData2> ringBuffer){
		this.ringBuffer = ringBuffer;
	}
	public void pushData(ByteBuffer bb){
		long sequence = ringBuffer.next();
		try{
			PCData2 event = ringBuffer.get(sequence);
			event.set(bb.getLong(0));
		}finally{
			ringBuffer.publish(sequence);
		}
	}
}


//消费者
public class Consumer2 implements WorkHandler<PCData2>{

	@Override
	public void onEvent(PCData2 arg0) throws Exception {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getId()+": Event: -- "
				+arg0.get()*arg0.get()+" --");
	}

}


//执行
public static void main(String[] args) throws Exception{
		Executor executor = Executors.newCachedThreadPool();
		PCDataFactory factory = new PCDataFactory();
		int bufferSize = 1024;
		Disruptor<PCData2> disruptor = new Disruptor<PCData2>(factory,bufferSize,executor,
				ProducerType.MULTI,new BlockingWaitStrategy());
		disruptor.handleEventsWithWorkerPool(
				new Consumer2(),
				new Consumer2(),
				new Consumer2(),
				new Consumer2());
		disruptor.start();
		RingBuffer<PCData2> ringBuffer = disruptor.getRingBuffer();
		Producer2 p = new Producer2(ringBuffer);
		ByteBuffer bb = ByteBuffer.allocate(8);
		for(long l = 0;true;l++){
			bb.putLong(0,l);
			p.pushData(bb);
			Thread.sleep(100);
			System.out.println("add data "+l);
		}
	}

6 Futur模式,也是一种多线程开发常见的模式,它的核心思想是异步调用。当一个函数执行很慢,可以先让调用者立即返回,让函数在后台慢慢处理请求。

7 并发流水线

public class Msg {
	public double i;
	public double j;
	public String orgStr = null;
}


public class Plus implements Runnable {
	public static BlockingQueue<Msg> bq = new LinkedBlockingQueue<Msg>();

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			try{
				Msg msg = bq.take();
				msg.j = msg.i+msg.j;
				Multiply.bq.add(msg);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
}


public class Multiply implements Runnable {
	public static BlockingQueue<Msg> bq = new LinkedBlockingQueue<Msg>();

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			try{
				Msg msg = bq.take();
				msg.i = msg.i*msg.j;
				Div.bq.add(msg);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
}


public class Div implements Runnable {
	public static BlockingQueue<Msg> bq = new LinkedBlockingQueue<Msg>();

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			try{
				Msg msg = bq.take();
				msg.i = msg.i/2;
				System.out.println(msg.orgStr+"="+msg.i);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
}

public class PStreamMain {

	public static void main(String[] args){
		new Thread(new Plus()).start();
		new Thread(new Multiply()).start();
		new Thread(new Div()).start();
		
		for(int i=1;i<=10;i++){
			for(int j=1;j<=10;j++){
				Msg msg = new Msg();
				msg.i = i;
				msg.j = j;
				msg.orgStr = "(("+i+"+"+j+")*"+i+")/2";
				Plus.bq.add(msg);
			}
		}
	}
	
}

8 Java NIO是一套新的IO机制。

猜你喜欢

转载自blog.csdn.net/haima95/article/details/82024093