1. 程式人生 > >java多執行緒之synchronized和volatile關鍵字

java多執行緒之synchronized和volatile關鍵字

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靜態方法上是給物件加鎖

此為讀書筆記,還望各位多多指導