柒八块表的博客 程序猿一枚

Java多线程系列-JCU-API实现

2017-11-24

Java-Concurrent-Util

背景

接着上一篇《Java多线程系列-增加工作线程-线程池》,Java Concurrent包提供了一些API,怎么用呢;

需求

  1. 用JUC包提供的互斥、锁机制去改造我们的WebServer;

功能开发

JUC包提供了Lock相关类用来替代synchronized关键字(线程间互斥),提供了Condition来替代wait、notify方法(线程间通信);

我们用Lock、Condition来改造下阻塞队列;加入一个lock-队列本身的锁、一个写线程Condition、一个读线程Condition;如下:


// 共享变量锁
private Lock lock = new ReentrantLock();

// 写线程的条件
private Condition putCondition = lock.newCondition();

// 读线程的条件
private Condition takeCondition = lock.newCondition();

public SimpleQueue(int cap) {
    this.capacity = cap;
    this.items = new Object[cap];
    this.size = 0;
    this.putIndex = 0;
    this.takeIndex = 0;
}

public void put(E e) throws InterruptedException {
    lock.lock();
    try {
        // 监听器线程往队列中放入socket,如果当前队列满了则监听器等待
        while (isFull()) {
            Logs.SERVER.info("{} wait put queue : {}", Thread.currentThread().getName(), e);
            putCondition.await();
        }
        // 若队列没满,则监听器线程往队列中放入socket;并且如果先前已经有工作线程在等待取数据,通知工作线程来取
        items[putIndex] = e;
        putIndex = (putIndex + 1) % capacity;
        size++;
        Logs.SERVER.info("queue isFull {}, isEmpty {}, capacity {}, size {}, takeIndex {}, putIndex {}", isFull(), isEmpty(),
                capacity, size, takeIndex, putIndex);
        takeCondition.signalAll();
    } finally {
        lock.unlock();
    }
}

public E take() throws InterruptedException {
    // 工作线程来取socket,如果当前队列为空,则工作线程进行等待
    lock.lock();
    try {
        while (isEmpty()) {
            Logs.SERVER.info("{} wait get socket", Thread.currentThread().getName());
            takeCondition.await();
        }
        // 队列不为空,工作线程从队列中取出socket;并且如果先前有监听器线程在等待往队列中放数据,通知监听器线程放
        E e = (E) items[takeIndex];
        // 将已经取走的引用置为空,让GC可以回收
        items[takeIndex] = null;
        takeIndex = (takeIndex + 1) % capacity;
        size--;
        Logs.SERVER.info("queue isFull {}, isEmpty {}, capacity {}, size {}, takeIndex {}, putIndex {}", isFull(), isEmpty(),
                capacity, size, takeIndex, putIndex);
        putCondition.signal();
        return e;
    } finally {
        lock.unlock();
    }

}

完整代码实现:WebServer.java

知识点

对于解决线程间互斥、通信两个问题,至此我们用了两种方式,也即Java给出的两套方案,我们的程序逻辑没变,只是换一种方式去实现而已;

其实掌握了其中的思想,剩下的就是查API去实现逻辑,我认为的多线程编程两个主要问题点:互斥、通信,出现这两个问题的关键在于:共享变量,如果没有共享变量,都是线程本地变量,那么就不会存在资源冲突、通信了;打个比方:公司只一台跑步机,所有人都可以用、每个人就是一个线程,而跑步机便是共享变量,A同学在使用跑步机、那个其它同学就只能等待,这就是互斥,如果A用完、告诉B同学你可以使用了,这就叫通信;如果说每人一台跑步机,每个人的跑步机都是自己独享的,就不存在互斥、通信了,因为根本没有竞争;

想明白多线程问题的上下文就不会觉得多线程不好掌握了,其实和我们日常生活中的道理一致,只是苦于一些书籍写的太抽象、不接地气,导致我们理解因难,大道至简;

问题

JUC其实除了Lock、Condition,还给我们提供了很多工具类,比如线程池、Atomic包等,可以极大方便我们平时应用;

当然我们的线程池完全可以用ExcutorService替换掉,我们的队列完全可以也BlockingQueue替换掉;

感想

人生经历的过程的,就是不断认识自己不足、一点点改进的过程;


Similar Posts



Comments