java同步與多線程淺析



2七、多線程 同步的原理 多線程





 一. 實現多線程  1. 虛假的多線程



  例1
函數



public class TestThread

{

 int i=0, j=0;

 public void go(int flag)

 {

  while(true)

  {

   try{ Thread.sleep(100);

  }

  catch(InterruptedException e)

  {

   System.out.println("Interrupted");

  }

  if(flag==0) i++;

  System.out.println("i=" + i);

  }

  else

  {

   j++;

   System.out.println("j=" + j);

  }

 }

}

public static void main(String[] args)

{

 new TestThread().go(0);

 new TestThread().go(1);

}

}
優化



  上面程序的運行結果爲:



i=1 i=2 i=3
。。。



  結果將一直打印出I的值。咱們的意圖是當在while循環中調用sleep()時,另外一個線程就將起動,打印出j的值,但結果卻並非這樣。關於sleep()爲何不會出現咱們預想的結果,在下面將講到。
this





探討Java多線程及其同步的實現 spa







 
 
 
 
 
 
 
 
 
 
 
 












  2. 實現多線程



  經過繼承class Thread或實現Runnable接口,咱們能夠實現多線程



  2.1 經過繼承class Thread實現多線程



  class Thread中有兩個最重要的函數run()start()



  1) run()函數必須進行覆寫,把要在多個線程中並行處理的代碼放到這個函數中。



  2) 雖然run()函數實現了多個線程的並行處理,但咱們不能直接調用run()函數,而是經過調用start()函數來調用run()函數。在調用start()的時候,start()函數會首先進行與多線程相關的初始化(這也是爲何不能直接調用run()函數的緣由),而後再調用run()函數。



  例2
線程



public class TestThread extends Thread

{

 private static int threadCount = 0;

 private int threadNum = ++threadCount;

 private int i = 5;

 public void run()

 {

  while(true)

  {

   try

   {

    Thread.sleep(100); 

   }

   catch(InterruptedException e)

   {

    System.out.println("Interrupted");

   }

   System.out.println("Thread " + threadNum +
" = " + i);

   if(--i==0) return;

  }

 }



 public static void main(String[] args)

 {

  for(int i=0; i<5; i++)

   new TestThread().start();

 }

}
對象



  運行結果爲:



Thread 1 = 5

Thread 2 = 5

Thread 3 = 5

Thread 4 = 5

Thread 5 = 5

Thread 1 = 4

Thread 2 = 4

Thread 3 = 4

Thread 4 = 4

Thread 1 = 3

Thread 2 = 3

Thread 5 = 4

Thread 3 = 3

Thread 4 = 3

Thread 1 = 2

Thread 2 = 2

Thread 5 = 3

Thread 3 = 2

Thread 4 = 2

Thread 1 = 1

Thread 2 = 1

Thread 5 = 2

Thread 3 = 1

Thread 4 = 1

Thread 5 = 1



  從結果可見,例2能實現多線程的並行處理。



  **:在上面的例子中,咱們只用new產生Thread對象,並無用reference來記錄所產生的Thread對象。根據垃圾回收機制,當一個對象沒有被reference引用時,它將被回收。可是垃圾回收機制對Thread對象不成立。由於每個Thread都會進行註冊動做,因此即便咱們在產生Thread對象時沒有指定一個reference指向這個對象,實際上也會在某個地方有個指向該對象的reference,因此垃圾回收器沒法回收它們。



繼承



  3) 經過Thread的子類產生的線程對象是不一樣對象的線程 接口



class TestSynchronized extends Thread

{

 public TestSynchronized(String name)

 {

  super(name);

 }

 public synchronized static void prt()

 {

  for(int i=10; i<20; i++)

  {

   System.out.println(Thread.currentThread().getName()
+ " : " + i);

   try

   {

    Thread.sleep(100);

   }

   catch(InterruptedException e)

   {

    System.out.println("Interrupted");

   }

  }

 }



 public synchronized void run()

 {

  for(int i=0; i<3; i++)

  {

   System.out.println(Thread.currentThread().getName()
+ " : " + i);

   try

   {

    Thread.sleep(100);

   }

   catch(InterruptedException e)

   {

    System.out.println("Interrupted");

   }

  }

 }

}



 public class TestThread

 {

  public static void main(String[] args)

  {

   TestSynchronized t1 = new TestSynchronized("t1");

   TestSynchronized t2 = new
TestSynchronized("t2");

   t1.start();

   t1.start(); //1

   //t2.start(); (2 }}
資源



  運行結果爲:



t1 : 0

t1 : 1

t1 : 2

t1 : 0

t1 : 1

t1 : 2



  因爲是同一個對象啓動的不一樣線程,因此run()函數實現了synchronized。若是去掉(2)的註釋,把代碼(1)註釋掉,結果將變爲:



t1 : 0

t2 : 0

t1 : 1

t2 : 1

t1 : 2

t2 : 2



  因爲t1t2是兩個對象,因此它們所啓動的線程可同時訪問run()函數。







  2.2 經過實現Runnable接口實現多線程



  若是有一個類,它已繼承了某個類,又想實現多線程,那就能夠經過實現Runnable接口來實現。



  1) Runnable接口只有一個run()函數。



  2) 把一個實現了Runnable接口的對象做爲參數產生一個Thread對象,再調用Thread對象的start()函數就可執行並行操做。若是在產生一個Thread對象時以一個Runnable接口的實現類的對象做爲參數,那麼在調用start()函數時,start()會調用Runnable接口的實現類中的run()函數。



  例3.1



public class TestThread implements Runnable

{

 private static int threadCount = 0;

 private int threadNum = ++threadCount;

 private int i = 5;

 public void run()

 {

  while(true)

  {

   try

   {

    Thread.sleep(100);

   }

   catch(InterruptedException e)

   {

    System.out.println("Interrupted");

   }

   System.out.println("Thread " + threadNum +
" = " + i);

   if(--i==0) return;

  }

 }



 public static void main(String[] args)

 {

  for(int i=0; i<5; i++) new Thread(new
TestThread()).start();
 //1

 }

}



  運行結果爲:



Thread 1 = 5

Thread 2 = 5

Thread 3 = 5

Thread 4 = 5

Thread 5 = 5

Thread 1 = 4

Thread 2 = 4

Thread 3 = 4

Thread 4 = 4

Thread 4 = 3

Thread 5 = 4

Thread 1 = 3

Thread 2 = 3

Thread 3 = 3

Thread 4 = 2

Thread 5 = 3

Thread 1 = 2

Thread 2 = 2

Thread 3 = 2

Thread 4 = 1

Thread 5 = 2

Thread 1 = 1

Thread 2 = 1

Thread 3 = 1

Thread 5 = 1



  例3是對例2的修改,它經過實現Runnable接口來實現並行處理。代碼(1)處可見,要調用TestThread中的並行操做部分,要把一個TestThread對象做爲參數來產生Thread對象,再調用Thread對象的start()函數。



  3) 同一個實現了Runnable接口的對象做爲參數產生的全部Thread對象是同一對象下的線程。



  例3.2



package mypackage1;

public class TestThread implements Runnable

{

 public synchronized void run()

 {

  for(int i=0; i<5; i++)

  {

   System.out.println(Thread.currentThread().getName()
+ " : " + i);

   try

   {

    Thread.sleep(100);

   }

   catch(InterruptedException e)

   {

    System.out.println("Interrupted");

   }

  }

 }



 public static void main(String[] args)

 {

  TestThread testThread = new TestThread();

  for(int i=0; i<5; i++)

  //new Thread(testThread, "t" + i).start(); (1

  new Thread(new TestThread(), "t" +
i).start();
 (2 }}



  運行結果爲:



t0 : 0

t1 : 0

t2 : 0

t3 : 0

t4 : 0

t0 : 1

t1 : 1

t2 : 1

t3 : 1

t4 : 1

t0 : 2

t1 : 2

t2 : 2

t3 : 2

t4 : 2

t0 : 3

t1 : 3

t2 : 3

t3 : 3

t4 : 3

t0 : 4

t1 : 4

t2 : 4

t3 : 4

t4 : 4



  因爲代碼(2)每次都是用一個新的TestThread對象來產生Thread對象的,因此產生出來的Thread對象是不一樣對象的線程,因此全部Thread對象均可同時訪問run()函數。若是註釋掉代碼(2),並去掉代碼(1)的註釋,結果爲:



t0 : 0

t0 : 1

t0 : 2

t0 : 3

t0 : 4

t1 : 0

t1 : 1

t1 : 2

t1 : 3

t1 : 4

t2 : 0

t2 : 1

t2 : 2

t2 : 3

t2 : 4

t3 : 0

t3 : 1

t3 : 2

t3 : 3

t3 : 4

t4 : 0

t4 : 1

t4 : 2

t4 : 3

t4 : 4



  因爲代碼(1)中每次都是用同一個TestThread對象來產生Thread對象的,因此產生出來的Thread對象是同一個對象的線程,因此實現run()函數的同步。







  二. 共享資源的同步



  1. 同步的必要性



  例4



class Seq

{

 private static int number = 0;

 private static Seq seq = new Seq();

 private Seq() {}

 public static Seq getInstance()

 {

  return seq;

 }

 public int get()

 {

  number++;  

  //a


  return number; 

  //b


 }

}



public class TestThread

{

 public static void main(String[] args)

 {

  Seq.getInstance().get(); 

  //1

  Seq.getInstance().get(); 

  //2


 }

}



  上面是一個取得序列號的單例模式的例子,但調用get()時,可能會產生兩個相同的序列號:



  當代碼(1)和(2)都試圖調用get()取得一個惟一的序列。當代碼(1)執行完代碼(a),正要執行代碼(b)時,它被中斷了並開始執行代碼(2)。一旦當代碼(2)執行完(a)而代碼(1)還未執行代碼(b),那麼代碼(1)和代碼(2)就將獲得相同的值。







  2. 經過synchronized實現資源同步



  2.1 鎖標誌



  2.1.1 每一個對象都有一個標誌鎖。當對象的一個線程訪問了對象的某個synchronized數據(包括函數)時,這個對象就將被上鎖,因此被聲明爲synchronized的數據(包括函數)都不能被調用(由於當前線程取走了對象的鎖標誌)。只有當前線程訪問完它要訪問的synchronized數據,釋放鎖標誌後,同一個對象的其它線程才能訪問synchronized數據。



  2.1.2 每一個class也有一個鎖標誌。對於synchronized
static
數據(包括函數)能夠在整個class下進行鎖定,避免static數據的同時訪問。



  例5



class Seq

{

 private static int number = 0;

 private static Seq seq = new Seq();

 private Seq() {}

 public static Seq getInstance(){ return seq; }

 public synchronized int get()

 {

  //1


  number++;

  return number;

 }

}



  例5在例4的基礎上,把get()函數聲明爲synchronized,那麼在同一個對象中,就只能有一個線程調用get()函數,因此每一個線程取得的number值就是惟一的了。



  例6



class Seq

{

 private static int number = 0;

 private static Seq seq = null;

 private Seq() {}

 synchronized public static Seq getInstance()

 {

  //1


  if(seq==null) seq = new Seq();

  return seq;

 }

 public synchronized int get()

 {

  number++;

  return number;

 }

}



  例6getInstance()函數聲明爲synchronized,那樣就保證經過getInstance()獲得的是同一個seq對象。



  2.2 non-staticsynchronized數據只能在同一個對象的純種實現同步訪問,不一樣對象的線程仍可同時訪問。



  例7



class TestSynchronized implements Runnable

{

 public synchronized void run()

 {

  //1

  for(int i=0; i<10; i++)

  {

   System.out.println(Thread.currentThread().getName()
+ " : " + i);

   /*2*/

   try

   {

    Thread.sleep(100);

   }

   catch(InterruptedException e)

   {

    System.out.println("Interrupted");

   }

  }

 }

}



public class TestThread

{

 public static void main(String[] args)

 {

  TestSynchronized r1 = new TestSynchronized();

  TestSynchronized r2 = new TestSynchronized();

  Thread t1 = new Thread(r1, "t1");

  Thread t2 = new Thread(r2, "t2"); //(3) //

  Thread t2 = new Thread(r1, "t2"); (4

  t1.start();

  t2.start();

 }

}



  運行結果爲:



t1 : 0

t2 : 0

t1 : 1

t2 : 1

t1 : 2

t2 : 2

t1 : 3

t2 : 3

t1 : 4

t2 : 4

t1 : 5

t2 : 5

t1 : 6

t2 : 6

t1 : 7

t2 : 7

t1 : 8

t2 : 8

t1 : 9

t2 : 9



  雖然咱們在代碼(1)中把run()函數聲明爲synchronized,但因爲t1t2是兩個對象(r1r2)的線程,而run()函數是non-staticsynchronized數據,因此仍可被同時訪問(代碼(2)中的sleep()函數因爲在暫停時不會釋放標誌鎖,由於線程中的循環很難被中斷去執行另外一個線程,因此代碼(2)只是爲了顯示結果)。



  若是把例7中的代碼(3)註釋掉,並去年代碼(4)的註釋,運行結果將爲:



t1 : 0

t1 : 1

t1 : 2

t1 : 3

t1 : 4

t1 : 5

t1 : 6

t1 : 7

t1 : 8

t1 : 9

t2 : 0

t2 : 1

t2 : 2

t2 : 3

t2 : 4

t2 : 5

t2 : 6

t2 : 7

t2 : 8

t2 : 9



  修改後的t1t2是同一個對象(r1)的線程,因此只有當一個線程(t1t2中的一個)執行run()函數,另外一個線程才能執行。







  2.3 對象的鎖標誌class鎖標誌是相互獨立的。

8



class TestSynchronized extends Thread

{

 public TestSynchronized(String name)

 {

  super(name);

 }



 public synchronized static void prt()

 {

  for(int i=10; i<20; i++)

  {

   System.out.println(Thread.currentThread().getName()
+ " : " + i);

   try

   {

    Thread.sleep(100);

   }

   catch(InterruptedException e)

   {

    System.out.println("Interrupted");

   }

  }

 }



 public synchronized void run()

 {

  for(int i=0; i<10; i++)

  {

   System.out.println(Thread.currentThread().getName()
+ " : " + i);

   try

   {

    Thread.sleep(100);

   }

   catch(InterruptedException e)

   {

    System.out.println("Interrupted");

   }

  }

 }

}



public class TestThread

{

 public static void main(String[] args)

 {

  TestSynchronized t1 = new
TestSynchronized("t1");

  TestSynchronized t2 = new
TestSynchronized("t2");

  t1.start();

  t1.prt(); //1

  t2.prt(); //2

 }

}



  運行結果爲:



main : 10

t1 : 0

main : 11

t1 : 1

main : 12

t1 : 2

main : 13

t1 : 3

main : 14

t1 : 4

main : 15

t1 : 5

main : 16

t1 : 6

main : 17

t1 : 7

main : 18

t1 : 8

main : 19

t1 : 9

main : 10

main : 11

main : 12

main : 13

main : 14

main : 15

main : 16

main : 17

main : 18

main : 19



  在代碼(1)中,雖然是經過對象t1來調用prt()函數的,但因爲prt()是靜態的,因此調用它時不用通過任何對象,它所屬的線程爲main線程。



  因爲調用run()函數取走的是對象鎖,而調用prt()函數取走的是class鎖,因此同一個線程t1(由上面可知其實是不一樣線程)調用run()函數且還沒完成run()函數時,它就能調用prt()函數。但prt()函數只能被一個線程調用,如代碼(1)和代碼(2),即便是兩個不一樣的對象也不能同時調用prt()







  3. 同步的優化



  1) synchronized block



  語法爲:synchronized(reference){ do this }



  reference用來指定以某個對象的鎖標誌大括號內的代碼實施同步控制。



  例9



class TestSynchronized implements Runnable

{

 static int j = 0;

 public synchronized void run()

 {

  for(int i=0; i<5; i++)

  {

   //1

   System.out.println(Thread.currentThread().getName()
+ " : " + j++);

   try

   {

    Thread.sleep(100);

   }

   catch(InterruptedException e)

   {

    System.out.println("Interrupted");

   }

  }

 }

}



public class TestThread

{

 public static void main(String[] args)

 {

  TestSynchronized r1 = new TestSynchronized();

  TestSynchronized r2 = new TestSynchronized();

  Thread t1 = new Thread(r1, "t1");

  Thread t2 = new Thread(r1, "t2");

  t1.start();

  t2.start();

 }

}



  運行結果爲:



t1 : 0

t1 : 1

t1 : 2

t1 : 3

t1 : 4

t2 : 5

t2 : 6

t2 : 7

t2 : 8

t2 : 9



  上面的代碼的run()函數實現了同步,使每次打印出來的j老是不相同的。但實際上在整個run()函數中,咱們只關心j的同步,而其他代碼同步與否咱們是不關心的,因此能夠對它進行如下修改:



class TestSynchronized implements Runnable

{

 static int j = 0;

 public void run()

 {

  for(int i=0; i<5; i++)

  {

   //1

   synchronized(this)

   {

    System.out.println(Thread.currentThread().getName()
+ " : " + j++);

   }

   try

   {

    Thread.sleep(100);

   }

   catch(InterruptedException e)

   {

    System.out.println("Interrupted");

   }

  }

 }

}



public class TestThread

{

 public static void main(String[] args)

 {

  TestSynchronized r1 = new TestSynchronized();

  TestSynchronized r2 = new TestSynchronized();

  Thread t1 = new Thread(r1, "t1");

  Thread t2 = new Thread(r1, "t2");

  t1.start();

  t2.start();

 }

}



  運行結果爲:



t1 : 0

t2 : 1

t1 : 2

t2 : 3

t1 : 4

t2 : 5

t1 : 6

t2 : 7

t1 : 8

t2 : 9



  因爲進行同步的範圍縮小了,因此程序的效率將提升。同時,代碼(1)指出,當對大括號內的println()語句進行同步控制時,會取走當前對象的鎖標誌,即對當前對象上鎖,不讓當前對象下的其它線程執行當前對象的其它synchronized數據。

相關文章
相關標籤/搜索