Skip to content

Commit 7203bf3

Browse files
authored
Merge pull request #2 from CyC2018/master
update
2 parents ab9df54 + e287699 commit 7203bf3

File tree

5 files changed

+199
-14
lines changed

5 files changed

+199
-14
lines changed

notes/Java 并发.md

Lines changed: 187 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,30 @@
2828
* [join()](#join)
2929
* [wait() notify() notifyAll()](#wait-notify-notifyall)
3030
* [await() signal() signalAll()](#await-signal-signalall)
31+
* [七、J.U.C - AQS](#七juc---aqs)
32+
* [CountdownLatch](#countdownlatch)
33+
* [CyclicBarrier](#cyclicbarrier)
34+
* [Semaphore](#semaphore)
35+
* [八、J.U.C - 其它组件](#八juc---其它组件)
36+
* [FutureTask](#futuretask)
3137
* [BlockingQueue](#blockingqueue)
32-
* [七、线程不安全示例](#七线程不安全示例)
33-
* [八、Java 内存模型](#八java-内存模型)
38+
* [ForkJoin](#forkjoin)
39+
* [九、线程不安全示例](#九线程不安全示例)
40+
* [十、Java 内存模型](#十java-内存模型)
3441
* [主内存与工作内存](#主内存与工作内存)
3542
* [内存间交互操作](#内存间交互操作)
3643
* [内存模型三大特性](#内存模型三大特性)
3744
* [先行发生原则](#先行发生原则)
38-
* [、线程安全](#九线程安全)
45+
* [十一、线程安全](#十一线程安全)
3946
* [线程安全分类](#线程安全分类)
4047
* [线程安全的实现方法](#线程安全的实现方法)
41-
* [、锁优化](#十锁优化)
48+
* [十二、锁优化](#十二锁优化)
4249
* [自旋锁与自适应自旋](#自旋锁与自适应自旋)
4350
* [锁消除](#锁消除)
4451
* [锁粗化](#锁粗化)
4552
* [轻量级锁](#轻量级锁)
4653
* [偏向锁](#偏向锁)
47-
* [、多线程开发良好的实践](#九多线程开发良好的实践)
54+
* [十三、多线程开发良好的实践](#十三多线程开发良好的实践)
4855
* [参考资料](#参考资料)
4956
<!-- GFM-TOC -->
5057

@@ -683,6 +690,168 @@ public class AwaitSignalExample {
683690
}
684691
```
685692

693+
# 七、J.U.C - AQS
694+
695+
java.util.concurrent(J.U.C)大大提高了并发性能,AQS 被认为是 J.U.C 的核心。
696+
697+
## CountdownLatch
698+
699+
用来控制一个线程等待多个线程。
700+
701+
维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒。
702+
703+
<div align="center"> <img src="../pics//CountdownLatch.png"/> </div><br>
704+
705+
```java
706+
public class CountdownLatchExample {
707+
708+
public static void main(String[] args) throws InterruptedException {
709+
final int totalTread = 10;
710+
CountDownLatch countDownLatch = new CountDownLatch(totalTread);
711+
ExecutorService executorService = Executors.newCachedThreadPool();
712+
for (int i = 0; i < totalTread; i++) {
713+
executorService.execute(() -> {
714+
System.out.print("run..");
715+
countDownLatch.countDown();
716+
});
717+
}
718+
countDownLatch.await();
719+
System.out.println("end");
720+
executorService.shutdown();
721+
}
722+
}
723+
```
724+
725+
```html
726+
run..run..run..run..run..run..run..run..run..run..end
727+
```
728+
729+
## CyclicBarrier
730+
731+
用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。
732+
733+
和 CountdownLatch 相似,都是通过维护计数器来实现的。但是它的计数器是递增的,每次执行 await() 方法之后,计数器会加 1,直到计数器的值和设置的值相等,等待的所有线程才会继续执行。和 CountdownLatch 的另一个区别是 CyclicBarrier 的计数器可以循环使用,所以它才叫做循环屏障。
734+
735+
下图应该从下往上看才正确。
736+
737+
<div align="center"> <img src="../pics//CyclicBarrier.png"/> </div><br>
738+
739+
```java
740+
public class CyclicBarrierExample {
741+
public static void main(String[] args) throws InterruptedException {
742+
final int totalTread = 10;
743+
CyclicBarrier cyclicBarrier = new CyclicBarrier(totalTread);
744+
ExecutorService executorService = Executors.newCachedThreadPool();
745+
for (int i = 0; i < totalTread; i++) {
746+
executorService.execute(() -> {
747+
System.out.print("before..");
748+
try {
749+
cyclicBarrier.await();
750+
} catch (InterruptedException e) {
751+
e.printStackTrace();
752+
} catch (BrokenBarrierException e) {
753+
e.printStackTrace();
754+
}
755+
System.out.print("after..");
756+
});
757+
}
758+
executorService.shutdown();
759+
}
760+
}
761+
```
762+
763+
```html
764+
before..before..before..before..before..before..before..before..before..before..after..after..after..after..after..after..after..after..after..after..
765+
```
766+
767+
## Semaphore
768+
769+
Semaphore 就是操作系统中的信号量,可以控制对互斥资源的访问线程数。
770+
771+
<div align="center"> <img src="../pics//Semaphore.png"/> </div><br>
772+
773+
以下代码模拟了对某个服务的并发请求,每次只能由 3 个客户端同时访问,请求总数为 10。
774+
775+
```java
776+
public class SemaphoreExample {
777+
public static void main(String[] args) {
778+
final int clientCount = 3;
779+
final int totalRequestCount = 10;
780+
Semaphore semaphore = new Semaphore(clientCount);
781+
ExecutorService executorService = Executors.newCachedThreadPool();
782+
for (int i = 0; i < totalRequestCount; i++) {
783+
executorService.execute(()->{
784+
try {
785+
semaphore.acquire();
786+
System.out.print(semaphore.availablePermits() + " ");
787+
semaphore.release();
788+
} catch (InterruptedException e) {
789+
e.printStackTrace();
790+
}
791+
});
792+
}
793+
executorService.shutdown();
794+
}
795+
}
796+
```
797+
798+
```html
799+
2 1 2 2 2 2 2 1 2 2
800+
```
801+
802+
# 八、J.U.C - 其它组件
803+
804+
## FutureTask
805+
806+
在介绍 Callable 时我们知道它可以有返回值,返回值通过 Future 进行封装。FutureTask 实现了 RunnableFuture 接口,该接口继承自 Runnable 和 Future<V> 接口,这使得 FutureTask 既可以当做一个任务执行,也可以有返回值。
807+
808+
```java
809+
public class FutureTask<V> implements RunnableFuture<V>
810+
```
811+
812+
```java
813+
public interface RunnableFuture<V> extends Runnable, Future<V>
814+
```
815+
816+
当一个计算任务需要执行很长时间,那么就可以用 FutureTask 来封装这个任务,用一个线程去执行该任务,然后执行其它任务。当需要该任务的计算结果时,再通过 FutureTask 的 get() 方法获取。
817+
818+
```java
819+
public class FutureTaskExample {
820+
public static void main(String[] args) throws ExecutionException, InterruptedException {
821+
FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
822+
@Override
823+
public Integer call() throws Exception {
824+
int result = 0;
825+
for (int i = 0; i < 100; i++) {
826+
Thread.sleep(10);
827+
result += i;
828+
}
829+
return result;
830+
}
831+
});
832+
833+
Thread computeThread = new Thread(futureTask);
834+
computeThread.start();
835+
836+
Thread otherThread = new Thread(() -> {
837+
System.out.println("other task is running...");
838+
try {
839+
Thread.sleep(1000);
840+
} catch (InterruptedException e) {
841+
e.printStackTrace();
842+
}
843+
});
844+
otherThread.start();
845+
System.out.println(futureTask.get());
846+
}
847+
}
848+
```
849+
850+
```html
851+
other task is running...
852+
4950
853+
```
854+
686855
## BlockingQueue
687856

688857
java.util.concurrent.BlockingQueue 接口有以下阻塞队列的实现:
@@ -692,10 +861,6 @@ java.util.concurrent.BlockingQueue 接口有以下阻塞队列的实现:
692861

693862
提供了阻塞的 take() 和 put() 方法:如果队列为空 take() 将阻塞,直到队列中有内容;如果队列为满 put() 将阻塞,指到队列有空闲位置。
694863

695-
它们响应中断,当收到中断请求的时候会抛出 InterruptedException,从而提前结束阻塞状态。
696-
697-
是线程安全的。
698-
699864
**使用 BlockingQueue 实现生产者消费者问题**
700865

701866
```java
@@ -774,7 +939,13 @@ Consumer-3 is consuming product.( Made By Producer-3 )
774939
Consumer-4 is consuming product.( Made By Producer-4 )
775940
```
776941

777-
# 七、线程不安全示例
942+
943+
## ForkJoin
944+
945+
// TODO
946+
947+
948+
# 九、线程不安全示例
778949

779950
如果多个线程对同一个共享数据进行访问而不采取同步操作的话,那么操作的结果是不一致的。
780951

@@ -815,7 +986,7 @@ public class ThreadUnsafeExample {
815986
997
816987
```
817988

818-
# 、Java 内存模型
989+
# 、Java 内存模型
819990

820991
Java 内存模型视图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。
821992

@@ -1019,7 +1190,7 @@ join() 方法返回先行发生于 Thread 对象的结束。
10191190
10201191
如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。
10211192

1022-
# 、线程安全
1193+
# 十一、线程安全
10231194

10241195
## 线程安全分类
10251196

@@ -1334,7 +1505,7 @@ public T get() {
13341505

13351506
ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因为根本不存在多线程竞争。在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。
13361507

1337-
# 、锁优化
1508+
# 十二、锁优化
13381509

13391510
高效并发是从 JDK 1.5 到 JDK 1.6 的一个重要改进,HotSpot 虚拟机开发团队在这个版本上花费了大量的精力去实现各种锁优化技术,如适应性自旋(Adaptive Spinning)、锁消除(Lock Elimination)、锁粗化(Lock Coarsening)、轻量级锁(Lightweight Locking)和偏向锁(Biased Locking)等。这些技术都是为了在线程之间更高效地共享数据,以及解决竞争问题,从而提高程序的执行效率。
13401511

@@ -1409,7 +1580,8 @@ public static String concatString(String s1, String s2, String s3) {
14091580

14101581
偏向锁可以提高带有同步但无竞争的程序性能。它同样是一个带有效益权衡(Trade Off)性质的优化,也就是说,它并不一定总是对程序运行有利,如果程序中大多数的锁总是被多个不同的线程访问,那偏向模式就是多余的。在具体问题具体分析的前提下,有时候使用参数 -XX:-UseBiasedLocking 来禁止偏向锁优化反而可以提升性能。
14111582

1412-
# 九、多线程开发良好的实践
1583+
1584+
# 十三、多线程开发良好的实践
14131585

14141586
1. 给线程起个有意义的名字,这样可以方便找 Bug;
14151587

@@ -1432,3 +1604,4 @@ public static String concatString(String s1, String s2, String s3) {
14321604
- [Java - Understanding Happens-before relationship](https://www.logicbig.com/tutorials/core-java-tutorial/java-multi-threading/happens-before.html)
14331605
- [6장 Thread Synchronization](https://www.slideshare.net/novathinker/6-thread-synchronization)
14341606
- [How is Java's ThreadLocal implemented under the hood?](https://stackoverflow.com/questions/1202444/how-is-javas-threadlocal-implemented-under-the-hood/15653015)
1607+
- [Concurrent](https://sites.google.com/site/webdevelopart/21-compile/06-java/javase/concurrent?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1)

notes/设计模式.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,18 @@ if (uniqueInstance == null) {
126126
}
127127
```
128128

129+
uniqueInstance 采用 volatile 关键字修饰也是很有必要的。
130+
131+
`uniqueInstance = new Singleton();` 这段代码其实是分为三步执行。
132+
133+
1. 分配内存空间。
134+
2. 初始化对象。
135+
3. 将 uniqueInstance 指向分配的内存地址。
136+
137+
但是由于 JVM 具有指令重排的特性,有可能执行顺序变为了 `1>3>2`,这在单线程情况下自然是没有问题。但如果是多线程就有可能 B 线程获得是一个还没有被初始化的对象以致于程序出错。
138+
139+
所以使用 volatile 修饰的目的是禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
140+
129141
# 三、简单工厂
130142

131143
## 意图

pics/CountdownLatch.png

17 KB
Loading

pics/CyclicBarrier.png

20 KB
Loading

pics/Semaphore.png

27 KB
Loading

0 commit comments

Comments
 (0)