java多執行緒之synchronized和volatile關鍵字
阿新 • • 發佈:2019-02-07
synchronized同步方法
髒讀
在多個執行緒對同一個物件中的例項變數進行併發訪問的時候,取到的資料可能是被更改過的,稱之為“髒讀”,這就是非執行緒安全的。解決的方法為synchronized關鍵字進行同步,使之操作變成同步而非非同步。
public class PublicVar {
public String username = "A";
public String password = "AA";
synchronized public void setValue(String username, String password) {
try {
this.username = username;
Thread.sleep(5000);
this.password = password;
System.out.println("setValue method thread name="
+ Thread.currentThread().getName() + " username="
+ username + " password=" + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void getValue() {//不加同步將會造成髒讀
System.out.println("getValue method thread name="
+ Thread.currentThread().getName() + " username=" + username
+ " password=" + password);
}
}
public class ThreadA extends Thread {
private PublicVar publicVar;
public ThreadA(PublicVar publicVar) {
super();
this.publicVar = publicVar;
}
@Override
public void run() {
super.run();
publicVar.setValue("B", "BB");
}
}
public class Test {
public static void main(String[] args) {
try {
PublicVar publicVarRef = new PublicVar();
ThreadA thread = new ThreadA(publicVarRef);
thread.start();
Thread.sleep(200);// 列印結果受此值大小影響
publicVarRef.getValue();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
未加同步鎖
加了同步鎖
多個物件監視器多個鎖
多個執行緒訪問多個物件,JVM會建立多個鎖
public class Test {
public static void main(String[] args) {
TestRunable public1 = new TestRunable();
TestRunable public2 = new TestRunable();
ThreadA athread = new ThreadA(public1);
athread.start();
ThreadB bthread = new ThreadA(public2);
bthread.start();
}
}
上面示例是兩個執行緒訪問同一個類的兩個不同例項物件,效果是非同步的方式執行。即時加了同步鎖也是非同步執行,因為建立了兩個物件,將會產生兩把鎖。
鎖重入
當一個執行緒得到一個物件鎖後,再次請求此物件鎖是可以再次得到該物件鎖的。就是在自己已經獲得該物件鎖的前提下,還可以再次獲取自己的鎖。可重入鎖也支援在父子類繼承的環境中。
public class MyThread extends Thread {
@Override
public void run() {
Service service = new Service();
service.service1();
}
}
public class Service {
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");
}
}
public class Run {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
synchronized同步語句塊
同步語句塊的好處
public class Task {
private String getData1;
/**
* synchronized public void doLongTimeTask(){}
* 如果同步鎖在方法上
* A執行緒執行的時候會鎖住3秒鐘,然B執行緒再執行,效率太低
* 所以同步程式碼塊是效率最高的方法
*/
public void doLongTimeTask() {
try {
System.out.println("begin task");
Thread.sleep(3000);
synchronized (this) {
getData1 = Thread.currentThread().getName();
}
System.out.println(getData1);
System.out.println("end task");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class MyThread1 extends Thread {
private Task task;
public MyThread1(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
task.doLongTimeTask();
}
}
public class MyThread2 extends Thread {
private Task task;
public MyThread2(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
task.doLongTimeTask();
}
}
public class Run {
public static void main(String[] args) {
Task task = new Task();
MyThread1 thread1 = new MyThread1(task);
thread1.start();
MyThread2 thread2 = new MyThread2(task);
thread2.start();
}
}
如果同步方法的話,程式跑完大概在6秒鐘左右,A執行緒B執行緒各用時3秒鐘
如果同步語句塊的話,讓耗時的操作非同步執行,那麼程式跑完大概也就3秒鐘,效率提升很高。
一半同步一半非同步
把上面的Task類修改如
在synchronized 中就是同步,不在synchronized 中就是非同步,可以執行看下結果
public class Task {
public void doLongTimeTask() {
for (int i = 0; i < 100; i++) {
System.out.println("nosynchronized threadName="
+ Thread.currentThread().getName() + " i=" + (i + 1));
}
System.out.println("");
synchronized (this) {
for (int i = 0; i < 100; i++) {
System.out.println("synchronized threadName="
+ Thread.currentThread().getName() + " i=" + (i + 1));
}
}
}
}
死鎖
不同的執行緒都在等待不可能釋放的鎖,從而導致所有任務都無法完成,造成執行緒的假死。
public class DealThread 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) {
// TODO Auto-generated catch block
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("按lock2->lock1程式碼順序執行了");
}
}
}
}
}
public class Run {
public static void main(String[] args) {
try {
DealThread t1 = new DealThread();
t1.setFlag("a");
Thread thread1 = new Thread(t1);
thread1.start();
Thread.sleep(100);
t1.setFlag("b");
Thread thread2 = new Thread(t1);
thread2.start();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
互相等待,導致執行緒假死
volatile關鍵字
volatile關鍵字提示執行緒每次從共享記憶體中讀取變數,而不是私有記憶體。
適用場合是在多個執行緒中可以感知例項變數被更改了。
在JVM被設定成-server伺服器模式執行時,為了執行緒執行的效率,執行緒一直在私有堆疊中。其他執行緒更改的例項變數值卻會更新在公共堆疊中。
解決非同步死迴圈
public class RunThread extends Thread {
//volatile 關鍵字 isRunning變數將從公共堆疊中取值
volatile private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
System.out.println("進入run了");
while (isRunning == true) {
}
System.out.println("執行緒被停止了!");
}
}
public class Run {
public static void main(String[] args) {
try {
RunThread thread = new RunThread();
thread.start();
Thread.sleep(1000);
thread.setRunning(false);
System.out.println("已經賦值為false");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
在-server伺服器模式中執行
volatile的非原子性(synchronized的程式碼塊有volatile同步的功能)
volatile關鍵字增加了多執行緒之間例項變數的可見性,但是不能保證同步性
public class Service {
private boolean isContinueRun = true;
public void runMethod() {
String anyString = new String();
while (isContinueRun == true) {
//synchronized 可以使多個執行緒訪問統一資源具有同步性
//可以同步 工作記憶體中的私有變數和公共記憶體中的變數
synchronized (anyString) {
}
}
System.out.println("停下來了!");
}
public void stopMethod() {
isContinueRun = false;
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.runMethod();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.stopMethod();
}
}
public class Run {
public static void main(String[] args) {
try {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.start();
Thread.sleep(1000);
ThreadB b = new ThreadB(service);
b.start();
System.out.println("已經發起停止的命令了!");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
在-server伺服器模式中執行
注意事項
**synchronized關鍵字鎖住變數的時候最好不要用String型別的,要考慮字串常量池的問題
例如:
String str="aaa";
String str1="aaa";
//java中的字串常量池會導致同步失效
//System.out.print(str==str1);//true
//所以最好用 Object o=new Object();
synchronized(str){
//TODO
}
synchronized關鍵字加到static靜態方法上是給Class類加鎖
Class鎖可以對類的所有物件例項起作用。
synchronized關鍵字加到非static靜態方法上是給物件加鎖
此為讀書筆記,還望各位多多指導