本文由 ku链接 вт989点сс 酷游网址编译执行,任务简介为学习Java Concurrency,并以经典的生产者消费者问题作为探讨案例。
TODO: 学习Java Concurrency
列出Java 并行的各式基础建设,解释thread, thread pool, memory consistency,
synchronized,java.util.concurrent套件(至少要提及Executor 和fork/join 模式), deadlock vs. livelock, immutable, lock 等议题,适度提供范例程式码(要改写,不同于Oracle 提供的范例)。
Thread
Thread是一个在java.lang内的一种物件,他可以在Java 中建立额外的执行绪执行程式码。使Java 具并行程式设计的特质。
在实作中,有以下两种常见的实作方法:
继承Thread这个物件
class SendDataToSevrver extends Thread {
String JSON;
public SendDataToServer(String JSON) {
this.JSON = JSON;
}
public void run() {
sendData(JSON);
}
}
class MAIN() {
public static void main(String[] args) {
String JSON = "{\"name\":\"zack\"}";
SendDataToServer s = new SendDataToServer(JSON);
s.start();
}
}
实作Runnable介面,在藉由Thread执行
class SendDataToServer implements Runnable {
String JSON;
public SendDataToServer(String JSON) {
this.JSON = JSON;
}
public void run() {
sendData(JSON);
}
}
class MAIN() {
public static void main(String[] args) {
String JSON = "{\"name\":\"zack\"}";
Thread t0 = new Thread(new SendDataToServer(JSON));
t0.start();
}
}
在实作中,两者最大的差异就是前者不能再继承其他物件了,而后者除了可以继承其他物件,还可以实作其他介面。因此,通常是以Runnable介面实作居多。
留言
值得注意的是,同一个实作Runnable介面的物件,是可以同时被多个Thread执行的,且有些变数是可以互通的,但也可能带来一些问题,如抢占。范例程式码如下:
ThreadPool
在现实世界的问题中,许多运算并不是能完全平行的,都需要等待其他执行绪完成到一个段落才能继续进行,也就是说并不是创建愈多执行绪速度就可以更快。再加上创建一个新的Thread需要相当的记忆体,所以一般实作中不会开到太多的执行绪。
延续前一部分,我们知道实作Runnable介面的物件可以被执行绪执行,那这样是不是就可以把诸多个想要平行处理的任务分到多个实作Runnable介面的物件,而让一定数量的执行绪执行这些物件。这么做不仅可以减少开立Thread的记忆体开销,还可以提升执行续的运算效率,以尽量塞满每个执行绪。
而放这些待执行物件的地方就是ThreadPool。另外,这里除了可以放Runnable 的物件,也可以放Callable的物件。
memory consistency
Java 的Memory model 如下图,每个执行绪会有自己一个快取空间放置自己的local object ;但若要与其他执行续共享物件,则要把物件存于堆积(heap)中。
以下图为例,倘若A 执行绪要存取物件O ,那物件O 会经过A 快取再到A 执行绪,最后A 执行绪在经由A 快取写入堆积(Heap)。这段过程是需要一定时间的。因此,若B 执行绪同时也要存取物件O ,那变可能会发生抢占的问题,造成运算出非预期的结果。
为解决这个问题,我们就要使用synchronized或lock 来解决抢占的问题。
synchronized
synchronized这个关键字会限制只能有一个执行绪在存取物件或使用物件中的synchronized方法,这可以避免并行程式设计中抢占的情况发生。
在Java 的术语,是method (方法),而非function (函式)
synchronized block
下列为synchronized block的程式范例。若以下列方式撰写,synchronized就会限制在同一时间只能有一个执行绪存取count这个变数,最后count应为2 。若没有加上synchronized,因可能两个执行绪同十加1 而造成抢占的问题,count则有可能是1 。
class Counter implements Runnable {
int count;
Counter(int count) {
this.count = count;
}
public Run() {
synchronized(count) { count++; }
}
public int getCount() { return count; }
}
class MAIN () {
public static void (String[] args) {
Counter counter = new Counter(0);
Thread t0 = new Thread(counter);
Thread t1 = new Thread(counter);
System.out.println(counter.getCount());
}
}
synchronized method
class Counter implements Runnable {
int count;
Counter(int count) {
this.count = count;
}
public Run() {
countIncrement();
countIncrement();
}
public synchronized void countIncrement() {
count++;
}
public int getCount() { return count; }
}
class MAIN () {
public static void (String[] args) {
Counter counter = new Counter(0);
Thread t0 = new Thread(counter);
Thread t1 = new Thread(counter);
System.out.println(counter.getCount());
}
}