《Java Concurrency in Practice》读书笔记

from p90:
Executor接口提供了一种将任务提交和任务执行解耦的手段,它基于生产者消费者模式:

Executor is based on the producer consumer pattern, where activities that submit tasks are the producers (producing units of work to be done) and the threads that execute tasks are the consumers (consuming those units of work).

The value of decoupling submission from execution is that it lets you easily specify, and subsequently change without great difficulty, the execution policy for a given class of tasks.

Separating the specification of execution policy from task submission makes it practical to select an execution policy at deployment time that is matched to the available hardware.

线程池
线程线实际是采用BlockingQueue<Runnable>队列来存放任务,其中的Runnable就是被提交的任务。
p92:

A thread pool is tightly bound to a work queue holding tasks waiting to be executed. Worker threads have a simple life: request the next task from the work queue, execute it, and go back to waiting for another task.

当程序使用了Executors.newFixedThreadPool

… uses an Executor with a bounded pool of worker threads. Submitting a task with execute adds the task to the work queue, and the worker threads repeatedly dequeue tasks from the work queue and execute them.

p103 interrupt:

Blocking library methods like Thread.sleep and Object.wait try to detect when a thread has been interrupted and return early. They respond to interruption by clearing the interrupted status and throwing InterruptedException

实验代码:

class PrimeProducer extends Thread {
    
    
    private final BlockingQueue<BigInteger> queue;
    PrimeProducer(BlockingQueue<BigInteger> queue) {
    
    
        this.queue = queue;
    }
    public void run() {
    
    
        try {
    
    
            BigInteger p = BigInteger.ONE;
            while (!Thread.currentThread().isInterrupted())
//            while (true)
                queue.put(p = p.nextProbablePrime());
        } catch (InterruptedException consumed) {
    
    
            /* Allow thread to exit */
            System.out.println(queue);
            System.out.println("interrupted");
        }
    }
    public void cancel() {
    
     interrupt(); }

    public static void main(String[] args) throws InterruptedException {
    
    
              BlockingQueue<BigInteger> queue = new LinkedBlockingDeque<>();
        //        BlockingQueue<BigInteger> queue = new ArrayBlockingQueue<>(18);
        PrimeProducer t = new PrimeProducer(queue);
        t.start();
        Thread.sleep(10);
        t.cancel();
        System.out.println("main"+t.queue);
    }
}

不明白的是,当我使用LinkedBlockingDeque,而且使用while(true)循环,即不去主动用isInterrupted()方法检测中断位,而依赖阻塞方法put自动检测中断位并抛出InterruptedException时,程序没有如我意料地抛出异常。这是为什么。同等情况下,使用LinkedBlockingQueue或者ArrayBlockingQueue都可以。

p122 线程池的maximumPoolSize等参数:

The core size is the target size; the implementation attempts to maintain the pool at this size even when there are no tasks to execute, and will not create more threads than this unless the work queue is full. The maximum pool size is the upper bound on how many pool threads can be active at once. A thread that has been idle for longer than the keep alive time becomes a candidate for reaping and can be terminated if the current pool size exceeds the core size.

线程池几个参数的意义,上文基本讲得很清楚。我的唯一疑问是,当核心线程数和任务队列都满了(假设最大线程数大于核心线程数),此时有任务提交,会有新的线程生成,但此线程是为了哪个任务?是已经在blockingqueue中排队的任务?还是此时新提交的这个任务?因为书中有下面这么一句,我倾向于是前者。因为此时只有先安排已经在队列中排队的任务,才可能做到这一点。于是我做了一个实验。

Using a FIFO queue like LinkedBlockingQueue or ArrayBlockingQueue causes tasks to be started in the order in which they arrived.

public class SdhThreadPoolExecutor {
    
    
    public static void main(String[] args){
    
    
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,4,
                360L, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(2));
        for (int i = 0; i < 6; i++) {
    
    
            try {
    
    
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            int finalI = i;
            pool.submit(
                 () ->{
    
    
                     System.out.println("task "+ finalI+" start @"+new Date().getTime());
                     try {
    
    
                         Thread.sleep(10000L);
                     } catch (InterruptedException e) {
    
    
                         e.printStackTrace();
                     }
                     System.out.println("task "+ finalI+" end @"+new Date().getTime());
                 }
            );
        }
    }
}

输出结果:
task 0 start @1610080727040
task 1 start @1610080728041
task 4 start @1610080731042
task 5 start @1610080732043
task 0 end @1610080737041
task 2 start @1610080737041
task 1 end @1610080738041
task 3 start @1610080738041
task 4 end @1610080741043
task 5 end @1610080742043
task 2 end @1610080747041
task 3 end @1610080748042
结果出乎我的意料,事实证明,当核心线程数和任务队列都满了(假设最大线程数大于核心线程数),此时有任务提交,新的线程生成后会直接给此新任务使用,而不是已经在队列中排队的任务。

p153 难道CPU密集的定义在这里?:

When the performance of an activity is limited by availability of a particular resource, we say it is bound by that resource: CPU bound, database bound, etc.

似乎翻译得不太对,因为本书后文出现了CPU intensive,它才应该是CPU密集

p159 内存栅栏:

The visibility guarantees provided by synchronized and volatile may entail using special instructions called memory barriers that can flush or invalidate caches, flush hardware write buffers, and stall execution pipelines. Memory barriers may also have indirect performance consequences because they inhibit other compiler optimizations; most operations cannot be reordered with memory barriers.

逃逸分析:

More sophisticated JVMs can use escape analysis to identify when a local object reference is never published to the heap and is therefore thread local.

锁消除(lock elision);锁粗化( lock coarsening)

锁分段(lock striping),用在jdk1.7的concurrenthashmap中

p165的一段对调优有用。比如:

I/O bound. You can determine whether an application is disk bound using iostat or perfmon, and whether it is bandwidth limitedbymonitoringtrafficlevelsonyournetwork.

p200 我总结了:因为下列三种可能,要在循环中使用Object.wait方法

  1. 伪唤醒:

wait is even allowed to return “spuriously” not in response to any thread calling notify

  1. 线程被唤醒了,但循环判断的条件谓词没有变化 :

maybe it hasn’t been true at all since you called wait. You don’t know why another thread called notify or notifyAll; maybe it was because another condition predicate associated with the same condition queue became true.

Because multiple threads could be waiting on the same condition queue for different condition predicates, using notify instead of notifyAll can be dangerous, primarily because single notification is prone to a problem akin to missed signals.

  1. 当线程被唤醒时,循环判断的条件谓词为真,但在线程拿到锁恢复运行之前,条件谓词又为假了:

A thread waking up from wait gets no special priority in reacquiring the lock; it contends for the lock just like any other thread attempting to enter a synchronized block.

It might have been true at the time the notifying thread called notifyAll, but could have become false again by the time you reacquire the lock. Other threads may have acquired the lock and changed the object’s state between when your thread was awakened and when wait reacquired the lock.

猜你喜欢

转载自blog.csdn.net/qq_23204557/article/details/112117173
今日推荐