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()可以进行类比,就不展开了