選手
1.JUCとは何ですか?
JUC: java.util の 3 つの同時プログラミング ツールキットを指します。
- java.util.concurrent
- java.util.concurrent.atomic
- java.util.concurrent.locks
マルチスレッドを実現する 4 つの方法:
- Threadクラスを継承する
- Runnableインターフェイスを実装する
- Callableインターフェイスを実装する
- スレッドプール
ビジネス: 共通スレッド コード スレッド
Runnableには戻り値がなく、Callable に比べて効率が比較的低くなります。
Runnable インターフェイスと Callable インターフェイスの違い:
- 戻り値の有無:Runnableは戻り値なし、Callableは戻り値あり
- 例外をスローするかどうか: call メソッドは結果を計算しますが、計算できない場合は例外がスローされます。
- 実装メソッド名が異なり、Runnable インターフェースは run メソッド、Callable インターフェースは call メソッドです。
2. スレッドとプロセス
プロセス: システム内で実行されているアプリケーション プログラムを指します。プログラムが実行されると、それはプロセスになります。プロセス - リソース割り当ての最小単位。
スレッド: システムがプロセッサ時間リソースを割り当てる基本単位、またはプロセス内で独立して実行される単位実行フロー。スレッド - プログラム実行の最小単位。
プロセス: これはプログラムであり、プロセスには複数のスレッドが含まれ、少なくとも 1 つのスレッドが含まれます。
Java にはデフォルトでメインと GC の 2 つのスレッドがあります。
Javaはスレッドを開くことができますか? start メソッドはスレッドを開始します
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//本地方法,底层C++,Java无法操作硬件,由操作系统决定是否创建线程,是否立即创建线程
private native void start0();
Java はスレッドを開始できません。基礎となるメソッドは start0() を呼び出します。これは、基礎となる C++ メソッドによって作成されたネイティブ メソッドです。Java はハードウェアを直接操作できません。
スレッドのいくつかの状態
Thread.State
public enum State {
NEW,//新建
RUNNABLE,//准备就绪
BLOCKED, //阻塞
WAITING,//一直等待
TIMED_WAITING,//超时等待,过时不候
TERMINATED;//终止
}
待機/睡眠の違い
1.別のクラスから
wait => オブジェクト、任意のオブジェクト インスタンスを呼び出すことができます
sleep => Thread、Threadの静的メソッド
2. ロックの解除について
wait はロックを解放しますが、sleep はロックを解放せず、ロックを占有する必要はありません。
3. 利用範囲と例外キャプチャが異なる
wait:同期コード ブロックで使用する必要があります。例外をキャッチする必要はありません。
sleep:どこでも使用できますが、例外をキャッチする必要があります
同時、並行
同時プログラミング: 同時、並列
同時実行性:複数のスレッドが同じリソースに同時にアクセスします (マルチスレッド共有リソース)
例: 春節旅行のチケットの入手、eコマースのフラッシュセール
並列処理:複数のタスクが一緒に実行され、後で集約されます。
例:電気ケトルでお湯を沸かしている間にインスタントラーメンを作り、インスタントラーメンの調味料を解体してバケツに注ぎます。
System.out.println(Runtime.getRuntime().availableProcessors());//获取cpu的核数
同時プログラミングの本質: CPU リソースを最大限に活用する
モニター
モニターモニター(つまり通常のロック)
これは、1 つのスレッドだけが保護されたデータまたはコードに同時にアクセスすることを保証する同期メカニズムです。
Jvm 同期は入口と出口に基づいており、モニター オブジェクトを使用して実装されます。
ユーザースレッドとデーモンスレッド
ユーザースレッド: カスタムスレッド (new Thread())
デーモンスレッド: ガベージコレクションなどのバックグラウンドの特別なスレッド
メインスレッドは終了しますが、ユーザースレッドはまだ実行中であり、JVM は存続します。
ユーザー スレッドはもう存在せず、すべてデーモン スレッドになり、jvm が終了します。
3.ロックロック(強調)
マルチスレッドプログラミングの手順:
- リソースクラスを作成し、リソースクラスに属性と操作メソッドを作成します。
- 複数のスレッドを作成し、リソースクラスの操作メソッドを呼び出す
ケース: 3 人の車掌が同時に 30 枚の切符を販売します。
従来の同期
public class SaleTicketDemo1 {
public static void main(String[] args) {
//并发:多个线程操作同一个资源类,把资源类丢入线程
Ticket ticket = new Ticket();
//Runnable接口 -》 函数式接口,lambda表达式:函数式接口的实例
new Thread(() -> {
for (int i = 0; i < 30; i++) {
ticket.sale();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 30; i++) {
ticket.sale();
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 30; i++) {
ticket.sale();
}
},"C").start();
}
}
/**
* 资源类
*/
class Ticket {
//属性
private int num = 30;
//synchronized 本质:线程串行化,排队,锁
public synchronized void sale() {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + (num--) + "张票,剩余" + num + "张");
}
}
}
ロックインターフェース
実装クラス
公平なロック: 非常に公平、早い者勝ち (日当たりが良く、効率が比較的低い)
不公平なロック: 非常に不公平です。キューにジャンプすることができます (デフォルトでは、スレッドの枯渇を引き起こす可能性がありますが、効率は高くなります)
public class SaleTicketDemo2 {
public static void main(String[] args) {
//并发:多个线程操作同一个资源类,把资源类丢入线程
Ticket2 ticket = new Ticket2();
new Thread(() -> {
for (int i = 0; i < 40; i++) ticket.sale();},"A").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) ticket.sale();},"B").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) ticket.sale();},"C").start();
}
}
/**
* 资源类
*
* Lock三部曲
* 1.new ReentrantLock();
* 2.lock.lock(); //加锁
* 3.finally -》 lock.unlock();//解锁
*
*/
class Ticket2 {
//属性
private int num = 30;
// 创建可重入锁
Lock lock = new ReentrantLock();
public void sale() {
lock.lock(); //加锁
try {
//业务代码
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + (num--) + "张票,剩余" + num + "张");
}
} finally {
lock.unlock();//解锁
}
}
}
同期とロックの違い
1. Synchronized は Java の組み込みキーワードであり、Lock は Java のインターフェースです
2. Synchronized はロック取得のステータスを判断できませんが、Lock はロックが取得されたかどうかを判断できます。
3. 同期すると自動的にロックが解除されますが、ロックは手動でロックを解除する必要があります。ロックを解除しないとデッドロックが発生します
4. ロックの取得中に同期スレッドがブロックされると、2 番目のスレッドは愚かに待つだけになり、ロックは必ずしも待つ必要はありません。
5. 同期リエントラントロック、中断不可、不公平、ロック、リエントラントロック、ロック判定可能、不公平/公平(自分で設定可能、デフォルトは不公平ロック)
6. synchronized は少量の同期コードのロックに適しており、Lock は大量の同期コードのロックに適しています。
7. ロックにより、読み取り操作の複数スレッドの効率が向上します。
在性能上来说,如果竞争资源不激烈,两者性能是差不多的,而当竞争资源非常激烈时(即大量线程同时竞争),此时Lock的性能要远远优于synchronized
4. スレッド間の通信
生産者と消費者の問題
インタビュー: シングルトン モード、ソート アルゴリズム、プロデューサー、コンシューマーの問題、デッドロック
リソースクラス操作手順:判定待ち~「業務実行中~」ウェイクアップ通知
生産者と消費者の問題の同期バージョン
Object类中方法
/**
* 线程之间通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* 线程之间交替执行 A B 操作同一个变量 num = 0
* A num+1
* B num-1
*/
public class ProducerAndConsumer {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
}
}
/**
* 资源类
*
* 判断等待,业务,通知
*/
class Data {
private int num = 0;
//+1
public synchronized void increment() throws InterruptedException {
if (num != 0) {
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
//通知其他线程我+1 完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if (num == 0) {
//等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
//通知其他线程我-1 完毕了
this.notifyAll();
}
}
問題が存在します。ABCD 4 スレッドの偽ウェイクアップ
public class ProducerAndConsumer {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
/**
* 资源类
* <p>
* 判断等待,业务,通知
*/
class Data {
private int num = 0;
/**
* +1
*/
public synchronized void increment() throws InterruptedException {
if (num != 0) {
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
//通知其他线程我+1 完毕了
this.notifyAll();
}
/**
* -1
*/
public synchronized void decrement() throws InterruptedException {
if (num == 0) {
//等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
//通知其他线程我-1 完毕了
this.notifyAll();
}
}
为什么会出现虚假唤醒问题呢?
主な理由は、wait メソッドは待機している場所からウェイクアップするためです (wait はロックを保持せず、他のスレッドが直接ロックを取得できます)。条件が初めて満たされる限り、ウェイクアップを待機するのは 1 回だけです。次の +1/-1 操作は直接実行されるため、>1 または <0 の数値が発生したり、誤ったウェイクアップによって発生するデッドロックが発生したりすることがあります。
誤ったウェイクアップの問題を防ぐ: if を while に変更します
/**
* 线程之间通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* 线程之间交替执行 A B 操作同一个变量 num = 0
* A num+1
* B num-1
*/
public class ProducerAndConsumer {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
/**
* 资源类
*
* 判断等待,业务,通知
*/
class Data {
private int num = 0;
//+1
public synchronized void increment() throws InterruptedException {
while (num != 0) {
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
//通知其他线程我+1 完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
while (num == 0) {
//等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
//通知其他线程我-1 完毕了
this.notifyAll();
}
}
JUC 版の生産者と消費者の問題
ロックによる条件検索(公式ドキュメント)
コード
public class ProducerAndConsumer2 {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
/**
* 资源类
*
* 判断等待,业务,通知
*/
class Data2 {
private int num = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//condition.await(); // 等待
//condition.signalAll(); // 唤醒全部
//+1
public void increment() throws InterruptedException {
lock.lock();
try {
//业务代码
while (num != 0) {
//等待
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
//通知其他线程我+1 完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
lock.lock();
try {
while (num == 0) {
//等待
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
//通知其他线程我-1 完毕了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
マルチスレッドプログラミングの完全な手順
ステップ 1: リソース クラスを作成し、リソース クラスに属性と操作メソッドを作成する
ステップ2: リソースクラスの操作方法
(1) 判定待ち
(2) ビジネスロジック
(3) 通知ウェイクアップ
ステップ 3: 複数のスレッドを作成し、リソース クラスの操作メソッドを呼び出す
ステップ 4: 誤ったウェイクアップを防止する
线程间定制通信
スレッドは合意された順序で実行されます
新しいテクノロジーには必ず元のテクノロジーをカバーするだけではなく、その利点と補足があります。
条件の正確な通知とスレッドのウェイクアップ
コードの実装: 3 つのスレッドが順次実行されます。
/**
* A B C三个线程顺序执行
*/
public class C {
public static void main(String[] args) {
Resource resource = new Resource();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.printA();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.printB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.printC();
}
}, "C").start();
}
}
/**
* 资源类 Lock
*/
class Resource {
private final Lock lock = new ReentrantLock();
private final Condition condition1 = lock.newCondition();
private final Condition condition2 = lock.newCondition();
private final Condition condition3 = lock.newCondition();
private int num = 1; // 1A 2B 3C
public void printA() {
lock.lock();
try {
//业务 判断 -》 执行 -》 通知
while (num != 1) {
//等待
condition1.await();
}
num = 2;
System.out.println(Thread.currentThread().getName() + "=>AAA");
//唤醒,唤醒指定线程B
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
//业务 判断 -》 执行 -》 通知
while (num != 2) {
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBB");
num = 3;
//唤醒线程C
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
//业务 判断 -》 执行 -》 通知
while (num != 3) {
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "=>CCC");
num = 1;
//唤醒线程A
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5. エイトロック現象
ロックとは何ですか? ロックが誰であるかをどうやって見分けるか?
任意のクラスのオブジェクト、クラス オブジェクト
ロックについての深い理解
* 八锁,就是关于锁的八个问题
* 1、标准情况下,两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话
* 2、sendSms()延迟4s情况下,两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话
*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
//锁的存在
new Thread(() -> {
phone.sendSms();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(() -> {
phone.call();
},"B").start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者!
//两个方法用的是同一把锁,谁先拿到谁执行!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
/**
* 3、增加了一个普通方法后,先打印发短信还是hello? hello
* 4、两个对象,两个同步方法 两个线程先打印 发短信 还是 打电话? 1/打电话 2/发短信
*/
public class Test2{
public static void main(String[] args) {
//两个对象,两个不同的对象,两把锁!
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
//锁的存在
new Thread(() -> {
phone1.sendSms();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(() -> {
phone2.call();
},"B").start();
}
}
class Phone2{
//synchronized 锁的对象是方法的调用者!
//两个方法用的是同一把锁,谁先拿到谁执行!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
//没有锁,不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}
/**
* 5.增加两个静态的同步方法 一个对象 两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话
* 6.两个对象 两个静态的同步方法 两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话
*/
public class Test3 {
public static void main(String[] args) {
//两个不同的对象 但是类只会加载一次 共用一把锁
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
//锁的存在
new Thread(() -> {
phone1.sendSms();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(() -> {
phone2.call();
},"B").start();
}
}
class Phone3{
//static静态方法 锁的是类对象 Phone3.class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
/**
* 7.一个静态同步方法一个普通同步方法 一个对象 两个线程先打印 发短信 还是 打电话? 打电话 发短信
* 8.一个静态同步方法一个普通同步方法 两个对象 两个线程先打印 发短信 还是 打电话? 打电话 发短信
*/
public class Test4 {
public static void main(String[] args) {
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
//锁的存在
new Thread(() -> {
phone1.sendSms();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(() -> {
phone2.call();
},"B").start();
}
}
class Phone4{
//static静态同步方法 锁的是类对象 Phone3.class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通同步方法 锁的是方法调用者
public synchronized void call(){
System.out.println("打电话");
}
}
まとめ:
- 同期方式では、最初にロックを取得した人が先に実行し、同じロックが順番に実行されます。
- 通常の同期メソッドは、this の現在のオブジェクト (つまり、メソッドの呼び出し元) をロックします。
- 静的同期メソッドはクラス オブジェクトをロックし、クラスは 1 回だけロードされるため、静的同期メソッドは常にクラス オブジェクト (XXX.class) をロックします。
同期の同期実装の基礎: Java のすべてのオブジェクトをロックとして使用できます。
それは以下の3つの形で現れます。
- 通常の同期メソッドの場合、ロックは現在のインスタンス オブジェクトです。
- 静的同期メソッドの場合、ロックは現在のクラスの Class オブジェクトです。
- 同期メソッド ブロックの場合、ロックは同期括弧内で構成されたオブジェクトです。
6. コレクション クラスは安全ではありません
リストは安全ではありません
//java.util.ConcurrentModificationException 并发修改异常!
public class ListTest {
public static void main(String[] args) {
//并发下 ArrayList不安全
/*
解决方案
1、List<String> list = new Vector<>();
2、List<String> list = Collections.synchronizedList(new ArrayList<>());
3、List<String> list = new CopyOnWriteArrayList<>();
*/
//CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略;
//多个线程操作的时候 list 读取的时候 固定的 写入(覆盖)
//再写入的时候避免覆盖,造成数据问题!
//读写分离
//CopyOnWriteArrayList 比 Vector 好在哪里?
//Vector 底层是synchronized实现效率较低 ; CopyOnWriteArrayList 底层是ReentrantLock实现 效率更高 灵活性也更高
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
セットは安全ではありません
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
//java.util.ConcurrentModificationException
public class SetTest {
public static void main(String[] args) {
//HashSet<String> set = new HashSet<>();
//解决方案一:
//Set<String> set = Collections.synchronizedSet(new HashSet<>());
//解决方案二:
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
HashSet の最下層は何ですか?
HashSet の最下層は HashMap です
public HashSet() {
map = new HashMap<>();
}
// add() set 本质就是map 中的key, key是不可重复的!
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// PRESENT 不变的值!
private static final Object PRESENT = new Object();
地図は安全ではありません
HashMap の最下層を確認する
public class MapTest {
public static void main(String[] args) {
/*
并发下 HashMap线程不安全
解决方案:
1.Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());
2.Map<String, Object> map = new ConcurrentHashMap<>();
*/
//HashMap<String, Object> map = new HashMap<>();
//Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());
Map<String, Object> map = new ConcurrentHashMap<>();
//加载因子,初始容量
for (int i = 0; i < 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
7、呼び出し可能
1. 戻り値を持つことができます
2. 例外をスローできる
3. さまざまなメソッド、run() -> call()
コードのテスト
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread(new MyThread()).start();
/*
本身Callable接口和Runnable接口毫无关系
通过一个Runnable接口的实现类FutureTask,Callable接口与Runnable接口构建了关系,便可以启动线程
*/
//适配类 FutureTask 是 Runnable接口的实现类 构造器 FutureTask(Callable<V> callable)
MyThread1 t1 = new MyThread1();
FutureTask<Integer> futureTask = new FutureTask<>(t1); //泛型是线程返回值类型
/*
启动两个线程,只会打印一个`call()...`
*/
new Thread(futureTask,"A").start(); //怎么启动Callable
new Thread(futureTask,"B").start(); //结果会被缓存,效率高
Integer i = futureTask.get(); //获取线程返回值 get()可能会产生阻塞!把他放到最后 或者 使用异步通信来处理!
System.out.println(i);
}
}
class MyThread implements Runnable{
@Override
public void run() {
}
}
/**
* 泛型是返回值类型
*/
class MyThread1 implements Callable<Integer>{
@Override
public Integer call(){
System.out.println("call()...");
//耗时的操作
return 1024;
}
}
知らせ:
1. キャッシュがある
2. 結果が得られるまで待つ必要がある場合があります。結果はブロックされます。
8. よく使用される補助クラス (必須)
8.1 CountDownLatch(減算カウンタ)
/**
* 计数器
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//倒计时 起始为6 必须要执行任务的时候,再使用!
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "--->Go out");
countDownLatch.countDown(); // -1
}, String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归零在向下执行
System.out.println("Close Door!");
}
}
原理:
countDownLatch.countDown () ; // カウント - 1
countDownLatch.await () ; // カウンタが 0 に戻るのを待ってから、下方向に実行します。
スレッドが countDown() を呼び出すたびに数値 -1 がカウントされ、カウンタが 0 になると、countDownLatch.await() が起動され、実行が継続されます。
8.2 CyclicBarrier (カウンターの追加)
public class CyclicBarrierDemo {
public static void main(String[] args) {
/*
集齐7颗龙珠召唤神龙
*/
//召唤神龙的线程
CyclicBarrier barrier = new CyclicBarrier(7, () -> System.out.println("成功召唤神龙!"));
for (int i = 1; i <= 7; i++) {
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集了" + temp + "个龙珠");
try {
barrier.await(); //等待
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
8.3 セマフォ(セマフォ)
駐車場を確保しましょう!
6台 駐車スペース3台
public class SemaphoreDemo {
public static void main(String[] args) {
//线程数量:停车位! 限流!
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
//acquire() 获得
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); //release() 释放
}
},String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire () ; //取得、いっぱいの場合は解放されるまで待ちます!
semaphore.release () ; // release は、現在のセマフォ +1 を解放し、待機中のスレッドを起動します。
役割: 複数の共有リソースを相互に排他的に使用します。同時電流制限、最大スレッド数を制御!
9. 読み取り/書き込みロック
書き込みロック:排他ロック
読み取りロック: 共有ロック
読み取り/書き込みロック: リソースは複数の読み取りスレッドまたは書き込みスレッドによってアクセスできますが、読み取りスレッドと書き込みスレッドは同時に存在できません。読み取りと書き込みは相互に排他的であり、読み取りと読み取りは共有されます。
読み取り書き込みロック
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <= 5; i++) {
final int temp = i;
//写入
new Thread(() -> {
myCache.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
final int temp = i;
//读取
new Thread(() -> {
System.out.println("获取" + temp + "缓存数据-> " + myCache.get(temp + ""));
}, String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存
*/
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
/**
* 存 写
*/
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "写入" + key);
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入OK");
}
/**
* 取 读
*/
public Object get(String key) {
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
return o;
}
}
操作結果:
結果分析: マルチスレッド操作であるため、複数のスレッドが同時に起動されます。書き込みスレッドがデータを書き込むまでに時間がかかることがありますが、この時点ではまだデータが書き込まれておらず、読み取りスレッドがデータの読み取りを開始し、結果としてデータが読み込まれない場合は、ロック制御が必要です。
読み取り/書き込みロックを追加します。
/**
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
*
* ReadWriteLock
* 读-读 可以共存!
* 读-写 不可共存!
* 写-写 不可共存!
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <= 5; i++) {
final int temp = i;
//写入
new Thread(() -> {
myCache.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
final int temp = i;
//读取
new Thread(() -> {
System.out.println("获取" + temp + "缓存数据-> " + myCache.get(temp + ""));
}, String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存
*/
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 存 写
*/
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "写入" + key);
try {
TimeUnit.MILLISECONDS.sleep(200);
map.put(key, value);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
System.out.println(Thread.currentThread().getName() + "写入OK");
}
/**
* 取 读
*/
public Object get(String key) {
readWriteLock.readLock().lock();
Object o = null;
try {
System.out.println(Thread.currentThread().getName() + "读取" + key);
o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
return o;
}
}
この時点では結果は正しいです
結果分析: 読み取り/書き込みロックの追加により、書き込みロックは排他ロック、読み取りロックは共有ロックになります。したがって、書き込みプロセス中はいかなる操作も許可されず、書き込み操作が完了した後は、複数のスレッドが読み取りを共有できます。
ロックのダウングレード: 書き込みロックは読み取りロックにダウングレードされますが、読み取りロックを書き込みロックにアップグレードすることはできません。
10. キューのブロック
ブロッキングキュー |
---|
ブロッキング キューはどのような状況で使用されますか: マルチスレッドの同時処理、スレッド プール!
キューの使い方を学ぶ
削除を追加
4つのAPI
道 | 例外をスローする | 例外をスローしません | ブロック待機 | タイムアウト待機 |
---|---|---|---|---|
追加 | 追加() | オファー() | 置く() | Offer(E e、長いタイムアウト、TimeUnit 単位) |
削除 | 削除() | ポーリング() | 取った() | ポーリング(長いタイムアウト、TimeUnit単位) |
キューの最初の要素を取得します | エレメント() | ピーク() | - | - |
/**
* 抛出异常
*/
public static void test1() {
//队列的大小
ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
//java.lang.IllegalStateException: Queue full 抛出异常
//System.out.println(arrayBlockingQueue.add("d"));
System.out.println("**************");
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
//java.util.NoSuchElementException 抛出异常
//System.out.println(arrayBlockingQueue.remove());
}
/**
*有返回值,不抛出异常
*/
public static void test2(){
ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
//System.out.println(arrayBlockingQueue.offer("d")); // false 不抛出异常!
System.out.println("*****");
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll()); // null 不抛出异常!
}
/**
*等待,阻塞(一直阻塞)
*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
//一直阻塞
arrayBlockingQueue.put("a");
arrayBlockingQueue.put("b");
arrayBlockingQueue.put("c");
//arrayBlockingQueue.put("d"); //队列没有位置了,一直阻塞
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take()); //没有数据了,一直阻塞
}
/**
*等待,阻塞(等待超时)
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.offer("a");
arrayBlockingQueue.offer("b");
arrayBlockingQueue.offer("c");
arrayBlockingQueue.offer("d",2, TimeUnit.SECONDS); //等待两秒,超时退出
System.out.println("******");
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
arrayBlockingQueue.poll(2, TimeUnit.SECONDS); //等待两秒,超时退出
}
SynchronousQueue 同期キュー
要素を入力した後、その要素を取り出すまで待ってから、その要素に別の要素を追加する必要があります。
put()、take()
/**
* 同步队列
* SynchronousQueue 和 BlockingQueue 不一样,SynchronousQueue 不存储元素
* put一个元素,必须从里面take取出来,否则不能再put进去值!(存一个,取一个,循环)
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
//存一个元素,取一个元素 循环
SynchronousQueue<Object> synchronousQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "->put 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName() + "->put 2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName() + "->put 3");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "->take " + synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "->take " + synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "->take " + synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T2").start();
}
}
11. スレッド プール (強調)
スレッド プール: 3 つのメソッド、7 つのパラメータ、および 4 つの拒否戦略
プーリング技術
プログラムは実行されますが、要はシステム リソースを占有しているということです。リソースの使用を最適化します。=> プーリング技術
スレッドプール、接続プール、メモリプール、オブジェクトプール...
作成したり破壊したりすることはリソースの無駄です。
プーリング技術:事前にリソースを用意しておき、使いたい人が来たら取りに来て、使用後は返却することで効率を高めます。
スレッドプールの利点
1. リソース消費を削減します。
2. 応答速度を向上させます。
3. 便利なスレッド管理。
スレッドの多重化、最大同時実行数の制御、スレッドの管理が可能
スレッドプール: 3 つの方法
/**
* Executors 工具类:创建线程池 3大方法
*/
public class Demo1 {
public static void main(String[] args) {
//ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建单个线程的线程池
//ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建固定线程的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();//创建可伸缩线程池
try {
for (int i = 0; i < 10; i++) {
//使用了线程池之后,用线程池来创建线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
ソースコード分析: 7 つのパラメータ
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//本质:ThreadPoolExecutor()
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂:创建线程,一般不用动
RejectedExecutionHandler handler) {
//拒绝策略
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
スレッドプールを手動で作成する
/**
* Executors 工具类:创建线程池 3大方法
*
* 4大拒绝策略:
* new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略 银行满了,还有人进来,不处理这个人,抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里!
* new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常
* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试和最早的竞争,也不会抛出异常!
*
*/
public class Demo2 {
public static void main(String[] args) {
//工具类创建
//ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建单个线程的线程池
//ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建固定线程的线程池
//ExecutorService threadPool = Executors.newCachedThreadPool();//创建可伸缩线程池
//手动创建线程池 ThreadPoolExecutor
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()); //队列满了,尝试和最早的竞争,也不会抛出异常!
try {
//最大承载:Queue + max
//超出最大承载抛出RejectedExecutionException 异常 (默认拒绝策略)
for (int i = 0; i < 9; i++) {
//使用了线程池之后,用线程池来创建线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
4 つの拒否戦略
* 4大拒绝策略:
* new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略 银行满了,还有人进来,不处理这个人,抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里!
* new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常
* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试和最早的竞争,也不会抛出异常!
概要と拡張
スレッド プール内のスレッドの最大数を設定するにはどうすればよいですか?
理解: IO 集中型、CPU 集中型 (チューニング)
public class Demo1 {
public static void main(String[] args) {
//手动创建线程池 ThreadPoolExecutor
//最大线程到底如何定义?
//1、CPU 密集型 电脑处理器数是几,就是几,可以保证CPU的效率最高!
//2、IO 密集型 大于 程序中十分耗IO的线程数 ---> 程序中 15个大型任务 io十分占用资源! =》 30
//获取CPU核数 电脑处理器数
System.out.println(Runtime.getRuntime().availableProcessors());
ExecutorService threadPool = new ThreadPoolExecutor(
2,
Runtime.getRuntime().availableProcessors(),
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()); //队列满了,尝试和最早的竞争,也不会抛出异常!
try {
//最大承载:Queue + max
//超出最大承载 RejectedExecutionException (默认拒绝策略)
for (int i = 0; i < 9; i++) {
//使用了线程池之后,用线程池来创建线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
12. 4 つの主要な機能インターフェイス (マスターする必要があります)
新時代のプログラマー: ラムダ式 (本質的に関数型インターフェイスのインスタンス)、チェーン プログラミング、関数型インターフェイス、ストリーム ストリーミング コンピューティング
関数型インターフェース: 抽象メソッドを 1 つだけ持つインターフェース
//此注解用来判断该接口是否是函数式接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
//简化编程模型,在新版本的框架底层大量应用!
関数機能インターフェース |
---|
コードのテスト
/**
* Function 函数型接口 有一个输入,有一个输出
* 只要是 函数式接口 可以用 lambda表达式简化
*/
public class Demo01 {
public static void main(String[] args) {
//输出输入的值
// Function<String ,String > fun = new Function<String ,String >() {
// @Override
// public String apply(String str) {
// return str;
// }
// };
Function<String ,String > fun = (str) -> str; //lambda表达式
System.out.println(fun.apply("123"));
}
}
述語アサーション インターフェイス |
---|
コードのテスト
/**
* Predicate 断定型接口 有一个输入值 返回值是布尔值!
*/
public class Demo02 {
public static void main(String[] args) {
//判断字符串是否为空
// Predicate<String> predicate = new Predicate<String>() {
// @Override
// public boolean test(String str) {
// return "".equals(str);
// }
// };
Predicate<String> predicate = str -> "".equals(str);
System.out.println(predicate.test(""));
System.out.println(predicate.test("123"));
}
}
消費者向け消費者向けインターフェース |
---|
コードのテスト
/**
* Consumer 消费型接口 只接收参数,不返回值
*/
public class Demo03 {
public static void main(String[] args) {
//接收参数,将其打印出来
// Consumer<String> consumer = new Consumer<String>() {
// @Override
// public void accept(String str) {
// System.out.println(str);
// }
// };
Consumer<String> consumer = str -> System.out.println(str);
consumer.accept("hello");
}
}
サプライヤー供給インターフェース |
---|
コードのテスト
/**
* Supplier 供给型接口 不需参数,有返回值
*/
public class Demo04 {
public static void main(String[] args) {
// Supplier<String> supplier = new Supplier<String>() {
// @Override
// public String get() {
// return "world";
// }
// };
Supplier<String> supplier = () -> "world";
System.out.println(supplier.get());
}
}
13. ストリームコンピューティング
ストリームコンピューティングとは
ビッグデータ: ストレージ + コンピューティング
ストレージ: コレクション、MySQL
計算: ストリームコンピューティング~
public class Test {
public static void main(String[] args) {
User user1 = new User(1, "a", 21);
User user2 = new User(2, "b", 22);
User user3 = new User(3, "c", 23);
User user4 = new User(4, "d", 24);
User user5 = new User(5, "e", 25);
User user6 = new User(6, "f", 26);
//存储交给集合
List<User> list = Arrays.asList(user1, user2, user3, user4, user5, user6);
//计算交给Stream流
//lambda表达式、链式编程、函数式接口、Stream流式计算
list.stream()
.filter(user -> user.getId() % 2 == 0) //找出id为偶数的用户
.filter(user -> user.getAge() > 23) //年龄大于23岁
.map(user -> user.getName().toUpperCase()) //用户名 转为大写
.sorted((u1, u2) -> -u1.compareTo(u2)) //用户名字母到着排序
.limit(1) //只输出一个用户
.forEach(System.out::println);
}
}
14、フォークジョイン
フォークジョインとは
JDK1.7でForkJoinしてタスクを並列実行!効率アップ〜。大容量のデータ通信速度がさらに速くなります!
ビッグデータ: MapReduce の中心となるアイデア -> 大きなタスクを小さなタスクに分割します。
ForkJoin の機能: ジョブ盗用!
実現原理は次のとおりです:両端キュー! 上と下からタスクを取得して実行できます。(両端のキューを維持します)
クラス ForkJoinPool の使用
フォーク参加タスク
ForkJoinの計算クラス:
/**
* 求和计算的任务
*
* 如何使用 ForkJoin?
* 1.ForkJoinPool 通过它来执行
* 2.计算任务 forkJoinPool.execute(ForkJoinTask<?> task)
* 3.计算类要继承ForkJoinTask
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
//临界值
private Long temp = 10000L;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
//计算方法
@Override
protected Long compute() {
if ((end - start) < temp) {
long sum = 0L;
for (Long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
//分支合并计算
Long middle = (start + end) / 2;//中间值
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
task1.fork(); //拆分任务,把线程任务压入线程队列
ForkJoinDemo task2 = new ForkJoinDemo(middle, end);
task2.fork(); //拆分任务,把线程任务压入线程队列
//结果汇总
return task1.join() + task2.join();
}
}
}
テストクラス:
/**
* 三六九等 三 六(ForkJoin) 九(Stream并行流)
*/
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//test1(); // 419
//test2();
test3();//234
}
//普通程序员
public static void test1() {
long sum = 0L;
long start = System.currentTimeMillis();
for (long i = 0L; i <= 10_0000_0000; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("sum = " + sum + " 时间:" + (end - start));
}
//会使用forkJoin
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum = " + sum + "时间:" + (end - start));
}
public static void test3() {
long start = System.currentTimeMillis();
//Stream并行流计算 []
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum = " + sum + " 时间:" + (end - start));
}
}
.Parallel().reduce(0, Long::sum) 並列ストリームを使用して計算し、効率を向上させます。(並列計算削減額)
15. 非同期コールバック
フューチャー デザインの本来の目的は、将来のイベントの結果をモデル化することです。
スレッドの非同期呼び出しでは通常、CompletableFuture クラスが使用されます。
(1) runAsync 戻り値のない非同期コールバック
//没有返回值的 runAsync 异步回调
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " runAsync => Void");
});
System.out.println("11111");
completableFuture.get(); //阻塞,获取执行结果
(2) 戻り値付きのsupplyAsync非同期コールバック
/**
* 类似异步调用:Ajax
*
* 异步调用:CompletableFuture
* 成功回调
* 失败回调
*/
public class Demo02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//有返回值的 supplyAsync 异步回调
//成功和失败回调
//返回的是错误信息
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " supplyAsync => Integer");
int i = 10 / 0;
return 1024;
});
//成功回调
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t=>" + t); //正常的返回结果
System.out.println("u=>" + u); //错误信息 java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
}).exceptionally((e -> {
//失败回调
System.out.println(e.getMessage());
return 233; // 可以获取到错误的返回结果
})).get());
}
}
16、JMM
揮発性についてのあなたの理解について話してください
volatile は、Java 仮想マシンによって提供される軽量の同期メカニズムです。
1. 視認性の確保
2. 原子性は保証されません
3. コマンドの並び替えの禁止
JMMとは
JMM: Java メモリ モデル、存在しないもの、概念! 約束!
JMM に関するいくつかの同期規則:
スレッドはワーキング
1. スレッドのロックを解除する前に、共有変数を直ちにメイン メモリにフラッシュ バックする必要があります。
2. スレッドがロックされる前に、メイン メモリの最新の値が作業メモリに読み込まれる必要があります。
3.施錠と解錠は同じロックです
8つの操作
- read (read) : メイン メモリ変数に作用し、変数の値をメイン メモリからスレッドの作業メモリに転送して、後続のロード アクションを使用できるようにします。
- load (load) : 作業メモリの変数に作用し、メインメモリ内の変数からの読み取り操作を作業メモリに置きます。
- use (use) : 作業メモリ内の変数に作用し、作業メモリ内の変数を実行エンジンに転送します。仮想マシンが変数を使用する必要がある値に遭遇すると、この命令が使用されます。
- Assign (assignment ): 作業メモリ内の変数に作用し、実行エンジンから受け取った値を作業メモリの変数コピーに置きます。
- Store (ストレージ) : メイン メモリ内の変数に作用し、後続の書き込みで使用するために変数の値を作業メモリからメイン メモリに転送します。
- write (書き込み) : メインメモリ内の変数に作用し、ストア操作によって作業メモリから取得した変数の値をメインメモリ内の変数に代入します。
- lock (lock) : メインメモリの変数に作用し、変数をスレッド排他的状態としてマークします。
- ロック解除 (ロック解除) : メイン メモリに作用する変数。ロック状態にある変数を解放します。解放された変数は他のスレッドによってロックできます。
JMM は、以下の 8 つの操作に対応する規制を設けています。
-
読み取りとロード、ストアと書き込みのいずれかの操作を単独で実行することはできません。つまり、read を使用する場合はロードする必要があり、store を使用する場合は write する必要があります。
-
スレッドは最新の代入操作を破棄することはできません。つまり、作業変数のデータが変更された後、メインメモリに通知する必要があります。
-
スレッドは、作業メモリからメインメモリに割り当てを行わずにデータを同期することはできません。
-
新しい変数はメイン メモリに作成する必要があり、作業メモリでは初期化されていない変数を直接使用することはできません。つまり、変数の use 操作と store 操作を実装する前に、assign 操作とload 操作を実行する必要があります。
-
変数をロックできるのは一度に 1 つのスレッドだけです。複数のロックの後、ロックを解除するには同じ回数のロック解除を実行する必要があります
-
変数に対してロック操作が実行されると、すべての作業メモリ内のこの変数の値がクリアされます。実行エンジンがこの変数を使用する前に、初期化された変数の値を再ロードまたは割り当てる必要があります。
-
変数がロックされていない場合、ロックを解除することはできません。また、別のスレッドによってロックされている変数のロックを解除することもできません。
-
変数のロックを解除する前に、変数をメイン メモリに同期して戻す必要があります
問題: プログラム A は、メイン メモリ内の値が変更されたことを知りません。
17、揮発性
1) 可視性の保証
public class JMMDemo {
// 如果不加volatile 程序会死循环
// 加了volatile是可以保证可见性的,volatile保证一旦数据被修改,其它线程立马能够感知到
private volatile static int num = 0;
public static void main(String[] args) { // main 线程
new Thread(()->{ // 线程1 不知道主内存中的值发生了变化
while (num == 0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
2) アトミック性は保証されません
原子性: 不可分
スレッド A がタスクを実行しているときは、中断したり分割したりすることはできず、同時に成功するか、同時に失敗します。
/**
* 不保证原子性
*/
public class VDemo2 {
// volatile 不保证原子性
private volatile static int num = 0;
public static void add() {
num++;
}
public static void main(String[] args) {
// 20个线程,每个线程调用100次 理论值 2万
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
// main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
ロックと同期が追加されていない場合にアトミック性を確保するにはどうすればよいですか?
アトミック クラスを使用してアトミック性の問題を解決する
public class VDemo2 {
// volatile 不保证原子性
// 原子类的 Integer
private volatile static AtomicInteger num = new AtomicInteger();
public static void add() {
//num++; //不是原子性操作
num.getAndIncrement(); // +1 操作 底层是CAS保证的原子性
}
public static void main(String[] args) {
// 20个线程,每个线程调用100次 理论值 2万
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
// main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
アトミック クラスの最下層はオペレーティング システムに直接リンクされています。メモリ内の値を変更してください!
Unsafeクラスは非常に特殊な存在です
3) コマンドの並び替えの禁止
コマンドの再配置
命令の再配置とは何ですか?
私たちが作成したプログラムは、コンピューターが私たちが作成したとおりに実行されるわけではありません。
ソースコード –> コンパイラ最適化の再配置 –> 命令の並列性も再配置される可能性があります –> メモリシステムも再配置されます –> 実行
プロセッサは命令の再配置を実行するときに、データ間の依存関係を考慮します。
int x=1; //1
int y=2; //2
x=x+5; //3
y=x*x; //4
//我们期望的执行顺序是 1234 可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的
考えられる影響結果: 前提: abxy の 4 つの値はデフォルトですべて 0 です
糸A | 糸B |
---|---|
x=a | y=b |
b=1 | a=2 |
通常の結果: x = 0; y =0;
糸A | 糸B |
---|---|
b=1 | a=2 |
x=a | y=b |
考えられる奇妙な結果: x = 2; y = 1; (非常に低い確率)
volatile は命令の再配置を回避できます。
volatile にはメモリ バリアが追加され、このメモリ バリアはこのバリア内の命令の順序を保証できます。
メモリバリア: CPU 命令。効果:
1. 特定の操作の実行順序を保証します。
2. 特定の変数のメモリ可視性を保証できます (これらの機能を使用して、揮発性実装の可視性を保証できます)
要約:
volatile は可視性を保証できますが、アトミック性は保証できません。メモリバリアがあるため、命令の再配置を避けることが保証できます。
インタビュアー: では、この記憶障壁がどこで最も使われるか知っていますか? シングルトンパターン
18. シングルトンモードで完全に遊ぶ
ハングリースタイル、DCLレイジースタイル
1) お腹を空かせた中華風
/**
* 饿汉式单例
* 核心思想:构造器私有化
*/
public class Hungry {
// 可能浪费内存空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private static final Hungry HUNGRY = new Hungry();
private Hungry(){
}
public static Hungry getInstance(){
return HUNGRY;
}
}
2) DCL 遅延スタイル
/**
* 懒汉式单例
* 道高一尺,魔高一丈!
*/
public class LazyMan {
private volatile static LazyMan lazyMan;
private static boolean flag = false;
private LazyMan() {
synchronized (LazyMan.class) {
if (!flag) {
flag = true;
} else {
throw new RuntimeException("不要试图使用反射破坏异常!");
}
}
}
//双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance() {
if (null == lazyMan) {
synchronized (LazyMan.class) {
if (null == lazyMan) {
lazyMan = new LazyMan(); // 不是一个原子性操作
}
}
}
return lazyMan;
}
//不加 synchronized 多线程情况下,不一定是单例
public static void main(String[] args) throws Exception {
// for (int i = 0; i < 10; i++) {
// new Thread(() -> {
// LazyMan.getInstance();
// }).start();
// }
//反射!
//LazyMan instance = LazyMan.getInstance();
Field flag = LazyMan.class.getDeclaredField("flag");
flag.setAccessible(true);
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LazyMan instance = constructor.newInstance();
flag.set(instance,false);
LazyMan instance1 = constructor.newInstance();
System.out.println(instance == instance1);
}
/*
创建对象的步骤:
1.分配内存空间
2.执行构造方法,初始化对象
3.把这个对象指向这个空间
123
132 线程A
线程B // 此时lazyMan还没有完成构造
*/
}
3) 静的内部クラス
/**
* 静态内部类
*/
public class Holder {
private Holder(){
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
}
シングルトンはリフレクションのため安全ではありません
4) 列挙型クラス
/**
* enum 是什么? 本身也是一个Class类
*/
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
//EnumSingle instance2 = EnumSingle.INSTANCE;
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
EnumSingle instance2 = constructor.newInstance(); //java.lang.NoSuchMethodException: com.lkl.singleton.EnumSingle.
System.out.println(instance1);
System.out.println(instance2);
}
}
列挙型を使用すると、リフレクションが壊れるのを防ぐことができます
列挙型の最終的な逆コンパイルされたソース コード:
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
19. CAS についての深い理解
CASとは
大工場は最下層を研究しなければなりません!画期的!自己啓発、弱い基盤、地を揺るがす オペレーティング システム、コンピューター ネットワークの原則
public class CASDemo {
//CAS compareAndSet:比较并交换!
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2021);
//期望、更新
//public final boolean compareAndSet(int expect, int update)
//如果我期望的值达到了,就更新,否则,就不更新,CAS 是CPU的并发原语!
System.out.println(atomicInteger.compareAndSet(2021, 2022));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2022));
System.out.println(atomicInteger.get());
}
}
安全でないクラス
atomicInteger.getAndIncrement(); |
---|
CAS: 現在作業メモリにある値とメインメモリにある値を比較し、値が予期されるものであれば、操作を実行します。そうでない場合は、スピン ロックを使用してループを続けます。
欠点:
- ループには時間がかかります。
- 一度に保証できる共有変数の原子性は 1 つだけです。
- ABAの問題があるだろう
CAS:ABA(王子様用ジャコウネコ)
メインメモリ内のA=1
スレッド 1: 期待値は 1 ですが、2 になります。
スレッド 2: 2 つの操作:
- 期待値は1、3になります
- 3 であると予想されますが、1 になります
したがって、スレッド 1 の場合、A の値は依然として 1 であるため、問題が発生し、スレッド 1 はだまされてしまいます。スレッド 1 は、A の値が変更されたことを知りません。
public class CASDemo2 {
//CAS compareAndSet:比较并交换!
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2021);
//期望、更新
//public final boolean compareAndSet(int expect, int update)
//如果我期望的值达到了,就更新,否则,就不更新,CAS 是CPU的并发原语!
// ======================= 捣乱的线程 ==============================
System.out.println(atomicInteger.compareAndSet(2021, 2022));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2022, 2021));
System.out.println(atomicInteger.get());
// ======================= 捣乱的线程 ==============================
//======================== 期望的线程 ==============================
System.out.println(atomicInteger.compareAndSet(2021, 6666));
System.out.println(atomicInteger.get());
}
}
20. 原子参照 (AtomicReference)
ABA 問題を解決し、アトミック参照を導入してください。対応する考え:楽観的なロック!
バージョン番号によるアトミック操作!
public class CASDemo3 {
//CAS compareAndSet:比较并交换!
public static void main(String[] args) {
//AtomicStampedReference 泛型如果使用包装类,注意对象引用问题
//正常在业务操作,这里泛型都是一个个对象
AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(1, 1);
new Thread(() -> {
int stamp = stampedReference.getStamp(); //获得版本号
System.out.println(Thread.currentThread().getName() + " 1 -> " + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(stampedReference.compareAndSet(1, 2,
stampedReference.getStamp(), stampedReference.getStamp() + 1));
System.out.println(Thread.currentThread().getName() + " 2 -> " + stampedReference.getStamp());
System.out.println(stampedReference.compareAndSet(2, 1,
stampedReference.getStamp(), stampedReference.getStamp() + 1));
System.out.println(Thread.currentThread().getName() + " 3 -> " + stampedReference.getStamp());
}, "a").start();
new Thread(() -> {
int stamp = stampedReference.getStamp(); //获得版本号
System.out.println(Thread.currentThread().getName() + " 1 -> " + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(stampedReference.compareAndSet(1, 6,
stampedReference.getStamp(), stampedReference.getStamp() + 1));
System.out.println(Thread.currentThread().getName() + " 2 -> " + stampedReference.getStamp());
}, "b").start();
try {
TimeUnit.SECONDS.sleep(3);
System.out.println("**************");
System.out.println(stampedReference.getReference());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
知らせ:
Integer はオブジェクト キャッシュ メカニズムを使用します。デフォルトの範囲は -128 ~ 127 です。valueOf はキャッシュを使用し、new は確実に新しいオブジェクトを作成し、新しいメモリを割り当てるため、new の代わりに静的ファクトリ メソッド valueOf を使用してオブジェクト インスタンスを取得することをお勧めします。空
整数ラッパー |
---|
21. 各種ロックの理解
1. 公平なロックと不公平なロック
公平なロック: 非常に公平、キュージャンプなし、早い者勝ちです! (効率が低い可能性があります)
不公平なロック: 非常に不公平です。キューにジャンプすることができます (デフォルトは不公平であり、効率はより高くなります)
public ReentrantLock() {
sync = new NonfairSync();
}
公平な状態を変更できるパラメータを持つコンストラクター
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2. リエントラントロック
リエントラント ロック (再帰的ロック): 外側のロックを取得した後、自動的に内側のロックを取得します (同期 [暗黙的] とロック [明示的] は両方とも再入可能ロックです)
synchronized版
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName() + " sms");
call();
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName() + " call");
}
}
ロックバージョン
public class Demo02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone2{
private final Lock lock = new ReentrantLock();
public void sms(){
lock.lock(); //Lock锁必须配对,有加锁就必须有解锁! 否则就会死锁!
try {
System.out.println(Thread.currentThread().getName() + " sms");
call();
} finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " call");
} finally {
lock.unlock();
}
}
}
3. スピンロック
スピンロック
カスタムスピンロック:
public class spinlock {
// int 0
// Thread null
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + " ==> myLock");
//自旋锁
while (!atomicReference.compareAndSet(null,thread)){
}
}
//解锁
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + " ==> myUnLock");
atomicReference.compareAndSet(thread,null);
}
}
カスタム スピンロックをテストします。
public class TestSpinLock {
public static void main(String[] args) {
// Lock lock = new ReentrantLock();
// lock.lock();
// lock.unlock();
// 底层使用的自旋锁CAS
spinlock spinlock = new spinlock();
new Thread(() -> {
spinlock.myLock();
try {
TimeUnit.SECONDS.sleep(4);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlock.myUnLock();
}
}, "T1").start();
new Thread(() -> {
spinlock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlock.myUnLock();
}
}, "T2").start();
}
}
出力結果 |
---|
結果分析:
2 つのスレッドが共同してロックを操作し、最初にロックを取得した方が最初に実行します。最初に T1 スレッドがロックを取得してロックし、続いて T2 スレッドが最初の行を入力し、次に 2 行目を出力します。T1 スレッドは 4 秒後にロックを解放し、次に T2 スレッドがロックを取得してロックします。を押して操作し、3 秒後にロックを解除します。したがって、最初に 1 行目と 2 行目を入力し、4 秒後に 3 行目を出力し、3 秒後に 4 行目を出力します。
4. デッドロック
デッドロックとは何ですか? 実行処理中、複数のプロセスはリソースの競合により相互に待機するため、外部からの干渉がなければ実行を続けることができません。
デッドロックは、2 つのスレッドが独自のロックを保持したままお互いのロックを取得しようとすると発生します。
デッドロックテスト、デッドロックを解消する方法!
/**
* 死锁样例
*/
public class DeadLockDemo {
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (a) {
System.out.println(Thread.currentThread().getName() + " 获取到锁a,试图获取锁b");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println("获取到锁b");
}
}
}, "A").start();
new Thread(() -> {
synchronized (b) {
System.out.println(Thread.currentThread().getName() + " 获取到锁b,试图获取锁a");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println("获取到锁a");
}
}
}, "B").start();
}
}
デッドロックの原因:
1 つ目: システム リソースの不足
2 番目: 実行中のプロセスの進行順序が適切ではない
3番目: リソースの誤った割り当て
デッドロックを特定して問題を解決するにはどうすればよいでしょうか?
1. jps を使用して、jdk の bin ディレクトリの下にあるプロセス番号を見つけます。jps があります。
注文:jps -l
2.jstack
プロセスのプロセス番号を使用してデッドロック情報を見つけます(jstack は、jvm に付属のスタック追跡ツールです)
注文:jstack 进程号
面接、働けます!トラブルシューティングの方法は?
1.ログ90%
2. スタック情報 10%