1.基本的な考え方
プロセス:特定のデータセット上でコンピューター内で実行されているプログラムのアクティビティであり、リソースの割り当てとスケジューリングのためのシステムの基本単位であり、オペレーティングシステム構造の基盤です。現代のスレッド指向のコンピュータアーキテクチャでは、プロセスはスレッドのコンテナです。プログラムは、命令、データ、およびその編成の説明であり、プロセスはプログラムのエンティティです。これは、特定のデータセット、リソースの割り当てとスケジューリングのためのシステムの基本単位、およびオペレーティングシステム構造の基盤上でのコンピューター内のプログラムの実行アクティビティです。プログラムは、命令、データ、およびその編成の説明であり、プロセスはプログラムのエンティティです。
スレッド: オペレーティングシステムが操作のスケジューリングを実行できる最小単位です。これはプロセスに含まれ、プロセスの実際の操作単位です。スレッドとは、プロセス内の単一の順次制御フローを指します。プロセス内で複数のスレッドを同時に実行でき、各スレッドは異なるタスクを並行して実行します。
スレッドを作成するいくつかの方法
public class T02_HowToCreateThread {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello MyThread!");
}
}
static class MyRun implements Runnable {
@Override
public void run() {
System.out.println("Hello MyRun!");
}
}
static class MyCall implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("Hello MyCall");
return "success";
}
}
public static void main(String[] args) {
// 第一种
new MyThread().start();
// 第二种
new Thread(new MyRun()).start();
// 第三种 Lambda
new Thread(() -> {
System.out.println("Hello Lambda!");
}).start();
// 第四种
Thread t = new Thread(new FutureTask<String>(new MyCall()));
t.start();
// 第五种:线程池。
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> {
System.out.println("Hello ThreadPool");
});
service.shutdown();
}
}
//请你告诉我启动线程的三种方式 1:Thread 2: Runnable 3:Executors.newCachedThrad(实际也是用的前两种)
スレッドの一般的に使用される方法:
- join():投稿を実行しているスレッドなど、他のスレッドがブロックされているスレッドに参加するための呼び出し。その後、他のスレッドを実行します。InterruptedExceptionは、外部割り込みによって生成される場合があります。
- sleep():現在実行中のスレッドを指定されたミリ秒数以内にスリープ(実行の一時停止)します。スリープ状態のスレッドはブロッキング状態になります。
- yield():yieldメソッドを呼び出すスレッドは、他のスレッドを最初に丁寧に実行させます。(他のスレッドが最初に実行される可能性が高く、CPUを解放すると、単独で実行される可能性が低くなります)
- Thread.currentThead():現在のスレッドオブジェクトを取得します
- getPriority():現在のスレッドの優先度を取得します
- setPriority():現在のスレッドの優先度を設定します
- 注:スレッドの優先度は高く、CPUによってスケジュールされる可能性は高くなりますが、実行されることを意味するわけではなく、優先度の低いスレッドを実行する可能性はわずかです。
- isAlive():スレッドがアクティブかどうかを判別します(スレッドがstartを呼び出した後、アクティブになります)
- 割り込み():割り込みスレッド
- wait():スレッドを待機させてブロック状態にします。このメソッドは、同期メソッドまたは同期コードブロックでのみ使用する必要があります
- notify():現在のスレッドをウェイクアップし、実行状態に入ります。このメソッドは、同期メソッドまたは同期コードブロックでのみ使用する必要があります
- notifyAll():待機中のすべてのスレッドをウェイクアップします。このメソッドは、同期メソッドまたは同期コードブロックでのみ使用する必要があります
共通スレッドステータス:
- New(新しい状態):new Thread(r)などのnew演算子を使用して新しいスレッドが作成された場合、スレッドはまだ実行を開始しておらず、.start()を呼び出していません。これは、そのステータスが新しいことを意味します。
- Runnable(runnable):start()メソッドが呼び出されると、スレッドスケジューラによって実行されます。つまり、オペレーティングシステムが実行され、スレッドは実行可能状態になります。
- 準備完了状態:準備完了状態とは、CPUの待機キューにスローしてキューに入れ、CPUの実行を待機することを意味します。
- 実行状態:実行するために実際にCPUにスローされると、実行状態と呼ばれます(yiledが呼び出されると、実行状態から準備完了状態になり、スレッドスケジューラが実行を選択すると、準備完了状態になります。状態から実行状態)
- 終了状態:スレッドが正常に実行されると、スレッドは開始されます(注:Teamedが終了した後は、新しい状態に戻ってstartを呼び出すことはできません。終了すると終了します)
- TimedWaiting:時間に応じて待機し、時間が経過すると戻る、Thread.sleep(time)、o.wait(time)、t.join(time)、LockSupport.parkNanos()、LockSupport.parkUntil()これらそれはすべて待つ時間の方法についてです。
- 、LockSUWaiting waiting:実行中にo.wait()、t.join()、LockSupport.park()が呼び出されて待機状態になり、notify()、notifyAll()、LockSupport.unpark()が呼び出されてから実行ステータスに戻る
- ブロック:コードブロックを同期する場合、ロックが取得されないと状態がブロックされ、ロックが取得されると状態の準備が整います。
上記の状態はすべてJVMによって管理されます。これは、JVMもオペレーティングシステムによって管理されるため、どちらがオペレーティングシステムであるかとJVMであるかを区別できないためです。JVMはオペレーティングシステムで実行される通常のプログラムです。
同期
- 原子性を保証し、可視性も保証します
- 再入可能
オブジェクトをロックします。
public class T {
private int count = 10;
private Object o = new Object();
public void m() {
synchronized(o) { //任何线程要执行下面的代码,必须先拿到o的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
同期(これ):上記のように毎回オブジェクトを更新する必要があることを考えると、現在のオブジェクトをロックするだけで十分です。
public class T {
private int count = 10;
public void m() {
synchronized(this) { //任何线程要执行下面的代码,必须先拿到this的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
public static void main(String[] args) {
T t = new T();
t.m();
}
}
または、次のように書くこともできます:(上記と同じ)
public class T {
private int count = 10;
public synchronized void m() { //等同于在方法的代码执行时要synchronized(this)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
静的メソッド:このオブジェクトはありません。このメソッドを実行するためにオブジェクトを作成する必要はありませんが、これに同期が追加された場合、ここでの同期(T.class)は同期(T.class)によってロックされます。 )はクラスTのオブジェクトです
public class T {
private static int count = 10;
public synchronized static void m() { //这里等同于synchronized(FineCoarseLock.class)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void mm() {
synchronized(T.class) { //考虑一下这里写synchronized(this)是否可以?
count --;
}
}
public static void main(String[] args) {
m();
mm();
}
}
補足:メモリへの同じClassLoaderスペースのクラスロードはシングルトンですが、異なるクラスローダーはそうではなく、異なるクラスローダーは相互にアクセスできないため、アクセスはシングルトンです。
Synchronizedは、原子性と可視性の両方を保証できます(同じロックが渡され、シングルトンコードがこの条件を満たさない場合)
public class T implements Runnable {
private /*volatile*/ int count = 10;
public synchronized void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
for(int i=0; i<5; i++) {
T t = new T();
new Thread(t, "THREAD" + i).start();
}
}
}
同期コードと非同期コードを同時に呼び出すことはできますか?
public class T {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + " m1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m1 end");
}
public void m2() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m2 ");
}
public static void main(String[] args) {
T t = new T();
/*new Thread(()->t.m1(), "t1").start();
new Thread(()->t.m2(), "t2").start();*/
new Thread(t::m1, "t1").start();
new Thread(t::m2, "t2").start();
/*
//1.8之前的写法
new Thread(new Runnable() {
@Override
public void run() {
t.m1();
}
});
*/
}
}
t1 m1 start...
t2 m2
t1 m1 end
ビジネスでダーティ読み取りが許可されている場合は、読み取りプロセスをロックする必要はありません。
public class Account {
String name;
double balance;
public synchronized void set(String name, double balance) {
this.name = name;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public /*synchronized*/ double getBalance(String name) {
return this.balance;
}
public static void main(String[] args) {
Account a = new Account();
new Thread(()->a.set("zhangsan", 100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
}
}
同期されたリエントラント
同期メソッドは別の同期メソッドを呼び出すことができます。スレッドはオブジェクトのロックを所有でき、再度適用されたときにオブジェクトのロックを取得します。つまり、ロックの同期取得は再入可能です。
public class T {
synchronized void m1() {
System.out.println("m1 start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
System.out.println("m1 end");
}
synchronized void m2() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2");
}
public static void main(String[] args) {
new T().m1();
}
}
親クラスがサブクラスを呼び出し、親クラスが同期され、super.mを呼び出すときにサブクラスが再入可能である必要があるという概念。そうしないと、問題が発生します(親クラスの呼び出しは同じロックです)。いわゆるリエントリーロックとは、ロックを取得した後もヨークをシャックリングし続けることを意味します。複数のロックを追加しますが、同じオブジェクトがロックされます。1つのロックに移動すると、1つ減少します。
public class T {
synchronized void m() {
System.out.println("m start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m end");
}
public static void main(String[] args) {
new TT().m();
}
}
class TT extends T {
@Override
synchronized void m() {
System.out.println("child m start");
super.m();
System.out.println("child m end");
}
}
例外ロック:プログラムの実行中に例外が発生した場合、デフォルトでロックが解除されますので、並行処理の過程で例外に注意してください。そうしないと、不整合が発生する可能性があります。
例:Webアプリの処理中に、複数のサーブレットスレッドが同じリソースにアクセスします。例外処理が適切でない場合は、最初のスレッドで例外をスローすると、他のスレッドが同期コード領域に入ります。例外が発生しました。
public class T {
int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName() + " start");
while(true) {
count ++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count == 5) {
int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续
System.out.println(i);
}
}
}
public static void main(String[] args) {
T t = new T();
Runnable r = new Runnable() {
@Override
public void run() {
t.m();
}
};
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
同期の基本原則。
- jdkの初期の頃、同期の基本的な実装は非常に重要であり、ロックを適用するためにオペレーティングシステムにアクセスする必要があり、同期効率が非常に低くなりました。
- 改善後、ロックアップグレードの概念があります(参照できます:はい、私はトイレのディレクターです!(2))。同期を使用する場合、HotSpotが達成しなければならないことは実際に最初に訪問するものですsync(オブジェクト)などのスレッドをロックします。それが来ると、このオブジェクトの頭にこのスレッドを記録するためのマークが付けられます。最初のスレッドのみがアクセスする場合、実際にはこのオブジェクトにロックされていません。内部で実装されている場合は、このスレッドのIDのみが記録されます(バイアスロック)。バイアスされたロックにスレッド競合がある場合は、スピンロックにアップグレードされます。コンセプトは、whileループを使用してここでスピンする(スピンロックにアップグレードする)ことであり、jdk1.6では10回にアップグレードされると規定されています。再びヘビー級ロック。、ヘビー級ロックは、リソースを申請するためにオペレーティングシステムに移動することです。これは、ロックエスカレーションのプロセスです。(ロックのアップグレードプロセスは元に戻せません)