Bootstrap

【Java从入门到放弃 之 多线程 三】

多线程 三

背景引入

这次引入背景之前,我先说了吧。之前我们通过synchronized 解决了多线程的竞争临界资源的问题。当然内存可见性的问题,我们之前也讨论过了。其实多线程还有一个问题需要引入,那就是多线程之间的协同。

class Producer {
    Queue<String> producer = new LinkedList<>();

    public synchronized void add(String s) {
        this.producer.add(s);
    }

    public synchronized String get() {
        while (producer.isEmpty()) {
        }
        return producer.remove();
    }
}

按照我们之前的写法,这样写可以吗? 不可以的。 为什么呢? 因为读取的时候获取的是对象锁。这样的话,写入就一直拿不到锁,写入就一直等待。这样的话,读取的一直拿到锁,在那里死循环。 这显然不是我们要的结果。其实我们要的是,读的时候如果里面没有就把锁放弃掉,不然就一直写入不了。其实这样就已经开始引入了线程之间的协同了。读线程与写线程要协同,不是简单的锁那样了。

不过好消息是,Java已经提供了支持。

public class Object {
    @IntrinsicCandidate
    public Object() {
    }

    @IntrinsicCandidate
    public final native Class<?> getClass();

    @IntrinsicCandidate
    public native int hashCode();

    public boolean equals(Object obj) {
        return this == obj;
    }

    @IntrinsicCandidate
    protected native Object clone() throws CloneNotSupportedException;

    public String toString() {
        return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());
    }

    @IntrinsicCandidate
    public final native void notify();

    @IntrinsicCandidate
    public final native void notifyAll();

    public final void wait() throws InterruptedException {
        this.wait(0L);
    }

    public final native void wait(long var1) throws InterruptedException;

    public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
        if (timeoutMillis < 0L) {
            throw new IllegalArgumentException("timeoutMillis value is negative");
        } else if (nanos >= 0 && nanos <= 999999) {
            if (nanos > 0 && timeoutMillis < 9223372036854775807L) {
                ++timeoutMillis;
            }

            this.wait(timeoutMillis);
        } else {
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }
    }

    /** @deprecated */
    @Deprecated(
        since = "9",
        forRemoval = true
    )
    protected void finalize() throws Throwable {
    }
}

可以看到wait()方法与notify() 和notifyAll() 这三个就是来实现我们说的协同效果的。下面我们通过案例,解决我们刚说的问题。

public class Test14 {

    public static void main(String[] args) throws InterruptedException {

        Producer producer = new Producer();
        LinkedList<Thread> threads = new LinkedList<>();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        String s = producer.get();
                        System.out.println(" read " + s);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();
            threads.add(thread);
        }

        Thread threadProducer = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    producer.add(String.valueOf(i));
                }
            }
        });

        threadProducer.start();
        threadProducer.join();
        Thread.sleep(100);

    }
}

再补充一个经典的生产者消费者模型的代码

public class Test14 {

    public static void main(String[] args) {
        MyQueue myQueue = new MyQueue();
        Producer producer = new Producer(myQueue);
        Consumer consumer = new Consumer(myQueue);
        producer.start();
        consumer.start();
    }


}
class MyQueue<E> {

    private Queue<E> queue = new ArrayDeque<>();

    public synchronized void put(E e) throws InterruptedException {
        queue.add(e);
        notifyAll();
    }
    public synchronized E take() throws InterruptedException {
        while(queue.isEmpty()) {
            wait();
        }
        E e = queue.poll();
        return e;
    }
}

class Producer extends Thread {
    MyQueue<String> queue;
    public Producer(MyQueue<String> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        try {
            while(true) {
                String task = String.valueOf(UUID.randomUUID());
                queue.put(task);
                System.out.println("produce " + task);
            }
        } catch (InterruptedException e) {
        }
    }
}

class Consumer extends Thread {
    MyQueue<String> queue;
    public Consumer(MyQueue<String> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        try {
            while(true) {
                String task = queue.take();
                System.out.println("consume " + task);
            }
        } catch(InterruptedException e) {
        }
    }
}

concurrent

Java5之后,引入了java.util.concurrent,提供了更多的并发的能力。下面我们简单介绍一下concurrent包下面的一些类。

  • 可重入锁 ReentrantLock
  • 读写锁 ReadWriteLock
  • 乐观锁 StampedLock
  • 信号量 Semaphore

可重入锁

** 使用可重入锁**

public class Test15 {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread[] myThreads = new Thread[100];
        for (int i = 0; i < myThreads.length; i++) {
            myThreads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 100; j++) {
                        counter.count();
                    }
                }
            });
            myThreads[i].start();
        }
        Thread.sleep(100);
        System.out.println(counter.num);
    }
}

class Counter {
    private final Lock lock = new ReentrantLock();

    public int num;

    public void count() {
        lock.lock();
        try {
            num++;
        }finally {
            lock.unlock();
        }
    }
}
线程协同

之前我们使用synchronized 是Java语言原生支持的,所以Object有协同用的方法。 现在我们使用的是可重入锁或者读写锁,那么线程协同,我们应该怎么解决?
答案: 使用Condition

使用方法:

  • Condition对象必须从Lock实例的newCondition()返回。
  • Condition的await()、signal()、signalAll()方法和synchronized使用的wait()、notify()、notifyAll()可以进行类比,就不展开了
;