Java编程思想 并发 新类库中的构件demo浅析

CountDownLatch

//: concurrency/CountDownLatchDemo.java
import java.util.concurrent.*;
import java.util.*;
import static net.mindview.util.Print.*;

// Performs some portion of a task:
class TaskPortion implements Runnable {
    private static int counter = 0;
    private final int id = counter++;
    private static Random rand = new Random(47);
    private final CountDownLatch latch;
    TaskPortion(CountDownLatch latch) {
        this.latch = latch;
    }
    public void run() {
        try {
            doWork();
            latch.countDown();  //此线程负责调用countDown
        } catch(InterruptedException ex) {
            // Acceptable way to exit
        }
    }
    public void doWork() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(rand.nextInt(2000));
        print(this + "completed");
    }
    public String toString() {
        return String.format("%1$-3d ", id);
    }
}

// Waits on the CountDownLatch:
class WaitingTask implements Runnable {
    private static int counter = 0;
    private final int id = counter++;
    private final CountDownLatch latch;
    WaitingTask(CountDownLatch latch) {
        this.latch = latch;
    }
    public void run() {
        try {
            print("before wait" + this);
            latch.await();  //此线程负责调用await
            print("Latch barrier passed for " + this);
        } catch(InterruptedException ex) {
            print(this + " interrupted");
        }
    }
    public String toString() {
        return String.format("WaitingTask %1$-3d ", id);
    }
}

public class CountDownLatchDemo {
    static final int SIZE = 100;
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();
        // All must share a single CountDownLatch object:
        CountDownLatch latch = new CountDownLatch(SIZE);
        for(int i = 0; i < 10; i++)
            exec.execute(new WaitingTask(latch));
        for(int i = 0; i < SIZE; i++)
            exec.execute(new TaskPortion(latch));
        print("Launched all tasks");
        exec.shutdown(); // Quit when all tasks complete
    }
} /* (Execute to see output) *///:~

从运行结果可以看出:

  • WaitingTask的10个线程,开始都阻塞在了await处。
  • 直到TaskPortion的100个线程都执行了countDown,WaitingTask的10个线程才从await处重新唤醒。
  • WaitingTask的10个线程,阻塞在await处的顺序,和它们被重新唤醒的顺序,是不一样的。

CyclicBarrier

//: concurrency/HorseRace.java
// Using CyclicBarriers.
import java.util.concurrent.*;
import java.util.*;
import static net.mindview.util.Print.*;

class Horse implements Runnable {
    private static int counter = 0;
    private final int id = counter++;
    private int strides = 0;  //记录自己这匹马从起点跑了多远
    private static Random rand = new Random(47);
    private static CyclicBarrier barrier;
    public Horse(CyclicBarrier b) { barrier = b; }
    public synchronized int getStrides() { return strides; }
    public void run() {
        try {
            while(!Thread.interrupted()) {
                synchronized(this) {
                    strides += rand.nextInt(3); // 每次await之前,向前随机跑0-2步
                }
                barrier.await();
            }
        } catch(InterruptedException e) {
            // A legitimate way to exit
        } catch(BrokenBarrierException e) {
            // This one we want to know about
            throw new RuntimeException(e);
        }
    }
    public String toString() { return "Horse " + id + " "; }
    public String tracks() {  //打印此匹马已跑过的路程,一个*代表一步
        StringBuilder s = new StringBuilder();
        for(int i = 0; i < getStrides(); i++)
            s.append("*");
        s.append(id);
        return s.toString();
    }
}

public class HorseRace {
    static final int FINISH_LINE = 75;
    private List<Horse> horses = new ArrayList<Horse>();
    private ExecutorService exec =
            Executors.newCachedThreadPool();
    private CyclicBarrier barrier;
    public HorseRace(int nHorses, final int pause) {
        barrier = new CyclicBarrier(nHorses, new Runnable() {  //匿名线程内部类
            public void run() {
                StringBuilder s = new StringBuilder();
                for(int i = 0; i < FINISH_LINE; i++)
                    s.append("="); // The fence on the racetrack
                print(s);
                for(Horse horse : horses)
                    print(horse.tracks());  //打印每匹马的路程
                for(Horse horse : horses)
                    if(horse.getStrides() >= FINISH_LINE) {  //如果有一匹马已经胜出(没考虑多匹马同时破线的情况)
                        print(horse + "won!");
                        exec.shutdownNow();
                        return;
                    }
                try {
                    TimeUnit.MILLISECONDS.sleep(pause);
                } catch(InterruptedException e) {
                    print("barrier-action sleep interrupted");
                }
            }
        });
        for(int i = 0; i < nHorses; i++) {
            Horse horse = new Horse(barrier);
            horses.add(horse);
            exec.execute(horse);
        }
    }
    public static void main(String[] args) {
        int nHorses = 7;
        int pause = 200;
        if(args.length > 0) { // Optional argument
            int n = new Integer(args[0]);
            nHorses = n > 0 ? n : nHorses;
        }
        if(args.length > 1) { // Optional argument
            int p = new Integer(args[1]);
            pause = p > -1 ? p : pause;
        }
        new HorseRace(nHorses, pause);
    }
} /* (Execute to see output) *///:~
  • 这个demo还是比较有意思的,就好比你看赛马比赛,但你只能通过一个幻灯片的方式来看,即每隔200ms来看这一帧里每匹马的当前路程。
  • 每个Horse线程都会阻塞在await处,但每阻塞一次 CyclicBarrier对象的计数器都会减少1,当7个Horse线程都执行了await,这个计数器便减小到了0。然后会执行CyclicBarrier构造器中的匿名线程,当匿名线程执行完毕后,7个Horse线程才会重新从await处唤醒。同样,阻塞的顺序和唤醒的顺序不一样。执行下面代码以观察效果:
//修改Horse的run函数:
    public void run() {
        try {
            synchronized(this) {
                strides += rand.nextInt(3); // 每次await之前,向前随机跑0-2步
            }
            print(id +" before await");
            barrier.await();
            print(id +" after await");

        } catch(InterruptedException e) {
            // A legitimate way to exit
        } catch(BrokenBarrierException e) {
            // This one we want to know about
            throw new RuntimeException(e);
        }
    }
/*output:
0 before await
2 before await
1 before await
3 before await
5 before await
4 before await
6 before await
===========================================================================
**0
*1
**2
**3
**4
*5
*6
6 after await
2 after await
0 after await
4 after await
5 after await
3 after await
1 after await
*/
  • exec.shutdownNow();必须执行,不然7个Horse线程会不停运行下去(因为while循环)。

DelayQueue

其实Java编程思想给的这个例子不好,在此之前我先讲一个简单例子帮忙理解。

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

class Item implements Delayed{
    private long time;  //存,啥时候延时到期
    String name;

    public Item(String name, long time, TimeUnit unit) {
        this.name = name;
        this.time = System.currentTimeMillis() + (time > 0? unit.toMillis(time): 0);
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return time - System.currentTimeMillis();
    }

    @Override
    public int compareTo(Delayed o) {
        Item item = (Item) o;
        long diff = this.time - item.time;
        if (diff <= 0) {// 改成>=会造成问题
            return -1;
        }else {
            return 1;
        }
    }
}

public class DelayedQueneTest {
    public static void main(String[] args) throws InterruptedException {
        Item item1 = new Item("item1", 3, TimeUnit.SECONDS);
        Item item2 = new Item("item2", 6, TimeUnit.SECONDS);
        Item item3 = new Item("item3", 9, TimeUnit.SECONDS);
        DelayQueue<Item> queue = new DelayQueue<>();
        queue.put(item1);
        queue.put(item2);
        queue.put(item3);
        System.out.println("begin time:" + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        for (int i = 0; i < 3; i++) {
            Item take = queue.take();
            System.out.format("name:{%s}, time:{%s}\n",take.name, LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
        }
    }
}
/*output:
begin time:2020-05-06T22:24:12.638
name:{item1}, time:{2020-05-06T22:24:15.582}
name:{item2}, time:{2020-05-06T22:24:18.582}
name:{item3}, time:{2020-05-06T22:24:21.582}
*/
  • getDelay函数,以判断当前Delayed对象是否达到延时时间。如果该函数返回小于0的值,代表延时时间还没有到达。观察getDelay的实现,它与currentTime进行比较,很明显,当时间流逝后,该函数的返回值会由负值 -> 0 -> 正值。该函数作为取出队首元素的标准,即返回值>=0时,才可以取出。
  • compareTo函数,作为比较的标准。现有两个Delayed对象,如果A对象.compareTo(B对象) < 0,那么在DelayQueue队列中,A对象会排到B对象的前面。(A对象更靠近队列头部)
  • 观察打印效果,每隔3秒出来一行字。说明queue.take();确实会阻塞,直到队首元素的延时时间到达。
  • diff <= 0如果改成diff >= 0,那么打印结果如下。本来,打印效果是每隔3秒出来一行字,现在改完最后三行字是一起出来的,而且顺序也是不对的(变成321的顺序了)。
    • 变成321的顺序是因为:改成diff >= 0后,延时时间点更靠后的Delayed对象反而更加靠近队首,自然第一个取出来的是item3。
    • 最后三行字是一起出来是因为:队列从头部到尾部的元素分别为item3、item2、item1,虽然item1最先到达延时时间,但由于队首是item3,item3还没有到达延时时间,自然不能取出队首元素。随着时间流逝,当item3到达时,item2和item1早就已经到达了,所以它们三个可以连续地取出,因为此时它们三个元素都满足取出条件。
begin time:2020-05-06T22:34:54.443
name:{item3}, time:{2020-05-06T22:35:03.381}
name:{item2}, time:{2020-05-06T22:35:03.388}
name:{item1}, time:{2020-05-06T22:35:03.388}

//: concurrency/DelayQueueDemo.java
import java.util.concurrent.*;
import java.util.*;
import static java.util.concurrent.TimeUnit.*;
import static net.mindview.util.Print.*;

class DelayedTask implements Delayed {
    private static int counter = 0;
    private final int id = counter++;
    private final int delta;  //要延时的时间
    private final long trigger;  //根据当前时间+delta
    protected static List<DelayedTask> sequence =
            new ArrayList<DelayedTask>();
    public DelayedTask(int delayInMilliseconds) {
        delta = delayInMilliseconds;
        trigger = System.nanoTime() +
                NANOSECONDS.convert(delta, MILLISECONDS);
        sequence.add(this);
    }
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(
                trigger - System.nanoTime(), NANOSECONDS);
    }
    @Override
    public int compareTo(Delayed arg) {
        DelayedTask that = (DelayedTask)arg;
        if(trigger < that.trigger) return -1;  //返回负数,则排在队列前面
        if(trigger > that.trigger) return 1;
        return 0;
    }
    public void taskToDo() { printnb(this + " "); }
    public String toString() {
        return String.format("[%1$-4d]", delta) +
                " Task " + id;
    }
    public String summary() {
        return "(" + id + ":" + delta + ")";
    }
    public static class EndSentinel extends DelayedTask {
        private ExecutorService exec;
        public EndSentinel(int delay, ExecutorService e) {
            super(delay);
            exec = e;
        }
        public void taskToDo() {
            for(DelayedTask pt : sequence) {
                printnb(pt.summary() + " ");
            }
            print();
            print(this + " Calling shutdownNow()");
            exec.shutdownNow();
        }
    }
}

class DelayedTaskConsumer implements Runnable {
    private DelayQueue<DelayedTask> q;
    public DelayedTaskConsumer(DelayQueue<DelayedTask> q) {
        this.q = q;
    }
    public void run() {
        try {
            while(!Thread.interrupted())
                q.take().taskToDo(); // Run task with the current thread
        } catch(InterruptedException e) {
            // Acceptable way to exit
            System.out.println("InterruptedException");
        }
        print("Finished DelayedTaskConsumer");
    }
}

public class DelayQueueDemo {
    public static void main(String[] args) {
        Random rand = new Random(47);
        ExecutorService exec = Executors.newCachedThreadPool();
        DelayQueue<DelayedTask> queue =
                new DelayQueue<DelayedTask>();
        // Fill with tasks that have random delays:
        for(int i = 0; i < 20; i++)
            queue.put(new DelayedTask(rand.nextInt(5000)));
        // Set the stopping point
        queue.add(new DelayedTask.EndSentinel(5000, exec));
        exec.execute(new DelayedTaskConsumer(queue));
        System.out.println("start all task");
    }
} /* Output:
start all task
[128 ] Task 11 [200 ] Task 7 [429 ] Task 5 [520 ] Task 18 [555 ] Task 1 [961 ] Task 4 [998 ] Task 16 [1207] Task 9 [1693] Task 2 [1809] Task 14 [1861] Task 3 [2278] Task 15 [3288] Task 10 [3551] Task 12 [4258] Task 0 [4258] Task 19 [4522] Task 8 [4589] Task 13 [4861] Task 17 [4868] Task 6 (0:4258) (1:555) (2:1693) (3:1861) (4:961) (5:429) (6:4868) (7:200) (8:4522) (9:1207) (10:3288) (11:128) (12:3551) (13:4589) (14:1809) (15:2278) (16:998) (17:4861) (18:520) (19:4258) (20:5000) 
[5000] Task 20 Calling shutdownNow()
Finished DelayedTaskConsumer
*///:~
  • 此例子我进行了大改造,主要是让DelayedTask不再实现Runnable接口,把run函数的逻辑写在自定义函数taskToDo里面,之前那么搞,反而让这个例子不好理解。
  • unit.convert(trigger - System.nanoTime(), NANOSECONDS)这句体现了一种策略模式,根据unit这个TimeUnit对象,和传入参数的TimeUnit对象,这二者最终确定了一个时间单位的转换关系。
  • [128 ] Task 11 [200 ] Task 7 [429 ] Task 5 ...,体现了,触发时间早的,排在了队列的头部。他们的依次有间隔的打印,体现了队首元素在时间未到时,会一直阻塞。

PriorityBlockingQueue

//: concurrency/PriorityBlockingQueueDemo.java
import java.util.concurrent.*;
import java.util.*;
import static net.mindview.util.Print.*;

class PrioritizedTask implements Comparable<PrioritizedTask>  {
    private Random rand = new Random(47);
    private static int counter = 0;
    private final int id = counter++;
    private final int priority;
    protected static List<PrioritizedTask> sequence =
            new ArrayList<PrioritizedTask>();
    public PrioritizedTask(int priority) {
        this.priority = priority;
        sequence.add(this);
    }
    public int compareTo(PrioritizedTask arg) {
        return priority < arg.priority ? 1 ://返回正数,放在队列后面(priority越小,位置更靠近队尾)
                (priority > arg.priority ? -1 : 0);
    }
    public void taskToDo() {
        try {
            TimeUnit.MILLISECONDS.sleep(rand.nextInt(250));
        } catch(InterruptedException e) {
            // Acceptable way to exit
        }
        print(this);
    }
    public String toString() {
        return String.format("[%1$-3d]", priority) +
                " Task " + id;
    }
    public String summary() {
        return "(" + id + ":" + priority + ")";
    }
    public static class EndSentinel extends PrioritizedTask {
        private ExecutorService exec;
        public EndSentinel(ExecutorService e) {
            super(-1); // Lowest priority in this program
            exec = e;
        }
        public void taskToDo() {
            int count = 0;
            for(PrioritizedTask pt : sequence) {
                printnb(pt.summary());
                if(++count % 5 == 0)
                    print();
            }
            print();
            print(this + " Calling shutdownNow()");
            exec.shutdownNow();
        }
    }
}

class PrioritizedTaskProducer implements Runnable {
    private Random rand = new Random(47);
    private Queue<PrioritizedTask> queue;
    private ExecutorService exec;
    public PrioritizedTaskProducer(
            Queue<PrioritizedTask> q, ExecutorService e) {
        queue = q;
        exec = e; // Used for EndSentinel
    }
    public void run() {
        print("生产者线程开始");
        // Unbounded queue; never blocks.
        // Fill it up fast with random priorities:
        for(int i = 0; i < 20; i++) {
            queue.add(new PrioritizedTask(rand.nextInt(10)));
            Thread.yield();
        }
        System.out.println("接下来加入权限为10");
        // Trickle in highest-priority jobs:
        try {
            for(int i = 0; i < 10; i++) {
                TimeUnit.MILLISECONDS.sleep(250);
                queue.add(new PrioritizedTask(10));
            }
            System.out.println("从低到高添加权限");
            // Add jobs, lowest priority first:
            for(int i = 0; i < 10; i++)
                queue.add(new PrioritizedTask(i));
            // A sentinel to stop all the tasks:
            queue.add(new PrioritizedTask.EndSentinel(exec));
        } catch(InterruptedException e) {
            // Acceptable way to exit
        }
        print("Finished PrioritizedTaskProducer");
    }
}

class PrioritizedTaskConsumer implements Runnable {
    private PriorityBlockingQueue<PrioritizedTask> q;
    public PrioritizedTaskConsumer(
            PriorityBlockingQueue<PrioritizedTask> q) {
        this.q = q;
    }
    public void run() {
        print("消费者线程开始");
        try {
            while(!Thread.interrupted()) {
                print("消费者线程 before take. q is "+q);
                q.take().taskToDo();
            }
        } catch(InterruptedException e) {
            // Acceptable way to exit
        }
        print("Finished PrioritizedTaskConsumer");
    }
}

public class PriorityBlockingQueueDemo {
    public static void main(String[] args) throws Exception {
        Random rand = new Random(47);
        ExecutorService exec = Executors.newCachedThreadPool();
        PriorityBlockingQueue<PrioritizedTask> queue =
                new PriorityBlockingQueue<PrioritizedTask>();
        exec.execute(new PrioritizedTaskProducer(queue, exec));
        exec.execute(new PrioritizedTaskConsumer(queue));
    }
} /* (Execute to see output) *///:~
  • PriorityBlockingQueue,看名字,就知道:它是一个阻塞队列,当队列无元素且想取出元素时,会阻塞;它还是一个优先队列,内部使用二叉堆。
  • 同样,对此demo进行了改造,且加了打印(主要是不让PrioritizedTask实现Runnable接口了,因为这样没有意义)。
  • 看源码可知,加入元素和取出元素都是对同一把锁进行加锁,自然,两个线程同时进行操作,有一个线程会因获得不到锁而阻塞。
生产者线程开始
消费者线程开始
消费者线程 before take. q is []
接下来加入权限为10
[9  ] Task 5
消费者线程 before take. q is [[9  ] Task 13, [8  ] Task 10, [9  ] Task 14, [8  ] Task 15, [7  ] Task 9, [8  ] Task 11, [8  ] Task 6, [8  ] Task 16, [8  ] Task 19, [1  ] Task 4, [5  ] Task 1, [3  ] Task 2, [1  ] Task 12, [2  ] Task 8, [8  ] Task 0, [0  ] Task 7, [1  ] Task 17, [0  ] Task 18, [1  ] Task 3]
[9  ] Task 13
...
  • 从上面打印结果来看,消费者线程执行到消费者线程 before take. q is [],就因为没有元素而阻塞了。之后生产者连续加入了20个随机权限的元素,在这期间,虽然队列有元素了,但消费者因一直没有获得到锁而阻塞。直到生产者第一次执行到了TimeUnit.MILLISECONDS.sleep(250);,消费者才抓住了机会,之后消费者一直取出元素直到取完(这就是为什么,打印效果是消费者线程 before take. q is []之后是[9 ] Task 5)。
...
消费者线程 before take. q is [[9  ] Task 13, [8  ] Task 10, [9  ] Task 14, [8  ] Task 15, [7  ] Task 9, [8  ] Task 11, [8  ] Task 6, [8  ] Task 16, [8  ] Task 19, [1  ] Task 4, [5  ] Task 1, [3  ] Task 2, [1  ] Task 12, [2  ] Task 8, [8  ] Task 0, [0  ] Task 7, [1  ] Task 17, [0  ] Task 18, [1  ] Task 3]
[9  ] Task 13
消费者线程 before take. q is [[9  ] Task 14, [8  ] Task 10, [8  ] Task 11, [8  ] Task 15, [7  ] Task 9, [3  ] Task 2, [8  ] Task 6, [8  ] Task 16, [8  ] Task 19, [1  ] Task 4, [5  ] Task 1, [1  ] Task 3, [1  ] Task 12, [2  ] Task 8, [8  ] Task 0, [0  ] Task 7, [1  ] Task 17, [0  ] Task 18]
[9  ] Task 14
...
  • 从上面可见,取出Task 13后,元素的顺序就全变了,但至少还保持着队首元素为最大权限元素。这是因为二叉堆在取出元素后,有元素下滤的过程,所以是不保证顺序的。
...
消费者线程 before take. q is []
[10 ] Task 20
消费者线程 before take. q is []
[10 ] Task 21
消费者线程 before take. q is []
[10 ] Task 22
消费者线程 before take. q is []
...
  • 有一段时间是生产者生产一个,消费者再消费一个的交替过程。因为生产者每次生产后,都会sleep一下。

ScheduledThreadPoolExecutor

import java.util.concurrent.*;
import java.util.*;

public class GreenhouseScheduler {
    private volatile boolean light = false;
    private volatile boolean water = false;
    private String thermostat = "Day";
    public synchronized String getThermostat() {
        return thermostat;
    }
    public synchronized void setThermostat(String value) {
        thermostat = value;
    }
    ScheduledThreadPoolExecutor scheduler =
            new ScheduledThreadPoolExecutor(10);
    public void schedule(Runnable event, long delay) {
        scheduler.schedule(event,delay,TimeUnit.MILLISECONDS);
    }
    public void
    repeat(Runnable event, long initialDelay, long period) {
        scheduler.scheduleAtFixedRate(
                event, initialDelay, period, TimeUnit.MILLISECONDS);
    }
    class LightOn implements Runnable {
        public void run() {
            // Put hardware control code here to
            // physically turn on the light.
            System.out.println("Turning on lights");
            light = true;
        }
    }
    class LightOff implements Runnable {
        public void run() {
            // Put hardware control code here to
            // physically turn off the light.
            System.out.println("Turning off lights");
            light = false;
        }
    }
    class WaterOn implements Runnable {
        public void run() {
            // Put hardware control code here.
            System.out.println("Turning greenhouse water on");
            water = true;
        }
    }
    class WaterOff implements Runnable {
        public void run() {
            // Put hardware control code here.
            System.out.println("Turning greenhouse water off");
            water = false;
        }
    }
    class ThermostatNight implements Runnable {
        public void run() {
            // Put hardware control code here.
            System.out.println("Thermostat to night setting");
            setThermostat("Night");
        }
    }
    class ThermostatDay implements Runnable {
        public void run() {
            // Put hardware control code here.
            System.out.println("Thermostat to day setting");
            setThermostat("Day");
        }
    }
    class Bell implements Runnable {
        public void run() { System.out.println("Bing!"); }
    }
    class Terminate implements Runnable {
        public void run() {
            System.out.println("Terminating");
            scheduler.shutdownNow();
            // Must start a separate task to do this job,
            // since the scheduler has been shut down:
            new Thread() {
                public void run() {
                    for(DataPoint d : data)
                        System.out.println(d);
                }
            }.start();
        }
    }
    // New feature: data collection
    static class DataPoint {
        final Calendar time;
        final float temperature;
        final float humidity;
        public DataPoint(Calendar d, float temp, float hum) {
            time = d;
            temperature = temp;
            humidity = hum;
        }
        public String toString() {
            return time.getTime() +
                    String.format(
                            " temperature: %1$.1f humidity: %2$.2f",
                            temperature, humidity);
        }
    }
    private Calendar lastTime = Calendar.getInstance();
    { // Adjust date to the half hour
        lastTime.set(Calendar.MINUTE, 30);
        lastTime.set(Calendar.SECOND, 00);
    }
    private float lastTemp = 65.0f;
    private int tempDirection = +1;
    private float lastHumidity = 50.0f;
    private int humidityDirection = +1;
    private Random rand = new Random(47);
    List<DataPoint> data = Collections.synchronizedList(
            new ArrayList<DataPoint>());
    class CollectData implements Runnable {
        public void run() {
            System.out.println("Collecting data");
            synchronized(GreenhouseScheduler.this) {
                // Pretend the interval is longer than it is:
                lastTime.set(Calendar.MINUTE,
                        lastTime.get(Calendar.MINUTE) + 30);
                // One in 5 chances of reversing the direction:
                if(rand.nextInt(5) == 4)
                    tempDirection = -tempDirection;
                // Store previous value:
                lastTemp = lastTemp +
                        tempDirection * (1.0f + rand.nextFloat());
                if(rand.nextInt(5) == 4)
                    humidityDirection = -humidityDirection;
                lastHumidity = lastHumidity +
                        humidityDirection * rand.nextFloat();
                // Calendar must be cloned, otherwise all
                // DataPoints hold references to the same lastTime.
                // For a basic object like Calendar, clone() is OK.
                data.add(new DataPoint((Calendar)lastTime.clone(),
                        lastTemp, lastHumidity));
            }
        }
    }
    public static void main(String[] args) {
        GreenhouseScheduler gh = new GreenhouseScheduler();
        gh.schedule(gh.new Terminate(), 5000);
        // Former "Restart" class not necessary:
        gh.repeat(gh.new Bell(), 0, 1000);
        gh.repeat(gh.new ThermostatNight(), 0, 2000);
        gh.repeat(gh.new LightOn(), 0, 200);
        gh.repeat(gh.new LightOff(), 0, 400);
        gh.repeat(gh.new WaterOn(), 0, 600);
        gh.repeat(gh.new WaterOff(), 0, 800);
        gh.repeat(gh.new ThermostatDay(), 0, 1400);
        gh.repeat(gh.new CollectData(), 500, 500);
    }
} /* (Execute to see output) *///:~
  • 好像没什么好讲的,就是schedule方法延时运行一次,scheduleAtFixedRate方法延时运行一次,之后每隔固定时间再运行。
  • 注意CollectData是一个成员内部类,且run方法里锁的是GreenhouseScheduler.this,之所以不直接锁自己,是因为run方法修改的变量都是GreenhouseScheduler外部类的成员变量,这样写才显得合理。当创建了两个CollectData对象作为线程运行,或者GreenhouseScheduler有一个synchronized方法也修改了同样的成员变量时,synchronized(GreenhouseScheduler.this)这样写才是正确的写法。
  • List<DataPoint> data = Collections.synchronizedList(new ArrayList<DataPoint>());这里通过synchronizedList静态方法获得了包装过的同步List,虽然只有一个CollectData线程会调用到它的方法。

Semaphore

//: concurrency/Pool.java
// Using a Semaphore inside a Pool, to restrict
// the number of tasks that can use a resource.
import java.util.concurrent.*;
import java.util.*;

public class Pool<T> {
    private int size;
    private List<T> items = new ArrayList<T>();
    private volatile boolean[] checkedOut;
    private Semaphore available;
    public Pool(Class<T> classObject, int size) {
        this.size = size;
        checkedOut = new boolean[size];
        available = new Semaphore(size, true);
        // Load pool with objects that can be checked out:
        for(int i = 0; i < size; ++i)
            try {
                // Assumes a default constructor:
                items.add(classObject.newInstance());
            } catch(Exception e) {
                throw new RuntimeException(e);
            }
    }
    //对于管理者,把对象给出去了,所以是out
    public T checkOut() throws InterruptedException {
        available.acquire();
        return getItem();
    }
    public void checkIn(T x) {
        if(releaseItem(x))
            available.release();
    }
    private synchronized T getItem() {
        for(int i = 0; i < size; ++i)  //从前往后找到一个没被使用的item
            if(!checkedOut[i]) {
                checkedOut[i] = true;
                return items.get(i);
            }
        return null; // Semaphore prevents reaching here
    }
    private synchronized boolean releaseItem(T item) {
        int index = items.indexOf(item);
        if(index == -1) return false; // 保证item是在list里的
        if(checkedOut[index]) {  // 这个item现在正在被使用
            checkedOut[index] = false;
            return true;
        }
        return false; // Wasn't checked out
    }
} ///:~

//: concurrency/Fat.java
// Objects that are expensive to create.
public class Fat {
    private volatile double d; // Prevent optimization
    private static int counter = 0;
    private final int id = counter++;
    public Fat() {
        // Expensive, interruptible operation:
        for(int i = 1; i < 10000; i++) {
            d += (Math.PI + Math.E) / (double)i;
        }
    }
    public void operation() { System.out.println(this); }
    public String toString() { return "Fat id: " + id; }
} ///:~

//: concurrency/SemaphoreDemo.java
// Testing the Pool class
import java.util.concurrent.*;
import java.util.*;
import static net.mindview.util.Print.*;

// A task to check a resource out of a pool:
class CheckoutTask<T> implements Runnable {
    private static int counter = 0;
    private final int id = counter++;
    private Pool<T> pool;
    public CheckoutTask(Pool<T> pool) {
        this.pool = pool;
    }
    public void run() {
        try {
            T item = pool.checkOut();
            print(this + "after checked out " + item);
            TimeUnit.SECONDS.sleep(1);
            print(this +"before checking in " + item);
            pool.checkIn(item);
        } catch(InterruptedException e) {
            // Acceptable way to terminate
        }
    }
    public String toString() {
        return "CheckoutTask " + id + " ";
    }
}

public class SemaphoreDemo {
    final static int SIZE = 25;
    public static void main(String[] args) throws Exception {
        final Pool<Fat> pool =
                new Pool<Fat>(Fat.class, SIZE);
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i = 0; i < SIZE; i++)
            exec.execute(new CheckoutTask<Fat>(pool));
        print("All CheckoutTasks created");
        List<Fat> list = new ArrayList<Fat>();
        for(int i = 0; i < SIZE; i++) {
            Fat f = pool.checkOut();
            printnb(i + ":after main() thread checked out ");
            f.operation();
            list.add(f);
        }
        print("主线程已经将信号量占满了");
        Future<?> blocked = exec.submit(new Runnable() {
            public void run() {
                try {
                    // Semaphore prevents additional checkout,
                    // so call is blocked:
                    print("这里将一直阻塞");
                    pool.checkOut();
                } catch(InterruptedException e) {
                    print("checkOut() Interrupted");
                }
            }
        });
        TimeUnit.SECONDS.sleep(2);
        blocked.cancel(true); // Break out of blocked call
        print("Checking in objects in " + list);
        for(Fat f : list)
            pool.checkIn(f);
        for(Fat f : list)
            pool.checkIn(f); // Second checkIn ignored
        exec.shutdown();
    }
} /* (Execute to see output) *///:~
  • 首先是运行25个CheckoutTask线程,它们会先执行acquire,再release。即取走的信号量最终会归还。
  • 然后是主线程执行25次acquire,此时信号量已经被全部取走了。
  • 最后执行那个匿名线程将一直阻塞,因为没有信号量可以取了。
        TimeUnit.SECONDS.sleep(2);
        pool.checkIn(list.get(0));  //加一句 以release一个信号量
        blocked.cancel(true); // Break out of blocked call
        print("Checking in objects in " + list);
        for(Fat f : list)
            pool.checkIn(f);
        for(Fat f : list)
            pool.checkIn(f); // Second checkIn ignored
        exec.shutdown();
  • 如果按如上代码加一句,那么最后那个匿名线程就不会一直阻塞,因为有一个信号量被释放了。

如果线程生前获得了一个信号量,随着线程的死亡,这个信号量会回收吗?为了验证这个问题,我写了如下demo:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class test5 {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        Semaphore available = new Semaphore(1);
        new Thread(new Runnable() {
            public void run() {
                try {
                    available.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            available.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行后,程序没有结束,dump threads后:

"main" #1 prio=5 os_prio=0 tid=0x0000000003842800 nid=0x49fc waiting on condition [0x00000000036cf000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x0000000740ba2088> (a java.util.concurrent.Semaphore$NonfairSync)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
	at java.util.concurrent.Semaphore.acquire(Semaphore.java:312)
	at test5.main(test5.java:27)
  • 匿名线程在执行完run函数后,就死亡了,但它没有释放信号量。
  • 主线程,由于获得不到信号量,就阻塞了。

Exchanger

Exchanger就好比 两两就消去的消消乐,只不过它加了一个规则就是两两配对成功后,会将信息交换后返回给对方。

import java.util.concurrent.Exchanger;

class ExchangerTest extends Thread {
    private Exchanger<String> exchanger;
    private String string;
    private String threadName;

    public ExchangerTest(Exchanger<String> exchanger, String string,
                         String threadName) {
        this.exchanger = exchanger;
        this.string = string;
        this.threadName = threadName;
    }

    public void run() {
        try {
            System.out.println(threadName + ": " + exchanger.exchange(string));
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

public class test {
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();
        ExchangerTest test1 = new ExchangerTest(exchanger, "string1",
                "thread-1");
        ExchangerTest test2 = new ExchangerTest(exchanger, "string2",
                "thread-2");
        ExchangerTest test3 = new ExchangerTest(exchanger, "string3", "thread-3");
        ExchangerTest test4 = new ExchangerTest(exchanger, "string4", "thread-4");

        test1.start();
        test2.start();
        test3.start();
        test4.start();
    }
}/*output:
thread-1: string2
thread-2: string1
*/

如果增加到三个线程,再加下面代码(注意,执行效果是随机的)。程序不会停止。

        ExchangerTest test3 = new ExchangerTest(exchanger, "string3", "thread-3");
        test3.start();
/*output:
thread-1: string2
thread-2: string1
*/        

如果增加到四个线程,再加下面代码(注意,执行效果是随机的)。程序会停止。

        ExchangerTest test3 = new ExchangerTest(exchanger, "string3", "thread-3");
        ExchangerTest test4 = new ExchangerTest(exchanger, "string4", "thread-4");
        test3.start();
        test4.start();
/*output:
thread-1: string2
thread-4: string3
thread-3: string4
thread-2: string1
*/         

综上可知:

  • Exchanger只适用于两个线程之间使用。当第一个线程调用exchange时,将阻塞;当第二个线程调用exchange时,不阻塞,并且第一个线程会被唤醒。
  • 当同时三个线程使用时,第三个线程由于没有第四个线程来执行exchange从而唤醒它,所以会一直阻塞,程序不停止。
  • 当同时四个线程使用时,程序能够停止,因为调用exchange的次数是偶数。但交换数据的双方是随机的。
  • 可以这么理解:Exchanger相当于CyclicBarrier ,只不过第一个参数,永远是2;第二个参数的匿名线程的逻辑就是交换两个元素。

//: concurrency/ExchangerDemo.java
import java.util.concurrent.*;
import java.util.*;
import net.mindview.util.*;

class ExchangerProducer<T> implements Runnable {
    private Generator<T> generator;  //生产者用Generator生产
    private Exchanger<List<T>> exchanger;
    private List<T> holder;
    ExchangerProducer(Exchanger<List<T>> exchg,
                      Generator<T> gen, List<T> holder) {
        exchanger = exchg;
        generator = gen;
        this.holder = holder;
    }
    public void run() {
        try {
            while(!Thread.interrupted()) {
                for(int i = 0; i < ExchangerDemo.size; i++)
                    holder.add(generator.next());
                // 每次都将充满的list去交换
                holder = exchanger.exchange(holder);
            }
        } catch(InterruptedException e) {
            // OK to terminate this way.
        }
    }
}

class ExchangerConsumer<T> implements Runnable {
    private Exchanger<List<T>> exchanger;
    private List<T> holder; //这里都没有初始化,所以默认是null
    private volatile T value;
    ExchangerConsumer(Exchanger<List<T>> ex, List<T> holder){
        exchanger = ex;
        this.holder = holder;
    }
    public void run() {
        try {
            while(!Thread.interrupted()) {
                holder = exchanger.exchange(holder);  //消费者不需要提供什么,所以交换null过去就行
                for(T x : holder) {
                    value = x; // 消费list中每个元素
                    holder.remove(x); // CopyOnWriteArrayList可以在遍历中删除
                }
            }
        } catch(InterruptedException e) {
            // OK to terminate this way.
        }
        System.out.println("Final value: " + value);
    }
}

public class ExchangerDemo {
    static int size = 10;
    static int delay = 5; // Seconds
    public static void main(String[] args) throws Exception {
        if(args.length > 0)
            size = new Integer(args[0]);
        if(args.length > 1)
            delay = new Integer(args[1]);
        ExecutorService exec = Executors.newCachedThreadPool();
        Exchanger<List<Fat>> xc = new Exchanger<List<Fat>>();
        List<Fat>
                producerList = new CopyOnWriteArrayList<Fat>(),
                consumerList = new CopyOnWriteArrayList<Fat>();
        exec.execute(new ExchangerProducer<Fat>(xc,
                BasicGenerator.create(Fat.class), producerList));
        exec.execute(
                new ExchangerConsumer<Fat>(xc,consumerList));
        TimeUnit.SECONDS.sleep(delay);
        exec.shutdownNow();
    }
} /* Output: (Sample)
Final value: Fat id: 29999  (执行结果每次随机)
*///:~
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值