From b27558acb1a4249d083ca9a762f9421a38f89101 Mon Sep 17 00:00:00 2001 From: Larry <40413350+liuhaidl10@users.noreply.github.com> Date: Wed, 24 Jul 2019 15:32:27 +0800 Subject: [PATCH 1/5] =?UTF-8?q?Update=20=E5=B9=B6=E5=8F=91=E7=BC=96?= =?UTF-8?q?=E7=A8=8B=E7=9A=84=E4=BC=98=E7=BC=BA=E7=82=B9.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...50\213\347\232\204\344\274\230\347\274\272\347\202\271.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/1.\345\271\266\345\217\221\347\274\226\347\250\213\347\232\204\344\274\230\347\274\272\347\202\271/\345\271\266\345\217\221\347\274\226\347\250\213\347\232\204\344\274\230\347\274\272\347\202\271.md" "b/1.\345\271\266\345\217\221\347\274\226\347\250\213\347\232\204\344\274\230\347\274\272\347\202\271/\345\271\266\345\217\221\347\274\226\347\250\213\347\232\204\344\274\230\347\274\272\347\202\271.md" index e45bdd2..661fed4 100644 --- "a/1.\345\271\266\345\217\221\347\274\226\347\250\213\347\232\204\344\274\230\347\274\272\347\202\271/\345\271\266\345\217\221\347\274\226\347\250\213\347\232\204\344\274\230\347\274\272\347\202\271.md" +++ "b/1.\345\271\266\345\217\221\347\274\226\347\250\213\347\232\204\344\274\230\347\274\272\347\202\271/\345\271\266\345\217\221\347\274\226\347\250\213\347\232\204\344\274\230\347\274\272\347\202\271.md" @@ -1,4 +1,4 @@ -一直以来并发编程对于刚入行的小白来说总是觉得高深莫测,于是乎,就诞生了想写点东西记录下,以提升理解和堆并发编程的认知。为什么需要用的并发?凡事总有好坏两面,之间的trade-off是什么,也就是说并发编程具有哪些缺点?以及在进行并发编程时应该了解和掌握的概念是什么?这篇文章主要以这三个问题来谈一谈。 +一直以来并发编程对于刚入行的小白来说总是觉得高深莫测,于是乎,就诞生了想写点东西记录下,以提升理解和对并发编程的认知。为什么需要用的并发?凡事总有好坏两面,之间的trade-off是什么,也就是说并发编程具有哪些缺点?以及在进行并发编程时应该了解和掌握的概念是什么?这篇文章主要以这三个问题来谈一谈。 # 1. 为什么要用到并发 # 一直以来,硬件的发展极其迅速,也有一个很著名的"摩尔定律",可能会奇怪明明讨论的是并发编程为什么会扯到了硬件的发展,这其中的关系应该是多核CPU的发展为并发编程提供的硬件基础。摩尔定律并不是一种自然法则或者是物理定律,它只是基于认为观测数据后,对未来的一种预测。按照所预测的速度,我们的计算能力会按照指数级别的速度增长,不久以后会拥有超强的计算能力,正是在畅想未来的时候,2004年,Intel宣布4GHz芯片的计划推迟到2005年,然后在2004年秋季,Intel宣布彻底取消4GHz的计划,也就是说摩尔定律的有效性超过了半个世纪戛然而止。但是,聪明的硬件工程师并没有停止研发的脚步,他们为了进一步提升计算速度,而不是再追求单独的计算单元,而是将多个计算单元整合到了一起,也就是形成了多核CPU。短短十几年的时间,家用型CPU,比如Intel i7就可以达到4核心甚至8核心。而专业服务器则通常可以达到几个独立的CPU,每一个CPU甚至拥有多达8个以上的内核。因此,摩尔定律似乎在CPU核心扩展上继续得到体验。因此,多核的CPU的背景下,催生了并发编程的趋势,通过**并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升**。 @@ -121,4 +121,4 @@ 阻塞和非阻塞通常用来形容多线程间的相互影响,比如一个线程占有了临界区资源,那么其他线程需要这个资源就必须进行等待该资源的释放,会导致等待的线程挂起,这种情况就是阻塞,而非阻塞就恰好相反,它强调没有一个线程可以阻塞其他线程,所有的线程都会尝试地往前运行。 ## 3.4 临界区 ## -临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每个线程使用时,一旦临界区资源被一个线程占有,那么其他线程必须等待。 \ No newline at end of file +临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每个线程使用时,一旦临界区资源被一个线程占有,那么其他线程必须等待。 From 72e5f34fbc26cacdd5c3364784263510cae72532 Mon Sep 17 00:00:00 2001 From: Larry <40413350+liuhaidl10@users.noreply.github.com> Date: Wed, 24 Jul 2019 18:22:35 +0800 Subject: [PATCH 2/5] =?UTF-8?q?Update=20=E7=BA=BF=E7=A8=8B=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E8=BD=AC=E6=8D=A2=E4=BB=A5=E5=8F=8A=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E6=93=8D=E4=BD=9C.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...5\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" | 1 + 1 file changed, 1 insertion(+) diff --git "a/2.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" "b/2.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" index 7458d85..bf6b695 100644 --- "a/2.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" +++ "b/2.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" @@ -209,6 +209,7 @@ public static native void yield();这是一个静态方法,一旦执行,它 另外需要注意的是,sleep()和yield()方法,同样都是当前线程会交出处理器资源,而它们不同的是,sleep()交出来的时间片其他线程都可以去竞争,也就是说都有机会获得当前线程让出的时间片。而yield()方法只允许与当前线程具有相同优先级的线程能够获得释放出来的CPU时间片。 # 4.守护线程Daemon # + - [深入浅出 JIT 编译器](https://www.ibm.com/developerworks/cn/java/j-lo-just-in-time/index.html) 守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默地守护一些系统服务,比如垃圾回收线程,JIT线程就可以理解守护线程。与之对应的就是用户线程,用户线程就可以认为是系统的工作线程,它会完成整个系统的业务操作。用户线程完全结束后就意味着整个系统的业务任务全部结束了,因此系统就没有对象需要守护的了,守护线程自然而然就会退。当一个Java应用,只有守护线程的时候,虚拟机就会自然退出。下面以一个简单的例子来表述Daemon线程的使用。 public class DaemonDemo { From 455784d8378254c68566353c08aafc22d0a2a865 Mon Sep 17 00:00:00 2001 From: Larry <40413350+liuhaidl10@users.noreply.github.com> Date: Wed, 24 Jul 2019 18:23:40 +0800 Subject: [PATCH 3/5] =?UTF-8?q?Update=20=E7=BA=BF=E7=A8=8B=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E8=BD=AC=E6=8D=A2=E4=BB=A5=E5=8F=8A=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E6=93=8D=E4=BD=9C.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/2.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" "b/2.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" index bf6b695..df4566c 100644 --- "a/2.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" +++ "b/2.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" @@ -196,7 +196,7 @@ public static native void sleep(long millis)方法显然是Thread的静态方法 1. sleep()方法是Thread的静态方法,而wait是Object实例方法 2. wait()方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。而sleep()方法没有这个限制可以在任何地方种使用。另外,wait()方法会释放占有的对象锁,使得该线程进入等待池中,等待下一次获取资源。而sleep()方法只是会让出CPU并不会释放掉对象锁; -3. sleep()方法在休眠时间达到后如果再次获得CPU时间片就会继续执行,而wait()方法必须等待Object.notift/Object.notifyAll通知后,才会离开等待池,并且再次获得CPU时间片才会继续执行。 +3. sleep()方法在休眠时间达到后如果再次获得CPU时间片就会继续执行,而wait()方法必须等待Object.notify/Object.notifyAll通知后,才会离开等待池,并且再次获得CPU时间片才会继续执行。 ## 3.4 yield ## From d08b165266e79a02ad905565e0d358e1efead6f6 Mon Sep 17 00:00:00 2001 From: Larry <40413350+liuhaidl10@users.noreply.github.com> Date: Wed, 24 Jul 2019 18:24:40 +0800 Subject: [PATCH 4/5] =?UTF-8?q?Update=20=E7=BA=BF=E7=A8=8B=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E8=BD=AC=E6=8D=A2=E4=BB=A5=E5=8F=8A=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E6=93=8D=E4=BD=9C.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...7\212\345\237\272\346\234\254\346\223\215\344\275\234.md" | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git "a/2.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" "b/2.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" index df4566c..91b73d5 100644 --- "a/2.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" +++ "b/2.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" @@ -174,7 +174,7 @@ Thread类除了提供join()方法外,另外还提供了超时等待的方法 } 输出结果为: - +``` > main terminated. > Thread-0 terminated. > Thread-1 terminated. @@ -185,7 +185,7 @@ Thread类除了提供join()方法外,另外还提供了超时等待的方法 > Thread-6 terminated. > Thread-7 terminated. > Thread-8 terminated. - +``` 在上面的例子中一个创建了10个线程,每个线程都会等待前一个线程结束才会继续运行。可以通俗的理解成接力,前一个线程将接力棒传给下一个线程,然后又传给下一个线程...... ## 3.3 sleep ## @@ -210,6 +210,7 @@ public static native void yield();这是一个静态方法,一旦执行,它 # 4.守护线程Daemon # - [深入浅出 JIT 编译器](https://www.ibm.com/developerworks/cn/java/j-lo-just-in-time/index.html) + 守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默地守护一些系统服务,比如垃圾回收线程,JIT线程就可以理解守护线程。与之对应的就是用户线程,用户线程就可以认为是系统的工作线程,它会完成整个系统的业务操作。用户线程完全结束后就意味着整个系统的业务任务全部结束了,因此系统就没有对象需要守护的了,守护线程自然而然就会退。当一个Java应用,只有守护线程的时候,虚拟机就会自然退出。下面以一个简单的例子来表述Daemon线程的使用。 public class DaemonDemo { From e0664bd9e8297bbac2de76c66e5eadeb407f45ae Mon Sep 17 00:00:00 2001 From: Larry <40413350+liuhaidl10@users.noreply.github.com> Date: Wed, 24 Jul 2019 21:48:17 +0800 Subject: [PATCH 5/5] =?UTF-8?q?Update=20=E7=BA=BF=E7=A8=8B=E6=B1=A0ThreadP?= =?UTF-8?q?oolExecutor=E5=AE=9E=E7=8E=B0=E5=8E=9F=E7=90=86.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...36\347\216\260\345\216\237\347\220\206.md" | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git "a/21.\347\272\277\347\250\213\346\261\240ThreadPoolExecutor\345\256\236\347\216\260\345\216\237\347\220\206/\347\272\277\347\250\213\346\261\240ThreadPoolExecutor\345\256\236\347\216\260\345\216\237\347\220\206.md" "b/21.\347\272\277\347\250\213\346\261\240ThreadPoolExecutor\345\256\236\347\216\260\345\216\237\347\220\206/\347\272\277\347\250\213\346\261\240ThreadPoolExecutor\345\256\236\347\216\260\345\216\237\347\220\206.md" index cbf7d9e..c422b96 100644 --- "a/21.\347\272\277\347\250\213\346\261\240ThreadPoolExecutor\345\256\236\347\216\260\345\216\237\347\220\206/\347\272\277\347\250\213\346\261\240ThreadPoolExecutor\345\256\236\347\216\260\345\216\237\347\220\206.md" +++ "b/21.\347\272\277\347\250\213\346\261\240ThreadPoolExecutor\345\256\236\347\216\260\345\216\237\347\220\206/\347\272\277\347\250\213\346\261\240ThreadPoolExecutor\345\256\236\347\216\260\345\216\237\347\220\206.md" @@ -109,6 +109,43 @@ execute方法执行逻辑有这样几种情况: 需要注意的是,线程池的设计思想就是使用了**核心线程池corePoolSize,阻塞队列workQueue和线程池maximumPoolSize**,这样的缓存策略来处理任务,实际上这样的设计思想在需要框架中都会使用。 +```java + class Producer implements Runnable { + private final BlockingQueue queue; + Producer(BlockingQueue q) { queue = q; } + public void run() { + try { + while (true) { queue.put(produce()); } + } catch (InterruptedException ex) { ... handle ...} + } + Object produce() { ... } + } + + class Consumer implements Runnable { + private final BlockingQueue queue; + Consumer(BlockingQueue q) { queue = q; } + public void run() { + try { + while (true) { consume(queue.take()); } + } catch (InterruptedException ex) { ... handle ...} + } + void consume(Object x) { ... } + } + + class Setup { + void main() { + BlockingQueue q = new SomeQueueImplementation(); + Producer p = new Producer(q); + Consumer c1 = new Consumer(q); + Consumer c2 = new Consumer(q); + new Thread(p).start(); + new Thread(c1).start(); + new Thread(c2).start(); + } + }} +``` + + # 4. 线程池的关闭 # 关闭线程池,可以通过`shutdown`和`shutdownNow`这两个方法。它们的原理都是遍历线程池中所有的线程,然后依次中断线程。`shutdown`和`shutdownNow`还是有不一样的地方: @@ -141,4 +178,4 @@ execute方法执行逻辑有这样几种情况: > 参考文献 《Java并发编程的艺术》 -[ThreadPoolExecutor源码分析,很详细](http://www.ideabuffer.cn/2017/04/04/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E7%BA%BF%E7%A8%8B%E6%B1%A0%EF%BC%9AThreadPoolExecutor/#addWorker%E6%96%B9%E6%B3%95) \ No newline at end of file +[ThreadPoolExecutor源码分析,很详细](http://www.ideabuffer.cn/2017/04/04/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E7%BA%BF%E7%A8%8B%E6%B1%A0%EF%BC%9AThreadPoolExecutor/#addWorker%E6%96%B9%E6%B3%95)