技能提升:并发编程(五)

3.BlockingQueue(阻塞队列)

//通过阻塞队列实现生产者消费者模式
public class Tmall3 {
	private int count=10;//队列的最大大小
	private BlockingQueue<Integer> blockingQueue=new ArrayBlockingQueue<>(count);
	public void push() {//生产者,当队列达到最大值,阻塞,停止生产,直到大小小于最大值,在开始生产
		try {
			blockingQueue.put(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	public void take() {//消费者,当队列中没有值时,阻塞等待,有值就获取
		try {
			System.out.println(blockingQueue.take());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

源码解析:

put()方法

public void put(E e) throws InterruptedException {
	checkNotNull(e);//判空
	final ReentrantLock lock = this.lock;//获得锁
	lock.lockInterruptibly();//加锁
	try {
		while (count == items.length)//如果队列大小等于最大值
			notFull.await();//阻塞
		enqueue(e);//添加元素
	} finally {
		lock.unlock();//释放锁
	}
}

private void enqueue(E x) {
	final Object[] items = this.items;
	items[putIndex] = x;
	if (++putIndex == items.length)
		putIndex = 0;
	count++;
	notEmpty.signal();//唤醒
}

tack()方法

public E take() throws InterruptedException {
	final ReentrantLock lock = this.lock;
	lock.lockInterruptibly();//加锁
	try {
		while (count == 0)//队列元素为0时
			notEmpty.await();//等待
		return dequeue();//取值
	} finally {
		lock.unlock();//释放锁
	}
}

private E dequeue() {
	final Object[] items = this.items;
	@SuppressWarnings("unchecked")
	E x = (E) items[takeIndex];
	items[takeIndex] = null;
	if (++takeIndex == items.length)
		takeIndex = 0;
	count--;
	if (itrs != null)
		itrs.elementDequeued();
	notFull.signal();//唤醒
	return x;
}

阻塞队列常用方法:

SynchronousQueue:队列中只有一个数据,不消费就无法插入,生产一个消费一个。

LinkedBlockingQueue:使用链表的阻塞队列。

4.CopyOnWriteArraySet

HashSet线程不安全解决方法:

  • 使用Collections的同步方法
  • CopyOnWriteArraySet:Set<String> set = new CopyOnWriteArraySet<String>();

 CopyOnWriteArraySet底层:

 

二十一:线程池

1.线程池优势

1)降低资源消耗,通过重复利用已创建的线程降低线程的创建预销毁消耗。

2)提高响应速度,当任务到达时,任务可以不用等待线程创建就立即执行。

3)提高线程的可管理性,线程是稀缺资源,如果无限创建,不仅会消耗资源,还会降低系统稳定性,使用线程池可以进行统一分配,调优和监控,但是要做到合理使用线程池,还需要懂得其原理。

2.简单使用

public class Demo1 {
	
	public static void main(String[] args) {
		//参数5用于存储任务的队列
		ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 20, 10, TimeUnit.DAYS, new ArrayBlockingQueue<>(10),new ThreadPoolExecutor.CallerRunsPolicy());
		
		AtomicInteger atomicInteger = new AtomicInteger();
		for (int i = 0; i < 100; i++) {
			poolExecutor.execute(new Runnable() {
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName());
					atomicInteger.getAndIncrement();
				}
			});
		}
		poolExecutor.shutdown();
		while (Thread.activeCount()>1) {}
		System.out.println(atomicInteger.get());
		
	}
}

参数解析

  • corePoolSize:线程池核心线程数(平时保留的线程数)
  • maximumPoolSize:线程池最大线程数(当workQueue都放不下时,启动新线程,最大线程数)
  • keepAliveTime:超出corePoolSize数量的线程的保留时间。
  • unit:keepAliveTime单位
  • workQueue:阻塞队列,存放来不及执行的线程

    • ArrayBlockingQueue:构造函数一定要传大小
    • LinkedBlockingQueue:构造函数不传大小会默认为(Integer.MAX_VALUE ),当大量请求任务时,容易造成内存耗尽。
    • SynchronousQueue:同步队列,一个没有存储空间的阻塞队列 ,将任务同步交付给工作线程。
    • PriorityBlockingQueue : 优先队列
  • threadFactory:线程工厂
  • handler:饱和策略

    • AbortPolicy(默认):直接抛出异常
    • CallerRunsPolicy:用调用者的线程执行任务
    • DiscardOldestPolicy:抛弃队列中最久的任务
    • DiscardPolicy:不处理,丢弃掉

执行流程:

  • 判断当前运行的线程数量是否超过corePoolSize,如果不超过corePoolSize。就创建一个线程直接执行该任务。—— 线程池最开始是没有线程在运行的 
  • 如果正在运行的线程数量超过或者等于corePoolSize,那么就将该任务加入到workQueue队列中去。 
  • 如果workQueue队列满了,也就是offer方法返回false的话,就检查当前运行的线程数量是否小于maximumPoolSize,如果小于就创建一个线程直接执行该任务。 
  • 如果当前运行的线程数量是否大于等于maximumPoolSize,那么就执行RejectedExecutionHandler来拒绝这个任务的提交。

3.原理解析

ThreadPoolExecutor的状态变量

//线程的控制状态:用来表示线程池的运行状态(整形高3位)和运行的worker数量(整形低29位)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//29位偏移量
private static final int COUNT_BITS = Integer.SIZE - 3;
//最大容量(2^29-1)
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

//线程的运行状态,需要3位表示,所以偏移量为32-3=29
private static final int RUNNING    = -1 << COUNT_BITS;//接受新任务并且已处理已经进入阻塞队列的任务
private static final int SHUTDOWN   =  0 << COUNT_BITS;//不接受新任务但是处理已经进入阻塞队列的任务
private static final int STOP       =  1 << COUNT_BITS;//不接受新任务,不处理已经进入阻塞队列的任务,中断正在运行的任务
private static final int TIDYING    =  2 << COUNT_BITS;//所有任务都已终止,workerCount=0,线程转化为 TIDYING,准备执行terminated()方法。
private static final int TERMINATED =  3 << COUNT_BITS;//表示已执行完terminated()方法。

// Packing and unpacking ctl
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

当我们向线程池提交任务时,通常使用execute()方法,接下来就先从该方法开始分析。

public void execute(Runnable command) {
	if (command == null)
		throw new NullPointerException();
	
	int c = ctl.get();//获得线程控制状态
	
	//出现情况一:线程池的线程数量小于corePoolSize核心线程数量,开启核心线程执行任务。
	if (workerCountOf(c) < corePoolSize) {
		if (addWorker(command, true))//添加Worker,线程池会将每一个任务包装为一个Worker对象
			return;
		c = ctl.get();//不成功再次获取线程状态
	}
	
	//出现情况二:线程池的线程数量不小于corePoolSize核心线程数量,或者开启核心线程失败,尝试将任务以非阻塞的方式添加到任务队列。
	if (isRunning(c) && workQueue.offer(command)) {
		int recheck = ctl.get();
		if (! isRunning(recheck) && remove(command))//线程池不处于Running,将自定义任务从workerQueue中移除
			reject(command);//拒绝执行命令
		else if (workerCountOf(recheck) == 0)//当worker数量==0时
			addWorker(null, false);//添加worker
	}
	
	//出现情况三:任务队列已满导致添加任务失败,开启新的非核心线程执行任务。
	else if (!addWorker(command, false))//添加worker失败
		reject(command);//拒绝执行命令
}

addWorker()方法

1)原子性增加WorkerCount

2)将用户给定的任务封装为Worker,并将此任务添加到Workers集合中

3)启动worker对应的线程,执行器run()方法

4)回滚worker创建动作,将worker重workers中移除,并原子性减少workerCount

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    //使用CAS机制轮询线程池的状态,如果线程池处于SHUTDOWN及大于它的状态则拒绝执行任务
    for (;;) {
        int c = ctl.get();//获取线程池状态
        int rs = runStateOf(c);//获取运行时状态
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        //使用CAS机制尝试将当前线程数+1
        //如果是核心线程当前线程数必须小于corePoolSize 
        //如果是非核心线程则当前线程数必须小于maximumPoolSize
        //如果当前线程数小于线程池支持的最大线程数CAPACITY 也会返回失败
        for (;;) {
            int wc = workerCountOf(c);//获取workerCount
            if (wc >= CAPACITY ||//如果workerCount数量大于等于最大容量
                wc >= (core ? corePoolSize : maximumPoolSize))//workerCount>=核心线程数大小||workerCount>=最大线程数大小
                return false;
            if (compareAndIncrementWorkerCount(c))//比较并增加worker数量
                break retry;//跳出外层循环
            c = ctl.get();//获取线程池状态
            if (runStateOf(c) != rs)//此次的状态与上次状态不同
                continue retry;//跳过剩余部分继续循环
        }
    }

    //这里已经成功执行了CAS操作将线程池数量+1,下面创建线程
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        //Worker内部有一个Thread,并且执行Worker的run方法,因为Worker实现了Runnable
        final Thread t = w.thread;
        if (t != null) {
            //这里必须同步在状态为运行的情况下将Worker添加到workers中
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);  //把新建的woker线程放入集合保存,这里使用的是HashSet
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            //如果添加成功则运行线程
            if (workerAdded) {
                t.start();//启动worker中线程,启动后会调用worker.run()方法,进而调用runWorker()方法
                workerStarted = true;
            }
        }
    } finally {
        //如果woker启动失败,则进行一些善后工作,比如说修改当前woker数量等等
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

addWorker这个方法先尝试在线程池运行状态为RUNNING并且线程数量未达上限的情况下通过CAS操作将线程池数量+1,接着在ReentrantLock同步锁的同步保证下判断线程池为运行状态,然后把Worker添加到HashSet workers中。如果添加成功则执行Worker的内部线程。
Worker是ThreadPoolExecutor的内部类:

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;

    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
    /** Per-thread task counter */
    volatile long completedTasks;

    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    /** Delegates main run loop to outer runWorker. */
    public void run() {
        runWorker(this);
    }

    // Lock methods
    //
    // The value 0 represents the unlocked state.
    // The value 1 represents the locked state.

    protected boolean isHeldExclusively() {
        return getState() != 0;
    }

    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    public void lock()        { acquire(1); }
    public boolean tryLock()  { return tryAcquire(1); }
    public void unlock()      { release(1); }
    public boolean isLocked() { return isHeldExclusively(); }

    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

runWorker()方法

//ThreadPoolExecutor类中
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    // 因为Worker的构造函数中setState(-1)禁止了中断,这里的unclock用于恢复中断
    w.unlock();
    boolean completedAbruptly = true;
    try {
        //一般情况下,task都不会为空,因此会直接进入循环体中
        while (task != null || (task = getTask()) != null) {
            w.lock();
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                //该方法是个空的实现,如果有需要用户可以自己继承该类进行实现
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    //真正的任务执行逻辑
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    //该方法是个空的实现,如果有需要用户可以自己继承该类进行实现
                    afterExecute(task, thrown);
                }
            } finally {
                //这里设为null,也就是循环体再执行的时候会调用getTask方法
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        //当指定任务执行完成,阻塞队列中也取不到可执行任务时,会进入这里,做一些善后工作
        //比如在corePoolSize跟maximumPoolSize之间的woker会进行回收
        processWorkerExit(w, completedAbruptly);
    }
}

runWorker方法实际会执行给定任务(用户传入的runnable的run()方法),当给定任务执行完毕,会继续从阻塞队列中获取任务,之道阻塞队列为空(表示任务执行完毕),在执行给定任务时,回使用钩子函数,通过钩子函数完成用户自定义的一些逻辑,在runWorker中会调用getTask()方法及钩子函数processWorkerExit()

getTask()方法

private Runnable getTask() {
    boolean timedOut = false;

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        try {
            //根据超时配置有两种方法取出任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

getTask()的实现跟我们构造参数keepAliveTime存活时间有关。我们都知道keepAliveTime代表了线程池中的线程(即woker线程)的存活时间,如果到期则回收woker线程,这个逻辑的实现就在getTask中,getTask()方法就是去阻塞队列中取任务,用户设置的存活时间,就是从这个阻塞队列中取任务等待的最大时间,如果getTask返回null,意思就是woker等待了指定时间仍然没有取到任务,此时就会跳过循环体,进入woker线程的销毁逻辑。

processWorkerExit()方法

processWorkerExit方法是在worker退出调用的钩子函数,引起worker退出的原因:

1)阻塞队列为空,没有可以运行的任务

2)调用了shutdown()/shutdownNow()方法

private void processWorkerExit(Worker w, boolean completedAbruptly) {
	if (completedAbruptly)
		decrementWorkerCount();

	final ReentrantLock mainLock = this.mainLock;
	mainLock.lock();
	try {
		completedTaskCount += w.completedTasks;
		workers.remove(w);
	} finally {
		mainLock.unlock();
	}

	tryTerminate();

	int c = ctl.get();
	if (runStateLessThan(c, STOP)) {
		if (!completedAbruptly) {
			int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
			if (min == 0 && ! workQueue.isEmpty())
				min = 1;
			if (workerCountOf(c) >= min)
				return; // replacement not needed
		}
		addWorker(null, false);
	}
}

线程池的关闭

关闭线程池,可以通过shutdownshutdownNow这两个方法。它们的原理都是遍历线程池中所有的线程,然后依次中断线程。shutdownshutdownNow还是有不一样的地方:

  • shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表;
  • shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程。

4.工作中如何使用线程池?

在阿里巴巴开发者手册中指出,现线程的创建不允许通过new显示的创建,必须要通过线程池,而线程池的使用,不允许使用Executors的静态方法创建,必须通过 ThreadPoolExecutor创建。

5.如何合理配置线程池参数:通过Runtime.getRuntime().availableProcessors();获取服务器核心线程数

  • 如果是CPU密集型:核心线程数+1个线程的线程池-->8核+1
  • 如果是IO密集型:
  • IO密集型线程池并不是一直在执行任务,则尽可能配置多个线程,核心线程数*2
  • 核心线程数/(1-阻塞系数(0.8-0.9))-->8/(1-0.9)=80

二十二:happens-before 规则

使用happens-before的概念来指定两个操作之间的执行顺序,由于这两个操作可以在一个线程之内,也可以是在不同线程之间。因此,JMM可以通过happens-before关系向程序员提供跨线程的内存可见性保证(如果A线程的写操作a与B线程的读操作b之间存在happens-before关系,尽管a操作和b操作在不同的线程中执行,但JMM向程序员保证a操作将对b操作可见),两个操作之间有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行。happens-before仅仅要求前一个操作的(执行的结果)对后一个操作可见。

happens-before规则:

1)程序次序原则:一个线程内,按照程序代码顺序,书写在前面的操作先行发生与书写在后面的操作。

2)监视器锁规则:一个unlock操作先行发生与后面对同一个锁的lock操作者,这里必须指同一个锁,后面指的是时间上的先后顺序。

3)volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的后面同样指时间上的先后顺序。

4)start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。

5)join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。

6)传递性规则:如果A先行发生于B,B先行发生于C,则A先行发生于C

7)程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。

8)对象终结规则:一个对象的初始化完成,先行发生于它的finalize方法的开始

二十三:内存语义

1.锁的内存语义

锁的释放与获取所建立的happens-before关系

/**
 * 程序释放规则:1 hb 2  2 hb 3   4 hb 5  5hb 6
 * 监视器规则 3 hb 4
 * 传递性 1 hb 4 ......
 */
public class Demo {
	private int value;
	private synchronized void a() {//1 获取锁
		value++;//2
	}//3 释放锁
	private synchronized void b() {//4 获取锁
		int a=value;//5
		//处理其他操作    
	}//6 释放锁
}

锁的释放与获取的内存语义

当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中 ,当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须要从主内存中去读取共享变量。

总结:
线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。
线程B获取一个锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。
2.volatile内存语义

volatile建立的happens-before关系

/**
 * 程序释放规则:1 hb 2  3 hb 4   4 hb 5
 * volatile变量规则 2 hb 3
 * 传递性 1 hb 4 ......
 */
public class Demo2 {
	
	private int a;
	private volatile boolean flag;
	
	public void a() {
		a=10;//1
		flag=true;//2
	}
	public void b() {
		if (flag) {//3
			int b=a+1;//4
			System.out.println(b);//5
		}
	}
}

volatile读写的内存语义

当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存,当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

3.final域的内存语义

写final域的重排序规则

写final域的重排序规则禁止把final域的写重排序到构建函数之外。

1)JMM禁止编译器把final域的写重排序到构建函数之外。
2)编译器会在final域的写之后,构造函数return之前插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构建函数之外。

写final域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域不具有这个保障:写普通域的操作可能会被编译器重排序到构建函数之外,而写final域的操作被写final域的重排序规则“限定”在了构造函数之内。

读final域的重排序规则

1)在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作(该规则仅针对处理器)。

2)编译器会在读final域操作的前面插入一个LoadLoad屏障。

读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读包含这个final域的对象的引用。如果该引用不为null,则引用对象的final域一定被已经被初始化过了。

final域为引用类型

在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作不能重排序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值