同期ロックには 2 種類あります
- クラスオブジェクト
- クラスのインスタンス
最初のタイプ: オブジェクトのロック。次の 2 つの方法があります。
// 方法一:synchronized 修饰static方法
public static synchronized void test(){
System.out.println(Thread.currentThread().getName()+" start ");
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end ");
}
// 方法二:synchronized锁class对象
public void test2(){
synchronized (getClass()) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" run ");
}
}
public static void main(String[] args) {
TestThread10 t = new TestThread10();
new Thread(()->TestThread10.test(),"线程 1 ").start();
new Thread(()->t.test2(),"线程 2 ").start();
}
結果は次のとおりです。
スレッド 1 では、最初にスレッド 1 を開始し、スレッド 1 が終了するまで待ってからスレッド 2 を実行します。
ロック クラスはクラス オブジェクトにロックを追加するものとして理解でき、ロックされたすべてのメソッドは実行前に前のロックが解放されるまで待つ必要があります。
2 番目のタイプ: クラスのインスタンス
スレッドの同期は、次のように同じインスタンス オブジェクトがロックされている場合にのみ実現できます。
public void test2(){
synchronized (this) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" run ");
}
}
同期例外キャプチャ
synchronizedを使用する場合、例外処理を行わないと自動的にロックが解除されますが、手動で
ロックし、自動的にロックを解除する仕組みとなっています。例外を処理する必要がある例を見てみましょう。処理しないと、以下のコードのスレッド 1 が解放されます。
private Integer c = 0;
@Override
public void run() {
count();
}
private synchronized void count(){
System.out.println(Thread.currentThread().getName()+" start。。。");
while (true) {
System.out.println(Thread.currentThread().getName()+" count="+c++);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (c == 5) {
int i = 1/0;
}
}
}
public static void main(String[] args) {
TestThread3 t = new TestThread3();
Thread t1 = new Thread(t, "线程 1");
Thread t2 = new Thread(t, "线程 2");
t1.start();
t2.start();
}
メインスレッドと子スレッド
スレッドはユーザー スレッドとデーモン スレッドに分けられます。
main メソッドは実際にはメイン スレッドであり、オペレーティング システムが java.exe を開始した後、プロセスを開始し、そのプロセスがメイン スレッドを開始し、メイン スレッドが他のスレッドを開始します。
- デーモンスレッド:メインスレッドと同時に終了(メインスレッドが終了し、デーモンスレッドも終了)
- ユーザー スレッド (非デーモン スレッド): ただし、すべてのユーザー スレッドが終了し、メイン スレッドも終了します。
t2.setDaemon(true);
t2.start();
揮発性物質の役割
このブロガーのブログをご覧ください: Java の Volatile キーワードの詳細な説明 - Zheng Bin ブログ - Blog Garden (cnblogs.com)
これには 2 つの機能があります。
- スレッド間の可視性
- 命令の並べ替えを防止する
知らせ:
可視性はアトミック性を意味するものではなく、他のスレッドが最新の値をリアルタイムで表示できるようにするだけであり、他の操作は保証されません。
命令の並べ替え、これは少し難解ですが、一時的に無視しても問題ありません
通知はランダムに開始された待機スレッドの 1 つです
Notice はランダムに開始される待機スレッドの 1 つであり、スレッドの優先順位とは関係がありません。また、wait メソッドと Notify メソッドは同じロック内で使用する必要があります。もう 1 つのポイントは、lock.wait は、ロックを許可するユーザーにロックを放棄することです。ただし、スレッドは、他のスレッドの実行が終了した後で lock.notify() を呼び出し、待機中のスレッドをウェイクアップしますが、現在のロック内のコードの実行が終了していないため、ロックは解放されません。
簡単なシーンシミュレーション:
put メソッドと get メソッド、および 2 つのプロデューサー スレッドと 10 のコンシューマー スレッドからの呼び出しのブロックをサポートできる getCount メソッドを備えた固定容量の同期コンテナー。
public class TestThread8 {
private final LinkedList list = new LinkedList();
private final int MAX = 10;
private int count = 0;
public synchronized void put(Object o) {
while (list.size() == MAX) {
try {
// 在这里等待;的那个调用notify时会从这里继续执行
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(o);
count++;
// 启动所有线程,包括生产者,随机的
this.notifyAll();
}
public synchronized void get() {
while (list.size() == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.removeLast();
count--;
this.notifyAll();
}
public int getCount() {
return list.size();
}
public static void main(String[] args) {
TestThread8 t = new TestThread8();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
int j = 0;
while (true) {
t.put(Thread.currentThread().getName() + " put " + t.getCount());
System.out.println(Thread.currentThread().getName() + " put " + t.getCount());
}
}).start();
}
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
t.get();
System.out.println(Thread.currentThread().getName() + " get " + t.getCount());
}
}).start();
}
}
}