我々は、適切な堅牢かつ効率的な並列システムを構築する方法を慎重に検討してきました。
プロセスとスレッド
プロセス(プロセス):コンピュータプログラムは、データの収集に特定のアクティビティで実行され、リソースの割り当てとスケジューリングシステムの基本的な単位は、基礎となるオペレーティング・システム・アーキテクチャです。
プロセスは、コンテナのスレッドです。プログラム命令、データおよび組織の他の形態は、プログラム・エンティティの処理について説明します。プロセスは、スレッドの数を収容することができます。
プロセスとスレッドの関係:スレッドは軽量プロセスでは、プログラム実行の最小単位です。なぜ我々は、複数のスレッドの代わりに、複数のプロセスを使用していますか?プロセスよりもはるかに少ないコストをスケジュールする、スレッドの切り替え以来、私たちは、代わりに複数のプロセスの複数のスレッドを使用しています。
ライフサイクルのスレッド
すべての状態のスレッドは、国家列挙のスレッドで定義されています。
public enum State{
NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;
}
NEW状態では、作成したばかりのスレッドは、このスレッドが開始されていないことを示しています。start()メソッドが呼び出されると、スレッドが実行を開始します。スレッドの実行が、RUNABLE状態で、準備スレッドするために必要なすべてのリソースを表す場合。
スレッドの実装プロセスで遭遇する場合synchronized
同期ブロックロックが要求を得られるまで、ブロックされたBLOCKED入力し、その後、スレッドは中断されるであろう。
waiting
そして、time_waiting
待機状態を表明して、それらの間の差はwaiting
、待機時間制限なしに入りますが、time_waiting
それは期間限定の待機状態になります。一般的には、待機中のスレッドがいくつかの特別なイベントを待っています。()メソッドスレッドが通知()メソッドを待っている、例えば、ウェイトによって待って、join()メソッドによって待っている間、ターゲット・スレッドを終了するスレッドを待機します。所望のイベント一旦スレッドに再度実行されるまで、runnable
状態。スレッドが終了すると、その後、終了を示す終了状態に入ります。
注:新しい状態からスタートした後、スレッドはもはや実行可能状態に戻すことはできません戻って終了したスレッドによって処罰NEW状態、共感、に行くことができません。
基本的な操作のスレッド:初期スレッド
提供されるいくつかのAPIのJavaスレッド操作でこのセクションの外観。
新しいスレッド
新しいスレッドは、非常にシンプルである一種のスレッドをカスタマイズするために、オーバーロードされたrun()メソッドの継承スレッドを使用することができ、ここでは匿名の内部クラスですが、また、オーバーロードされたrun()メソッド:
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("Hello, I am t1");
}
};
t1.start();
スタート()、スレッドthread後、run()メソッド、start()メソッドは、新しいスレッドを作成し、このスレッドの実行run()メソッドを聞かせています。
差()とt1.run()の二つの方法のt1.start:とコードの真上に実行呼び出して比較()メソッドもコンパイルし、新しいスレッドを作成できませんが、現在のスレッドで実行()を呼び出し方法は、(このオープン新しいスレッドをしようとしないでください、それが現在のスレッドのみ、シリアルコード実行の実行()内となります)。
Thread t1 = new Thread();
t1.run();
第二は、同じ動作を達成するために、Runnableインタフェースを使用することです。このアプローチは、単一継承のJavaマルチスレッドの欠点に対処します。
public class CreateThread implements Runnable {
@Override
public void run() {
System.out.println("Hi!I am Runnable");
}
public static void main(String args[]) {
Thread thread = new Thread(new CreateThread());
thread.start();
}
}
スレッドを終了
一般に、スレッドを手動でシャットダウンすることなく、最終的に完成します。特殊な状況、手動でシャットダウンする必要があります。
閉じ停止使用
あなたはストップ近くを使用することができますが、お勧めしませんし、なぜですか?その理由は、半分のスレッドが終了するまで実行するように強制停止()、あまりにも暴力的であり、いくつかのデータの不整合が発生することがあります。例えば:
记录1:ID=1,name=小明
记录2:ID=2,name=小王
上記のデータベースレコードが、これはこのような状況が発生する単一のスレッド、単一のマルチスレッドで発生しません、1、2又は記録保持、データまたは命令が損傷を受けるのいずれかに格納されています。
スレッドの終了時にはThread.stop()メソッドは、スレッドを直接終了し、直ちにスレッドが保持しているロックを解放します。この時点で、スレッドは半分を書いて終了します。ロックが解除されているので、ロック・スレッドを待つも、このデータの不整合を読む読み取ることができます。図は次のとおりです。
アナログコード:
public class StopThreadUnsafe {
public static User user = new User();
public static class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User() {
id = 0;
name = "0";
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}
public static class ChangeObjectThread extends Thread {
public void run() {
while (true) {
synchronized (user) {
int v = (int) (System.currentTimeMillis() / 1000);
user.setId(v);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
user.setName(v + "");
}
Thread.yield();
}
}
}
public static class ReadObjectThread extends Thread {
public void run() {
while (true) {
synchronized (user) {
if (user.getId() != Integer.parseInt(user.getName())) {
System.out.println(user.toString());
}
}
Thread.yield();
}
}
}
public static void main(String args[]) throws InterruptedException {
new ReadObjectThread().start();
while (true) {
Thread thread = new ChangeObjectThread();
thread.start();
Thread.sleep(150);
thread.stop();
}
}
}
上記の手順は、出力値idと名前が同じである必要がありますが、次のエラーの出力データは、このエラーはエラーではない、それを見つけるのは困難です。
User{name='1565947644', id=1565947645}
User{name='1565947644', id=1565947645}
上記の問題を解決するには?あなたは私たち自身でスレッドを抜く際に決定する必要があります。依然として、単にスレッドChangeObjectThread stopMe()を追加示してこの例を使用:
public static class ChangeObjectThread extends Thread {
volatile boolean stopme = false;
public void stopMe(){
stopme = true;
}
public void run() {
while (true) {
//手动停止线程
if (stopme){
System.out.println("exit by stop me");
break;
}
synchronized (user) {
int v = (int) (System.currentTimeMillis() / 1000);
user.setId(v);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
user.setName(v + "");
}
Thread.yield();
}
}
}
スレッドの割り込み
ターゲットスレッドがスレッドしていること)(中断及び停止することができますスレッドがスレッドが終了するとして機能しますが、それはすぐに撤退しませんが、通知を送信し、あなたはそれを終了したい誰か!ターゲットスレッドの裁量を終了するか否かの。
三つの方法およびスレッド中断:
public void interrupt() //中断线程
public boolean Thread.isInterrupted() //判断是否被中断
public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态
手動でロジックを追加しない場合でも、割り込みスレッド、割り込み処理、割り込みがどんな役割を果たしています。
public class T1 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(){
@Override
public void run() {
while(true){
if (Thread.currentThread().isInterrupted()){
System.out.println("Interruted!");
break;
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Interrupted when Sleep");
Thread.currentThread().interrupt(); //1
}
Thread.yield();
}
}
};
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
あなたが削除した場合、上記の例のコードが実行されるメソッドのThread.sleep(2000)は、その後、プログラムが見え、stopmeは前に非常に似ていますが、より強力な中断です。同様のループの本体は、待機する場合は()またはスリープ()などの操作は、割り込みのみで識別することができます。
割り込みがスリープ状態にスレッド中に発生した場合、それは例外:InterruptedException割り込み例外をスローし、割り込みフラグをクリアします。コード例では、割り込みフラグをリセットする(割り込みフラグは、この時点でクリアされている)例外1で撮影、それが割り込みif文の次のサイクルに再循環されます。
(待機を)待ってから、通知(通知)
public final void wait() throws InterruptedException
public final native void notify()
コールは、オブジェクトインスタンス()メソッドを待機する場合、現在のスレッドがこのオブジェクトで待機します。他のスレッドがこれまでにobj.notify呼び出すまで待機します。明らかに、この主題は、複数のスレッド間の通信の効果的な手段となっています。
どのように動作する()とnotiry()を待つのか?
スレッドははObject.wait()を呼び出した場合、それは待っているキューオブジェクトのオブジェクトを入力します。object.notify()が呼び出されると、それがランダムに目を覚ますためにスレッドを選択し、待ちキューからになります。選択は完全にランダムです。object.notifyAllは()これは、すべての待機中のスレッドがウェイクアップ待ち時間をキューイングし、ランダムではないでしょう。
Object.wait()メソッドは、単にコールではありません(待機かどうか、対応synchronzied計算書に含まれていなければならない)、または(通知)監視対象のオブジェクトを取得する必要があります。次の図は、待機()と通知()ワークフローを示しています。
次のコードは、単に待つ()および()通知に使用され、T1は直ちにオブジェクトのロックを解放し、はObject.wait()メソッドを実行します。この時点でオブジェクトが2 object.notify()メソッドで実行さT2を捕捉するロックを待っているが、この時点で、およびはObject.wait()メソッド、すぐに解放されていないが、唯一のコードのリリース後に同期ブロックを実行します。T2のリリース後、T1がT2には、次の手順を実行し、奪還しました。
public class SimpleWN {
final static Object object = new Object();
public static class T1 extends Thread{
public void run()
{
synchronized (object) {
System.out.println(System.currentTimeMillis()+":T1 start! ");
try {
System.out.println(System.currentTimeMillis()+":T1 wait for object ");
object.wait(); //1 wait()后,马上释放对象锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis()+":T1 end!");
}
}
}
public static class T2 extends Thread{
public void run()
{
synchronized (object) {
System.out.println(System.currentTimeMillis()+":T2 start! notify one thread");
object.notify(); //2 notify()后,没有马上释放对象锁,而是执行完synchronized块的代码后释放
System.out.println(System.currentTimeMillis()+":T2 end!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println(System.currentTimeMillis()+":T2 after sleep!");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println(System.currentTimeMillis()+":T2 after synchronized!");
}
}
public static void main(String[] args) {
Thread t1 = new T1() ;
Thread t2 = new T2() ;
// Thread t1_1 = new T1() ;
// t1_1.start();
t1.start();
t2.start();
}
} /**
1566280793124:T1 start!
1566280793125:T1 wait for object
1566280793127:T2 start! notify one thread
1566280793127:T2 end!
1566280796127:T2 after sleep!
1566280796128:T1 end!
1566280797127:T2 after synchronized!
*/
Object.wait()
そして、Thread.sleep()
の違い:はObject.wait()とのThread.sleep()メソッドは、スレッドがいくつかの時間を待つできるようにすることができます。外()起こされる可能待機に加えて、他の主要な違いは、ロックを解除します待ち()メソッドで、のThread.sleepありません。
サスペンド(一時停止)と(履歴書)のスレッドを継続
スレッドを中断した後、続行するために履歴書を()、待たなければなりません。
この方法は、もはや使用することは推奨されません。理由があるため、スレッドが中断され、すべてのロック・リソースを解放しません原因と同時に()一時停止()が放出された履歴書まで待つ推奨されません。予想外での履歴書()操作は()以前に中断した場合、それは恒久的に中断につながる可能性があります。。この時点で、他のスレッドは、それがロックになるために必要なアクセスしたい、それが関与します。あるいはシステム全体が正しく機能していません。
public class BadSuspend {
public static Object u = new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name){
super.setName(name);
}
@Override
public void run() {
synchronized (u) {
System.out.println("in "+getName());
Thread.currentThread().suspend(); //1
}
}
}
public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(100);
t2.start();
t1.resume();
System.out.println("t1 resume!");
t2.resume();
System.out.println("t2 resume!");
t1.join();
t2.join();
}
}/**
in t1
t1 resume!
t2 resume!
in t2
*/
上記の手順では、1(後)t1.startスレッドゴーは、オブジェクトのロックを解除しません。この時点で中断しています。t2.start(後)、T2 t1は、オブジェクトを解放するロックを待機する必要があります。ロック待ちプロセスにおいてt2において、t2.resumeは()(印刷結果を見ることができる)が発生し、その後だけT2ないThread.suspend()を生じました。この場合、T2が完全に停止されます。
あなたはより信頼性の高い、サスペンド()関数が必要な場合、どのようにそれを行うために、その?あなたは)((一時停止)と再開を達成するために、アプリケーション・レベルで、wait()を使用して)(通知することができます:
public class GoodSuspend {
public static Object u = new Object();
public static class ChangeObjectThread extends Thread {
// 标记变量,表示当前线程是否被挂起
volatile boolean suspendme = false; //1
// 挂起线程
public void suspendMe(){
suspendme = true;
}
// 继续执行线程
public void resumeMe(){
suspendme = false;
synchronized (this){
notify();
}
}
@Override
public void run(){
while (true){
synchronized (this){ //2
while (suspendme){
try {
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
synchronized (u){
System.out.println("in ChangeObjectThread");
}
Thread.yield();
}
}
}
public static class ReadObjectThread extends Thread{
@Override
public void run(){
while (true){
synchronized (u){
System.out.println("in ReadObjectThread");
}
Thread.yield();
}
}
}
public static void main(String[] args) throws InterruptedException {
ChangeObjectThread t1 = new ChangeObjectThread();
ReadObjectThread t2 = new ReadObjectThread();
t1.start();
t2.start();
Thread.sleep(1000);
t1.suspendMe();
System.out.println("suspend t1 2 sec");
Thread.sleep(2000);
System.out.println("resume t1");
t1.resumeMe();
}
}
1において、所定のマーカーsuspendme、suspendMe(()メソッドは、(待機を実行することにより実現)ハング)とresumeMeを(増加させながら現在のスレッドが、中断されているか否かを示す)(実行を介して継続通知()通知そして自身がロックされた時にハングタグ)、注2をクリアします。
スレッドの終わり(参加)と謙虚(歩留まり)を待ち
加わります
マルチスレッドでは、スレッドを入力して1つ以上のスレッドの追加の出力に非常に依存することができる、この時点では、このスレッドは、依存スレッドを待機する必要があります終了し、先に進むことができ、提供するJDK join()
これを達成するために。二つの方法の参加があります。
public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
最初の(結合)ターゲットスレッドが終了するまで現在のスレッドをブロックし、無限待機を示します。時間が与えられた目標スレッドがまだ実装がダウンしていきます関わらず、現在のスレッドの、実行されている超えた場合第二の方法は、最大待機時間を与えます。
自然の参加は、()現在のオブジェクトインスタンスのスレッド上で呼び出しスレッドはwait()を可能にすることです。ここではJDKスニペットの心臓(参加)の実装は次のとおりです。
while(isAlive()){
wait(0);
}
これは、呼び出し元のスレッドが現在のスレッドオブジェクトに待つことができます。スレッドの実行が完了すると、コールのnotifyAll()は、すべての待機中のスレッドの続行を通知する前に、起動されます。
Thread.yield()
public static native void yield();
これは、現在のスレッドのCPUを行います静的メソッドです。CPUを聞かせた後に、ではないかもしれない、再びかどうかを代入すると、CPUリソースの競合になります。私はいくつかの重要な仕事を行っている、と私は他のスレッドに、いくつかの作業の機会を休憩を取ることができますと言っている場合には呼び出します。
揮発性およびJavaのメモリモデル(JMM)
アトミック、発注や視界の周りにJavaのメモリモデル展開:前に述べました。
適切な場合には、スレッド、視認性とアトミック間の秩序を保証します。Javaは、仮想マシンを伝えるために肯定するいくつかの特別な操作やキーワードを使用してこの場所に特別な注意を払って、任意の最適化対象命令を変更することはできません。キーワードはvolatile
そのうちの一つです。
変数を宣言するために揮発性を使用して、我々は、この変数の特徴の視認性を確保することができます。前の例MultiThreadLongで、ロングタイプT揮発性のために、保証するようにアトミックを。
public class MultiThreadLong {
public volatile static long t=0;
public static class ChangeT implements Runnable{
......
揮発性を保証するアトミック操作は非常に大きな助けを持ってではなく、揮発性のロックの代わりに、複雑な操作の数の原子性を保証することはできません。次の例では、アトミック操作は、Iには保証されません++
public class PlusTask implements Runnable {
public volatile static Integer j = 0; //1
public void add(){
for (int i = 0; i < 10000; i++) {
j++;
}
}
@Override
public void run() {
// synchronized (PlusTask.class) {
add();
// }
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
PlusTask task = new PlusTask();
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new PlusTask());
threads[i].start();
}
for (int i = 0; i < 10; i++) {
threads[i].join();
}
System.out.println(j);
}
}
上記のコードは、最終的な値100,000、実際の所望の値よりも常に小さくなければなりません。
揮発性の保証に加えて、原子、データを確保することができ、視認性と秩序を。私たちは、以下の例を参照してください。
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready);
System.out.println(number);
}
}
public static void main(String[] args) throws InterruptedException {
new ReaderThread().start();
Thread.sleep(1000);
number = 42;
ready = true;
Thread.sleep(10000);
}
}
システムの最適化の結果として、ReaderThreadスレッドがReaderThreadが終了することはできません大手、メインスレッドを変更する「を参照してください。」ことができない場合があり、これは、視認性の典型的な問題です。しかし、単純に変数を宣言する準備ができて揮発性を利用して、Java仮想マシンを告げ、この変数は異なるスレッドで変更することができます。我々は問題を解決することができます。
経営のカテゴリー:スレッドグループ
スレッド数の多いシステムでは、明確な機能を割り当てて、あなたはスレッド同じ機能は、管理を容易にするために、スレッドグループに配置されることができます。
スレッドグループは簡単です使用します。
public class ThreadGroupName implements Runnable {
public static void main(String[] args) {
ThreadGroup tg = new ThreadGroup("PrintGroup"); //1 建立名为“PrintGroup”的线程组
Thread t1 = new Thread(tg, new ThreadGroupName(), "T1");
Thread t2 = new Thread(tg, new ThreadGroupName(), "T2");
t1.start();
t2.start();
System.out.println(tg.activeCount()); //2
tg.list(); //3
}
@Override
public void run() {
String groupAndName = Thread.currentThread().getThreadGroup().getName() + "-"
+ Thread.currentThread().getName();
while (true) {
System.out.println("I am " + groupAndName);
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
コード1、グループに参加するために、「PrintGroup」スレッドグループ、および2つのスレッドT1およびT2を確立します。2,3で、はactiveCount()は、アクティブなスレッドの数を取得することができ、リスト()スレッドグループ情報内のすべてのスレッドを表示します。
スレッドグループは、ストップを持っている()、それはしかし、スレッドグループ内のすべてのスレッドを停止します、とはThread.stop()細心の注意を払って使用するように、同じ問題を抱えているだろう。
駐留舞台裏:ガーディアンのスレッド(デーモン)
デーモンスレッドは、ガベージコレクションのスレッドなど、JITスレッドがデーモンスレッドとして理解することができるシステムの保護者、バックグラウンドでいくつかの系統的なサービスが完了すると、特別なスレッドです。それに対応してユーザスレッド、完全な事業運営。ユーザスレッドがすべての上に、スレッドオブジェクトの保護者が存在しない場合は、アプリケーション全体が自然に終了する必要があります。したがって、Javaでの参照、ときにのみデーモンスレッド、Java仮想マシン非難。
ここでは、単純なデーモンスレッドは、次のとおりです。
public class DaemonDemo {
public static class DaemonT extends Thread{
public void run(){
while(true){
System.out.println("I am alive");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t=new DaemonT();
t.setDaemon(true); //1
t.start();
Thread.sleep(2000); //当主线程执行完毕后,守护线程t也随之结束。
}
}
Tが終了したときにメインスレッドは、デーモンスレッドが終了し、デーモン・スレッドに1に設定されています。トンセットのデーモンスレッドを除くと、プログラムが終了することはありません。
第1のドライ重要なこと:スレッドの優先順位
Javaスレッドは、独自の優先順位を持つことができます。利点がありますリソースの競合が、当然のことながら、リソースをつかむ可能性がより高い優先度の高いスレッドが、これは確率の問題です。この優先順位の結果を予測することは容易ではありません生成された、優先度の低いスレッドが飢餓引き起こす可能性が(たとえ低優先度が、それは死ああに餓死することはできません)。そのため、厳しい状況で、まだアプリケーション層で、独自の問題のスレッドのスケジューリングを解決する必要があります。
Javaでは、1〜10個のスレッドの優先順位を表します。一般的にあなたは、組み込みの3つの静的変数のスカラを使用することができ、言いました:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
数より高い優先順位が、1と10の間の有効範囲より高いです。例:
public class PriorityDemo {
public static class HightPriority extends Thread{
static int count=0;
public void run(){
while(true){
synchronized(PriorityDemo.class){
count++;
if(count>10000000){
System.out.println("HightPriority is complete");
break;
}
}
}
}
}
public static class LowPriority extends Thread{
static int count=0;
public void run(){
while(true){
synchronized(PriorityDemo.class){
count++;
if(count>10000000){
System.out.println("LowPriority is complete");
break;
}
}
}
}
}
/**
* HightPriority先完成的次数多,但是 不保证
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
Thread high=new HightPriority();
LowPriority low=new LowPriority();
high.setPriority(Thread.MAX_PRIORITY); //1
low.setPriority(Thread.MIN_PRIORITY); //2
low.start();
high.start();
}
}
上述代码中1、2处设置了线程的优先级,所以总是高优先级的线程执行得会快些。
线程安全的概念与synchronized
并发程序开发的一大关注重点就是线程安全。程序并行化是为了获得更高的执行效率,同时保证程序的正确性。因此,线程安全是并行程序的根本和根基。
public class AccountingVol implements Runnable {
static AccountingVol accountingVol = new AccountingVol();
static volatile int i = 0;
public static void increase() {
i++;
}
@Override
public void run() {
for (int j = 0; j < 10000000; j++) {
increase();
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(accountingVol);
Thread t2 = new Thread(accountingVol);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
上述代码中,线程t1、t2可能同时读取i为0,并各自计算得到i=1,并先后写入这个结果,因此,虽然i++被执行了2次,但实际i的值只增加了1。
要解决这个问题,我们就要保证多个线程对i进行操作时完全同步。就是说,当线程A在写入时,B不仅不能写,也不能读。java中,提供了一个重要的关键字synchronized
来实现这个功能。
synchronized
作用是实现线程间的同步。它的工作是对同步的代码加锁,使得每次只能有一个线程进入同步块。
用法:
- 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
- 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
- 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
指定加锁对象
下面程序中,将synchronized作用于给定对象instance。每次进入被synchronized包裹的代码段,都会请求instance的锁。若有其他线程占用,则必须等待。
public class AccountingSync implements Runnable{
static AccountingSync instance=new AccountingSync();
static int i=0;
@Override
public void run() {
for(int j=0;j<10000000;j++){
synchronized(instance){
i++;
}
}
}
//main程序见上例代码
}
直接作用于实例方法
public class AccountingSync2 implements Runnable{
static AccountingSync2 instance=new AccountingSync2();
static int i=0;
public synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<10000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
AccountingSync2 i1=new AccountingSync2();
// AccountingSync2 i2=new AccountingSync2();
Thread t1=new Thread(i1);
Thread t2=new Thread(i1);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
上例代码中,synchronized关键字作用于一个实例方法,这就是说在进入increase()方法前,线程必须获得当前对象实例的锁。在本例中就是instance对象。在此例中,线程t1和t2需要用到相同的Ruanable实例i1,这样才能关注到同一个对象锁上。若两个线程使用不同的两个Runnable实例t1,t2,即两个线程使用了两把不同的锁。
但是,我们可以把increase()方法改成static的,这样方法块请求的是当前类的锁,而不是当前实例的,因此,线程可以同步。如下:
public class AccountingSync2 implements Runnable{
static AccountingSync2 instance=new AccountingSync2();
static int i=0;
public static synchronized void increase(){ //3
i++;
}
@Override
public void run() {
for(int j=0;j<10000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
AccountingSync2 i1=new AccountingSync2();
AccountingSync2 i2=new AccountingSync2();
Thread t1=new Thread(i1); //1
Thread t2=new Thread(i2); //2
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
1及び2つの異なるRunable例の使用が、静的3における同期方法は、この方法は、現在のクラスがロックラッチの現在のインスタンスではない必要があり、したがって、スレッドが正しく同期させることができます。
また、同期、スレッドセーフな外部スレッドまた、スレッド間の可視性と順序を確保することができる同期します。複数のスレッドが死の同期シリアル実行に制限されています。
プログラム中のゴースト:隠されたエラー
異常な例外スタック良い修理、しかし、例外なく、ノーログ、例外なくスタック、非常にクレイジーな人。
サイレントエラーケース
あなたがこのプログラムを実行する場合は、隠されたエラーが見つかります。
int v1 = 1073741827;
int v2 = 1431655768;
int ave = (v1+v2)/2;
上記AVEプリントアウト、あなたは値aveが-894 784 850、負の数がわかります。それはオーバーフローためです。この目に見えないエラーを見つけるのは難しい、と不気味です。
同時でのArrayList
ArrayListのスレッドセーフなコンテナです。あなたが複数のスレッドでのArrayListを使用している場合、プログラムはエラーが発生することがあり、その後、どのような問題が起こるのだろうか?
public class ArrayListMultiThread {
static ArrayList<Integer> al = new ArrayList<Integer>(10);
public static class AddThread implements Runnable{
@Override
public void run() {
for (int i=0;i<10000000;i++){
al.add(i);
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new AddThread());
Thread t2 = new Thread(new AddThread());
t1.start();t2.start();
t1.join(); t2.join();
System.out.println(al.size()); //抛出异常 返回小于2000的数值。
}
}
このエラーは、我々は、3つの異なる結果を得ることができ、上記のコードでは、元の出力は2000万する必要がありますが、原因ArrayListにサポートしていません。
まず、プログラムが正常に終了し、非常に小さなチャンス
第二に、プログラムは例外をスローします。
Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 1823230 at java.util.ArrayList.add(ArrayList.java:459) at geym.ch2.ArrayListMultiThread$AddThread.run(ArrayListMultiThread.java:11) at java.lang.Thread.run(Thread.java:745)
これは、拡張プロセス中のArrayListは、内部一貫性が破壊されたためであるが、国境を越えた問題をもたらすないロック保護、別のスレッド矛盾内部状態へのアクセスは、存在しないからです。
第三に、期待2000万プリントよりも低く、非常に微妙な問題の出現、。
!!改良された方法は非常に簡単で、代わりのArrayListのスレッドセーフなベクターは、することができます。
奇妙なHashMapの下に同時
同じHashMapのは、スレッドセーフではありません。
public class HashMapMultiThread {
static Map<String, String> map = new HashMap<String, String>();
public static class AddThread implements Runnable {
int start = 0;
public AddThread(int start) {
this.start = start;
}
@Override
public void run() {
for (int i = 0; i < 100000; i += 2) {
map.put(Integer.toString(i), Integer.toBinaryString(i));
}
}
public static void main(String[] args) throws InterruptedException {
// 根据你的电脑CPU核数来配置 两核启两个线程就行
Thread t1 = new Thread(new HashMapMultiThread.AddThread(0));
Thread t2 = new Thread(new HashMapMultiThread.AddThread(1));
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(map.size());
}
}
}
私たちは、100,000であることを期待しますが、実際は3例であってもよいです。
- まず、プログラムが正常に終了し、期待値の大きさ。
- 第二に、プログラムが正常に終了しますが、10万未満。
- 第三に、プログラムが終了したことがありません
最初の二つのケースと同様のArrayList、第三の場合の、競合マルチスレッドに、中のHashMapによるEntry<K,V>
リストの構造が破壊され、チェーンリング!チェーンリングは、HashMap.put()メソッドである場合、反復サイクルが死んでいます。示されるように、最も単純な環構造、KEY1およびKEY2互いの次の要素を示します。
初級FAQ:間違っロック
public class BadLockOnInteger implements Runnable {
public static Integer i = 0;
public static BadLockOnInteger instance = new BadLockOnInteger();
@Override
public void run() {
for (int j = 0; j < 10000000; j++) {
synchronized (i){ //1
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
1上記のコードでは、これが理由です、我々はプログラムを実行し、しかし、問題はいないようだ、私のロックを追加しましたが、数の期待値2,000万以下を得ましたか。不変オブジェクトはInteger型に属するので。その整数値を変更したい場合は、新しいIntegerオブジェクトを作成する必要があり、変更することはできません。複数のスレッドの中であなたは、必ずしも私は(私が変更された)同じオブジェクトが表示されていないので、コードの制御の問題の重要な部分で、その結果、異なるオブジェクトインスタンス内のすべてのロックに追加されます。
限り、ロックはその上のインスタンスに適用されると、この問題を修正しました:
synchronized (i){
次のコードに置き換えられました:
synchronized (instance){