Java多線程編程核心 - 對象及變量的並發訪問


 

1、什么是“線程安全”與“非線程安全”?

“非線程安全”會在多個線程對同一對象總的實例變量進行並發訪問時發生,產生的后果是“臟讀”,也就是取到的數據其實是被更改過的。

“線程安全”是以獲得的實例變量的值是經過同步處理的,不會出現臟讀的現象。

 

2、非線程安全例子?怎么解決?

非線程安全

package com.jvm.thread;

 

public class HasSelfPrivateNum {

   private int num =  0;

   public void add(String username){

      try{

          if(username.equals("a")){

             num = 100;

             System.out.println("a set over!");

             Thread.sleep(2000);

          }else{

             num = 200;

             System.out.println("b set over!");

          }

          System.out.println(username + " num = " + num);

      }catch(InterruptedException e){

          e.printStackTrace();

      }

   }

}

package com.jvm.thread;

 

public class MyThreadA extends Thread {

   private HasSelfPrivateNum obj;

   public MyThreadA(HasSelfPrivateNum obj){

      this.obj = obj;

   }

  

   @Override

   public void run() {

      super.run();

      obj.add("a");

   }

}

 

package com.jvm.thread;

 

public class MyThreadB extends Thread {

   private HasSelfPrivateNum obj;

   public MyThreadB(HasSelfPrivateNum obj){

      this.obj = obj;

   }

  

   @Override

   public void run() {

      super.run();

      obj.add("b");

   }

}

 

package com.jvm.thread;

 

public class MyThread06 {

   public static void main(String[] args) {

      HasSelfPrivateNum obj = new HasSelfPrivateNum();

      MyThreadA a = new MyThreadA(obj);

      a.start();

      MyThreadB b = new MyThreadB(obj);

      b.start();

   }

}

 

a set over!

b set over!

b num = 200

a num = 200

 

解決方法:在 add() 前加關鍵字synchronized

a set over!

a num = 100

b set over!

b num = 200

 

 

3、synchronized 關鍵字

當A線程調用anyObject 對象加入synchronized關鍵字的X方法時,A線程就獲得了X方法鎖,更准確地講,獲得的是對象的鎖,所以,其他線程必須等待A線程執行完畢才能調用X方法,但線程B可以隨意調用其他的非synchronized同步方法。

當A線程調用anyObject 對象加入synchronized關鍵字的X方法時,A線程就獲得了X方法所在的對象的鎖,所以其他線程必須等待A線程執行完畢才可以調用X方法,而B如果調用聲明了synchronized關鍵字的非X方法時,必須等待A線程將X方法執行完,也就是釋放對象鎖后才可以調用。

 

4、synchronized鎖重入

“可重入鎖”:自己可以再次獲得自己的內部鎖。可重入鎖也支持在父子繼承的環境中。

package com.jvm.thread;

 

public class Service extends Thread {

   synchronized public void service1(){

      System.out.println("service1");

      service2();

   }

   synchronized public void service2(){

      System.out.println("service2");

      service3();

   }

   synchronized public void service3(){

      System.out.println("service3");

   }

  

   @Override

   public void run() {

      super.run();

      Service service = new Service();

      service.service1();

   }

  

   public static void main(String[] args) {

      Service service = new Service();

      service.start();

   }

}

 

service1

service2

service3

 

 

5、鎖自動釋放——出現異常

當一個線程執行的代碼出現異常時,其所持有的鎖會自動釋放。

 

6、synchronized同步方法的缺點?解決辦法?

Synchronized同步方法在某些情況下是有弊端的,比如A線程調用同步方法執行一個長時間的任務,那么B線程則必須等待比較長時間。

在這樣的情況下可以使用synchronized同步語句塊來解決。

 

7、synchronized同步語句塊的使用?

當兩個並發線程訪問同一個對象object中的synchronized(this)同步代碼塊時,一段時間內只能有一個線程被執行,另外一個線程必須等待當前線程執行完這個代碼塊以后才能執行該代碼塊。

package com.jvm.thread;

 

public class MyTask {

   public void taskMethod(){

      try {

          synchronized (this) {

             System.out.println("begin time=" + System.currentTimeMillis());

             Thread.sleep(2000);

             System.out.println("end time=" + System.currentTimeMillis());

          }

      } catch (InterruptedException e) {

          e.printStackTrace();

      }

   }

}

 

package com.jvm.thread;

 

public class MyThreadA extends Thread {

   private MyTask task;

   public MyThreadA(MyTask task){

      this.task = task;

   }

  

   @Override

   public void run() {

      task.taskMethod();

   }

}

 

package com.jvm.thread;

 

public class MyThreadB extends Thread {

   private MyTask task;

   public MyThreadB(MyTask task){

      this.task = task;

   }

  

   @Override

   public void run() {

      task.taskMethod();

   }

}

 

package com.jvm.thread;

 

public class Run {

   public static void main(String[] args) {

      MyTask task = new MyTask();

      MyThreadA a = new MyThreadA(task);

      a.setName("a");

      a.start();

      MyThreadB b = new MyThreadB(task);

      b.setName("b");

      b.start();

   }

}

 

begin time=1498358631251

end time=1498358633252

begin time=1498358633252

end time=1498358635252

 

 

8、synchronized代碼塊間的同步性?

在使用不同synchronized(this)代碼塊時需要注意的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對同一個object中所有其他synchronized(this)同步代碼塊的訪問將被阻塞,這說明synchronized使用的“對象監視器”是一個。

 

9、將任意對象作為對象監視器?優點?

使用synchronized(this)格式來同步代碼塊,其實Java還支持對“任意對象”作為“對象監視器”來實現同步的功能。這個“任意對象”大多數是實例變量及方法的參數,使用格式為synchronized(非this對象)。

優點:如果在一個類中有很多個synchronized方法,這時雖然能實現同步,但是會受到阻塞,所以影響運行效率;但如果使用同步代碼塊鎖非this對象,則synchronized(非this)代碼塊中程序與同步方法是異步的,不與其他鎖this同步方法爭搶this鎖,則可大大提高效率。

package com.jvm.thread;

 

public class Service extends Thread {

   private String username;

   private String password;

   private String anyString = new String();

  

   public void setUsernamePassword(String username, String pwssword){

      try {

          synchronized (anyString) {

             System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in to synchronized block");

             username = username;

             Thread.sleep(3000);

             pwssword = pwssword;

             System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave synchronized block");

          }

      } catch (InterruptedException e) {

          e.printStackTrace();

      }

   }

}

package com.jvm.thread;

 

public class MyThreadA extends Thread {

   private Service service;

   public MyThreadA(Service service){

      this.service = service;

   }

  

   @Override

   public void run() {

      service.setUsernamePassword("a", "aa");

   }

}

 

package com.jvm.thread;

 

public class MyThreadB extends Thread {

   private Service service;

   public MyThreadB(Service service){

      this.service = service;

   }

  

   @Override

   public void run() {

      service.setUsernamePassword("b", "bb");

   }

}

 

package com.jvm.thread;

 

public class Run {

   public static void main(String[] args) {

      Service service = new Service();

      MyThreadA a = new MyThreadA(service);

      a.setName("A");

      a.start();

      MyThreadB b = new MyThreadB(service);

      b.setName("B");

      b.start();

   }

}

 

Thread name:A at 1498744309904 go in to synchronized block

Thread name:A at 1498744312906 leave synchronized block

Thread name:B at 1498744312906 go in to synchronized block

Thread name:B at 1498744315906 leave synchronized block

 

 

 

 

 

 

10、3個結論

“synchronized(非this對象x)”格式的寫法是將x對象本身作為“對象監視器”,這樣就可以得出以下3個結論:

1)當多個線程同時執行synchronized(x){}同步代碼塊時呈同步效果。

2)當其他線程執行x對象中synchronized同步方法時呈同步效果。

3)當其他線程執行x對象方法里面的synchronized(this)代碼塊時也呈同步效果。

原因:使用同一個“對象監視器”。

 

11、靜態同步synchronized方法與synchronized(class)代碼塊

關鍵字synchronized還可以應用在static靜態方法上,如果這樣寫,那是對當前的 *.java文件對應的Class類進行持鎖。

package com.jvm.thread;

 

public class Service extends Thread {

   synchronized public static void printA() {

      try {

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printA()");

          Thread.sleep(3000);

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printA()");

      } catch (InterruptedException e) {

          e.printStackTrace();

      }

   }

  

   synchronized public static void printB() {

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printB()");

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printB()");

   }

}

package com.jvm.thread;

 

public class MyThreadA extends Thread {

   private Service service;

   public MyThreadA(Service service){

      this.service = service;

   }

  

   @Override

   public void run() {

      service.printA();

   }

}

 

package com.jvm.thread;

 

public class MyThreadB extends Thread {

   private Service service;

   public MyThreadB(Service service){

      this.service = service;

   }

  

   @Override

   public void run() {

      service.printB();

   }

}

 

package com.jvm.thread;

 

public class Run {

   public static void main(String[] args) {

      Service service = new Service();

      MyThreadA a = new MyThreadA(service);

      a.setName("A");

      a.start();

      MyThreadB b = new MyThreadB(service);

      b.setName("B");

      b.start();

   }

}

 

Thread name:A at 1498746369790 go in printA()

Thread name:A at 1498746372791 leave printA()

Thread name:B at 1498746372792 go in printB()

Thread name:B at 1498746372792 leave printB()

 

分析:從運行結果來看,和synchronized加到非static方法上使用效果一樣。其實有本質上的不同,synchronized加到static靜態方法上是給Class類加鎖,而synchronized加到非static方法上是給對象加鎖。

package com.jvm.thread;

 

public class Service extends Thread {

   synchronized public static void printA() {

      try {

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printA()");

          Thread.sleep(3000);

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printA()");

      } catch (InterruptedException e) {

          e.printStackTrace();

      }

   }

  

   synchronized public static void printB() {

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printB()");

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printB()");

   }

  

   synchronized public void printC() {

      System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printC()");

      System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printC()");

   }

}

package com.jvm.thread;

 

public class MyThreadC extends Thread {

   private Service service;

   public MyThreadC(Service service){

      this.service = service;

   }

  

   @Override

   public void run() {

      service.printC();

   }

}

 

package com.jvm.thread;

 

public class Run {

   public static void main(String[] args) {

      Service service = new Service();

      MyThreadA a = new MyThreadA(service);

      a.setName("A");

      a.start();

      MyThreadB b = new MyThreadB(service);

      b.setName("B");

      b.start();

      MyThreadC myThreadC = new MyThreadC(service);

      myThreadC.setName("C");

      myThreadC.start();

   }

}

 

Thread name:A at 1498746943314 go in printA()

Thread name:C at 1498746943315 go in printC()

Thread name:C at 1498746943315 leave printC()

Thread name:A at 1498746946315 leave printA()

Thread name:B at 1498746946315 go in printB()

Thread name:B at 1498746946315 leave printB()

 

分析:異步的原因是持有不同的鎖,一個是對象鎖,另外一個是Class鎖,而Class鎖可以對類的所有對象實例起作用。

package com.jvm.thread;

 

public class Service extends Thread {

   synchronized public static void printA() {

      try {

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printA()");

          Thread.sleep(3000);

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printA()");

      } catch (InterruptedException e) {

          e.printStackTrace();

      }

   }

  

   synchronized public static void printB() {

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printB()");

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printB()");

   }

}

package com.jvm.thread;

 

public class Run {

   public static void main(String[] args) {

      Service service1 = new Service();

      Service service2 = new Service();

      MyThreadA a = new MyThreadA(service1);

      a.setName("A");

      a.start();

      MyThreadB b = new MyThreadB(service2);

      b.setName("B");

      b.start();

   }

}

 

Thread name:A at 1498747202057 go in printA()

Thread name:A at 1498747205057 leave printA()

Thread name:B at 1498747205057 go in printB()

Thread name:B at 1498747205057 leave printB()

 

同步代碼塊synchronized(class)代碼塊的作用和synchronized static方法的作用一樣。

 

12、數據類型String的常量池特性

在JVM中具有String常量池緩沖的功能

package com.jvm.thread;

 

public class Test {

   public static void main(String[] args) {

      String a = "a";

      String b = "a";

      System.out.println(a == b); //true

   }

}

 

將synchronized(string)同步塊與String聯合使用時,要注意常量池帶來的一些例外。

package com.jvm.thread;

 

public class Service extends Thread {

   public static void print(String str) {

      try {

          synchronized (str) {

             while(true){

                System.out.println(Thread.currentThread().getName());

                Thread.sleep(1000);

             }

          }

      } catch (InterruptedException e) {

          e.printStackTrace();

      }

   }

}

package com.jvm.thread;

 

public class MyThreadA extends Thread {

   private Service service;

   public MyThreadA(Service service){

      this.service = service;

   }

  

   @Override

   public void run() {

      service.print("AA");

   }

}

 

package com.jvm.thread;

 

public class MyThreadB extends Thread {

   private Service service;

   public MyThreadB(Service service){

      this.service = service;

   }

  

   @Override

   public void run() {

      service.print("AA");

   }

}

 

package com.jvm.thread;

 

public class Run {

   public static void main(String[] args) {

      Service service = new Service();

      MyThreadA a = new MyThreadA(service);

      a.setName("A");

      a.start();

      MyThreadB b = new MyThreadB(service);

      b.setName("B");

      b.start();

   }

}

 

A

A

A

A

A

A

A

 

分析:死循環,原因是兩個值都是AA兩個線程持有相同的鎖,所以造成線程B不能執行。這就是String常量池所帶來的問題。因此,在大多數情況下,同步synchronized代碼塊都不適用String作為鎖對象,而改用其他,比如 new Object()實例化一個Oject對象。

 

 

13、同步synchronized方法無限等待與解決

package com.jvm.thread;

 

public class Service extends Thread {

   synchronized public void methodA() {

      System.out.println("methodA begin");

      boolean isContinueRun = true;

      while (isContinueRun) {

 

      }

      System.out.println("methodA end");

   }

 

   synchronized public void methodB() {

      System.out.println("methodB begin");

      System.out.println("methodB end");

   }

}

package com.jvm.thread;

 

public class MyThreadA extends Thread {

   private Service service;

   public MyThreadA(Service service){

      this.service = service;

   }

  

   @Override

   public void run() {

      service.methodA();

   }

}

 

package com.jvm.thread;

 

public class MyThreadB extends Thread {

   private Service service;

   public MyThreadB(Service service){

      this.service = service;

   }

  

   @Override

   public void run() {

      service.methodB();

   }

}

 

package com.jvm.thread;

 

public class Run {

   public static void main(String[] args) {

      Service service = new Service();

      MyThreadA a = new MyThreadA(service);

      a.start();

      MyThreadB b = new MyThreadB(service);

      b.start();

   }

}

 

methodA begin

分析:線程A不釋放鎖,線程B永遠得不到運行的機會,鎖死了。

解決:同步代碼塊

package com.jvm.thread;

 

public class Service extends Thread {

   Object object1 = new Object();

   public void methodA() {

      synchronized (object1) {

          System.out.println("methodA begin");

          boolean isContinueRun = true;

          while (isContinueRun) {

            

          }

          System.out.println("methodA end");

      }

   }

 

   Object object2 = new Object();

   public void methodB() {

      synchronized (object2) {

          System.out.println("methodB begin");

          System.out.println("methodB end");

      }

   }

}

methodA begin

methodB begin

methodB end

 

 

14、多線程的死鎖

Java線程死鎖是一個經典的多線程問題,因為不同的線程都在等待根本不可能釋放的鎖,從而導致所有的任務都無法繼續完成。在多線程技術中心,“死鎖”是必需避免的,因為這會造成線程的“假死”。

死鎖例子:

package com.jvm.thread;

 

public class DeadThread implements Runnable {

   public String username;

   public Object lock1 = new Object();

   public Object lock2 = new Object();

  

   public void setFlag(String username){

      this.username = username;

   }

  

   @Override

   public void run() {

      if(username.equals("a")){

          synchronized (lock1) {

             try {

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

                Thread.sleep(3000);

             } catch (InterruptedException e) {

                e.printStackTrace();

             }

             synchronized (lock2) {

                System.out.println("lock1 -> lock2");

             }

          }

      }

     

      if(username.equals("b")){

          synchronized (lock2) {

             try {

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

                Thread.sleep(3000);

             } catch (InterruptedException e) {

                e.printStackTrace();

             }

             synchronized (lock1) {

                System.out.println("lock2 -> lock1");

             }

          }

      }

   }

  

   public static void main(String[] args) throws InterruptedException {

      DeadThread deadThread = new DeadThread();

      deadThread.setFlag("a");

      Thread thread1 = new Thread(deadThread);

      thread1.start();

      Thread.sleep(1000);

     

      deadThread.setFlag("b");

      Thread thread2 = new Thread(deadThread);

      thread2.start();

   }

}

 

username = a

username = b

注意:死鎖的實現與嵌套不嵌套沒有關系。

 

 

1/ valotile 關鍵字的作用是什么?缺點是什么?

使變量在多個線程間可見。但valotile關鍵字最致命的缺點是不支持原子性。

package com.jvm.thread;

 

public class PrintString {

       private boolean isContinuePrint = true;

      

       public void setContinuePrint(boolean isContinuePrint) {

              this.isContinuePrint = isContinuePrint;

       }

      

       public boolean isContinuePrint() {

              return isContinuePrint;

       }

      

       public void printStringMethod(){

              try {

                     while (isContinuePrint) {

                           System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName());

                           Thread.sleep(1000);

                     }

              } catch (InterruptedException e) {

                     e.printStackTrace();

              }

       }

      

       public static void main(String[] args) {

              PrintString printString = new PrintString();

              printString.printStringMethod();

              System.out.println("I will stop it! stopThread=" + Thread.currentThread().getName());

              printString.setContinuePrint(false);

       }

}

 

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

分析:main線程一直在處理while循環,沒辦法執行后面的代碼。

解決:使用多線程

package com.jvm.thread;

 

public class PrintString implements Runnable {

       private boolean isContinuePrint = true;

      

       public void setContinuePrint(boolean isContinuePrint) {

              this.isContinuePrint = isContinuePrint;

       }

      

       public boolean isContinuePrint() {

              return isContinuePrint;

       }

      

       public void printStringMethod(){

              try {

                     while (isContinuePrint) {

                           System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName());

                           Thread.sleep(1000);

                     }

              } catch (InterruptedException e) {

                     e.printStackTrace();

              }

       }

      

       @Override

       public void run() {

              printStringMethod();

       }

      

       public static void main(String[] args) {

              PrintString printString = new PrintString();

              new Thread(printString).start();

              System.out.println("I will stop it! stopThread=" + Thread.currentThread().getName());

              printString.setContinuePrint(false);

       }

 

}

 

I will stop it! stopThread=main

run printStringMethod threadName=Thread-0

 

 

package com.jvm.thread;

 

public class PrintString implements Runnable {

       volatile private boolean isContinuePrint = true;

      

       public void setContinuePrint(boolean isContinuePrint) {

              this.isContinuePrint = isContinuePrint;

       }

      

       public boolean isContinuePrint() {

              return isContinuePrint;

       }

      

       public void printStringMethod(){

              try {

                     while (isContinuePrint) {

                           System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName());

                           Thread.sleep(1000);

                     }

              } catch (InterruptedException e) {

                     e.printStackTrace();

              }

       }

      

       @Override

       public void run() {

              printStringMethod();

       }

      

       public static void main(String[] args) {

              PrintString printString = new PrintString();

              new Thread(printString).start();

              System.out.println("I will stop it! stopThread=" + Thread.currentThread().getName());

              printString.setContinuePrint(false);

       }

 

}

 

I will stop it! stopThread=main

run printStringMethod threadName=Thread-0

使用volatile關鍵字,強制從公共內存中讀取變量的值。

 

 

 

 

2/ 線程安全包含哪些方面?

原子性和可見性。Java的同步機制都是圍繞這兩個方面來確保線程安全的。

 

3/關鍵字synchronized和valotile比較?

a/關鍵字valotile是線程同步的輕量級實現,因此valotile性能更好。Valotile只能修飾變量,synchronized修飾方法和代碼塊。

b/多線程訪問valotile不會發生阻塞,而synchronized會。

c/valotile能保證數據的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性,因為它會將私有內存和公共內存中的數據做同步。

d/valotile解決的是變量在多個線程之間的可見性,而synchronized解決的是多個線程之間訪問資源的同步性。

 

3/valotile非原子性?怎么解決?

package com.jvm.thread;

 

public class MyThread extends Thread {

       volatile public static int count;

       private static void addCount(){

              for(int i = 0; i < 100; i++){

                     count++;

              }

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

       }

      

       @Override

       public void run() {

              addCount();

       }

      

       public static void main(String[] args) {

              MyThread[] myThreadArr = new MyThread[100];

              for(int i = 0; i < 100; i++){

                     myThreadArr[i] = new MyThread();

              }

              for(int i = 0; i < 100; i++){

                     myThreadArr[i].start();

              }

       }

}

 

count=100

count=300

count=200

count=400

count=600

count=600

count=700

count=800

count=900

count=1000

count=1100

count=1200

count=1400

count=1300

count=1500

count=1600

count=1700

count=1800

count=2000

count=1900

count=2200

count=2300

count=2200

count=2400

count=2500

count=2600

count=2900

count=2800

count=2700

count=3000

count=3100

count=3200

count=3300

count=3400

count=3600

count=3700

count=3500

count=4100

count=4000

count=3900

count=4300

count=3800

count=4400

count=4600

count=4200

count=4500

count=4800

count=4700

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6605

count=6705

count=6805

count=7005

count=7005

count=7205

count=7305

count=7405

count=7105

count=7505

count=7605

count=7705

count=7805

count=7905

count=8005

count=8105

count=8205

count=8305

count=8405

count=8505

count=8605

count=8705

count=8805

count=8905

count=9005

count=9105

count=9205

count=9305

count=9405

count=9505

count=9605

count=9705

count=9805

count=9905

分析:用圖來演示使用關鍵字valotile時出現非線程安全的原因。

a/ read和load階段:從主內存復制變量到當前線程工作內存;

b/ use和assign階段:執行代碼,改變共享變量值;

c/ store和write階段:用工作內存數據刷新主存對應變量的值。

在多線程環境中,use和assign是多次出現的,但這一操作並不是原子性,也就是在read和assign之后,如果主內存count變量發生修改之后,線程工作內存中的值由於已經加載,不會產生對應的變化,導致私有內存和公共內存中的變量不同步,因此,計算出來的結果和預期不一樣,也就出現了非線程安全問題。

 

 

解決:valotile關鍵字解決的是變量讀取時的可見性問題,但無法保證原子性,因此,對於多個線程訪問同一實例變量還是需要加鎖同步。

package com.jvm.thread;

 

public class MyThread extends Thread {

       public static int count;

       private synchronized static void addCount(){

              for(int i = 0; i < 100; i++){

                     count++;

              }

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

       }

      

       @Override

       public void run() {

              addCount();

       }

      

       public static void main(String[] args) {

              MyThread[] myThreadArr = new MyThread[100];

              for(int i = 0; i < 100; i++){

                     myThreadArr[i] = new MyThread();

              }

              for(int i = 0; i < 100; i++){

                     myThreadArr[i].start();

              }

       }

}

 

count=100

count=200

count=300

count=400

count=500

count=600

count=700

count=800

count=900

count=1000

count=1100

count=1200

count=1300

count=1400

count=1500

count=1600

count=1700

count=1800

count=1900

count=2000

count=2100

count=2200

count=2300

count=2400

count=2500

count=2600

count=2700

count=2800

count=2900

count=3000

count=3100

count=3200

count=3300

count=3400

count=3500

count=3600

count=3700

count=3800

count=3900

count=4000

count=4100

count=4200

count=4300

count=4400

count=4500

count=4600

count=4700

count=4800

count=4900

count=5000

count=5100

count=5200

count=5300

count=5400

count=5500

count=5600

count=5700

count=5800

count=5900

count=6000

count=6100

count=6200

count=6300

count=6400

count=6500

count=6600

count=6700

count=6800

count=6900

count=7000

count=7100

count=7200

count=7300

count=7400

count=7500

count=7600

count=7700

count=7800

count=7900

count=8000

count=8100

count=8200

count=8300

count=8400

count=8500

count=8600

count=8700

count=8800

count=8900

count=9000

count=9100

count=9200

count=9300

count=9400

count=9500

count=9600

count=9700

count=9800

count=9900

count=10000

 

 

4/驗證synchronized具有將線程工作內存的私有變量與公共內存中的變量同步的功能?

關鍵字synchronized可以保證在同一時刻,只有一個線程可以執行某個方法或者代碼快。它包含兩個特性:互斥性和可見性。同步synchronized不僅可以解決一個線程看到對象處於不一致的狀態,還可以保證進入同步方法或者同步代碼塊的每個線程,都能看到由同一個鎖保護之前所有的修改效果。

package com.jvm.thread;

 

public class Service {

       private boolean isCoutinueRun = true;

      

       public void runMethod(){

              while(isCoutinueRun){

                    

              }

              System.out.println("have stoped!");

       }

      

       public void stopMethod(){

              isCoutinueRun = false;

       }

}

 

package com.jvm.thread;

 

public class ThreadA extends Thread{

       private Service service;

 

       public ThreadA(Service service) {

              this.service = service;

       }

 

       @Override

       public void run() {

              service.runMethod();

       }

}

 

package com.jvm.thread;

 

public class ThreadB extends Thread{

       private Service service;

 

       public ThreadB(Service service) {

              this.service = service;

       }

 

       @Override

       public void run() {

              service.stopMethod();

       }

}

 

package com.jvm.thread;

 

public class Run {

       public static void main(String[] args) throws InterruptedException {

              Service service = new Service();

              ThreadA threadA = new ThreadA(service);

              threadA.start();

             

              Thread.sleep(1000);

             

              ThreadB threadB = new ThreadB(service);

              threadB.start();

              System.out.println("have start stop commad");

       }

}

 

have start stop commad

 

分析:出現死循環,各線程間的數據值沒有可視性造成的

解決:synchronized可以具有可視性

package com.jvm.thread;

 

public class Service {

       private boolean isCoutinueRun = true;

      

       public void runMethod(){

              String anyString = new String();

              while(isCoutinueRun){

                     synchronized (anyString) {

                          

                     }

              }

              System.out.println("have stoped!");

       }

      

       public void stopMethod(){

              isCoutinueRun = false;

       }

}

 

 

5/總結?

着重“外練互斥,內修可見”,是掌握多線程並發的重要技術。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM