2.2.7將任意對象作為對象監視器

多個線程調用同一個對象中的不同名稱的synchronized同步方法或synchronized(this)同步代碼塊時,調用的效果就是按順序執行,也就是同步的,阻塞的。
這說明synchronized同步方法或synchronized(this)同步代碼塊分別有兩種作用。

  1. synchronized同步方法
    synchronized同步方法或synchronized(this)同步代碼塊調用呈阻塞狀態。
    同一時間只有一個線程可以執行synchronized同步方法中的代碼

  2. synchronized(this)同步代碼塊
    對其他synchronized同步方法或synchronized(this)同步代碼塊調用呈阻塞狀態
    同一時間只有一個線程可以執行synchronized(this)同步代碼塊中的代碼

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

根據前面對synchronized(this)同步代碼塊的作用總結可知,synchronized(非this對象)格式的作用只有1種:synchronized(非this對象x)同步代碼塊中的代碼

當持有“對象監視器”為同一個對象的前提下,同一時間只有一個線程可以執行synchronized(非this對象x)同步代碼塊中的代碼。

/**
 * @author wuyoushan
 * @date 2017/4/10.
 */
public class Service {

    private String usernameParam;
    private String passwordParam;
    private String anyString=new String();

    public void setUsernamePassword(String username,String password) {
        try{
            synchronized (anyString){
                System.out.println("線程名稱為:"+Thread.currentThread().getName()
                +"在"+System.currentTimeMillis()+"進入同步塊");
                usernameParam=username;
                Thread.sleep(3000);
                passwordParam=password;
                System.out.println("線程名稱為:"+Thread.currentThread().getName()
                +"在"+System.currentTimeMillis()+"離開同步塊");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

/**
 * @author wuyoushan
 * @date 2017/4/4.
 */
public class ThreadA extends Thread{

    private Service service;

    public ThreadA(Service service){
        super();
        this.service=service;
    }

    @Override
    public void run() {
       service.setUsernamePassword("a","aa");
    }
}

/**
 * @author wuyoushan
 * @date 2017/4/4.
 */
public class ThreadB extends Thread{

    private Service service;

    public ThreadB(Service service){
        super();
        this.service=service;
    }

    @Override
    public void run() {
        service.setUsernamePassword("b","bb");
    }
}

/**
 * @author wuyoushan
 * @date 2017/3/20.
 */
public class Run {
    public static void main(String[] args){
        Service service=new Service();
        ThreadA a=new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b=new ThreadB(service);
        b.setName("B");
        b.start();
    }
}

程序的運行結果為:

線程名稱為:A在1492391031020進入同步塊
線程名稱為:A在1492391034020離開同步塊
username:a password:aa
線程名稱為:B在1492391034020進入同步塊
線程名稱為:B在1492391037020離開同步塊
username:b password:bb

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

將Service.java文件代碼更改如下:

/**
 * @author wuyoushan
 * @date 2017/4/10.
 */
public class Service {

    private String usernameParam;
    private String passwordParam;

    public void setUsernamePassword(String username,String password) {
        try{
            String anyString=new String();
            synchronized (anyString){
                System.out.println("線程名稱為:"+Thread.currentThread().getName()
                +"在"+System.currentTimeMillis()+"進入同步塊");
                usernameParam=username;
                Thread.sleep(3000);
                passwordParam=password;
                System.out.println("線程名稱為:"+Thread.currentThread().getName()
                +"在"+System.currentTimeMillis()+"離開同步塊");
                 System.out.println("username:"+usernameParam+" password:"+passwordParam);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

程序的運行結果如下所示:

線程名稱為:A在1492474468701進入同步塊
線程名稱為:B在1492474468702進入同步塊
線程名稱為:A在1492474471702離開同步塊
username:b password:aa
線程名稱為:B在1492474471702離開同步塊
username:b password:bb

可見,使用“synchronized(非this對象x)同步代碼塊”格式進行同步操作時,對象監視器必須是同一個對象。如果不是同一個對象監視器,運行的結果就是異步調用了,就會交叉運行。

下面我們再用另外一個項目來驗證一下使用“synchronized(非this對象x)同步代碼塊”格式時,持有不同的對象監聽器時異步的效果。


/**
 * @author wuyoushan
 * @date 2017/4/10.
 */
public class Service {
    private String anyString=new String();
    public void a() {
        try{
            synchronized (anyString){
                System.out.println("a begin");
                Thread.sleep(3000);
                System.out.println("a end");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    synchronized public void b() {
        System.out.println("b begin");
        System.out.println("b end");

    }
}

/**
 * @author wuyoushan
 * @date 2017/4/4.
 */
public class ThreadA extends Thread{

    private Service service;

    public ThreadA(Service service){
        super();
        this.service=service;
    }

    @Override
    public void run() {
        service.a();
    }
}

/**
 * @author wuyoushan
 * @date 2017/4/4.
 */
public class ThreadB extends Thread{

    private Service service;

    public ThreadB(Service service){
        super();
        this.service=service;
    }

    @Override
    public void run() {
        service.b();
    }
}

/**
 * @author wuyoushan
 * @date 2017/3/20.
 */
public class Run {
    public static void main(String[] args){
        Service service=new Service();
        ThreadA a=new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b=new ThreadB(service);
        b.setName("B");
        b.start();
    }
}

程序的運行結果為:

a begin
b begin
b end
a end

由于對象監視器的不同,所以運行結果就是異步的。
同步代碼塊放在非同步synchronized方法中進行聲明,并不能保證調用方法的線程執行同步/順序性,也就是線程調用方法的順序是無序的,雖然在同步代碼塊中執行的順序是同步的,這樣極易出現“臟讀”問題。

使用“synchronized(非this對象x)同步代碼塊”格式也可以解決“臟讀”問題。但在解決臟讀問題之前,先做一個實驗,實驗的目標是驗證多個線程調用同一個方法是隨機的。

/**
 * @author wuyoushan
 * @date 2017/2/5.
 */
public class MyList{
    private List list=new ArrayList();
    synchronized public void add(String username){
        System.out.println("ThreadName="+Thread.currentThread().getName()
                +"執行了add方法");
        list.add(username);
        System.out.println("ThreadName="+Thread.currentThread().getName()
                +"退出了add方法");
    }
    synchronized public int getSize(){
        System.out.println("ThreadName="+Thread.currentThread().getName()
                +"執行了getSize方法");
        int sizeValue=list.size();
        System.out.println("ThreadName="+Thread.currentThread().getName()
                +"退出了getSize方法");
        return sizeValue;
    }

}

/**
 * @author wuyoushan
 * @date 2017/2/5.
 */
public class ThreadA extends Thread{
    private MyList list;

    public ThreadA( MyList list) {
        super();
        this.list = list;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            list.add("threadA"+(i+1));
        }
    }
}

/**
 * @author wuyoushan
 * @date 2017/2/5.
 */
public class ThreadB extends Thread {
    private MyList list;

    public ThreadB( MyList list) {
        super();
        this.list = list;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            list.add("threadA"+(i+1));
        }
    }
}

/**
 * @author wuyoushan
 * @date 2017/2/5.
 */
public class Run {
    public static void main(String[] args) {
        MyList myList=new MyList();
        ThreadA a=new ThreadA(myList);
        a.setName("A");
        a.start();
        ThreadB b=new ThreadB(myList);
        b.setName("B");
        b.start();
    }
}

程序運行后的結果如下:

ThreadName=B執行了add方法
ThreadName=B退出了add方法
ThreadName=A執行了add方法
ThreadName=A退出了add方法
ThreadName=B執行了add方法
ThreadName=B退出了add方法

從運行的結果來看,同步代碼塊中的代碼是同步打印的,當前線程的“執行”與“退出”是成對出現的。但線程A和線程B的執行卻是異步的,這就有可能出現臟讀的環境。由于線程執行方法的順序不確定,所以當A和B兩個線程執行帶有分支判斷的方法時,就會出現邏輯上的錯誤,有可能出現臟讀。

/**
 * @author wuyoushan
 * @date 2017/4/19.
 */
public class MyOneList {
    private List<String> list=new ArrayList<>();

    synchronized public void add(String data){
        list.add(data);
    }

    synchronized public int getSzie(){
        return list.size();
    }
}

/**
 * @author wuyoushan
 * @date 2017/4/19.
 */
public class MyService {
    public MyOneList addServiceMethod(MyOneList list,String data){
        try{
            if(list.getSzie()<1){
                Thread.sleep(2000);     //模擬從遠程花費2秒取回數據
                list.add(data);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return list;
    }
}

/**
 * @author wuyoushan
 * @date 2017/4/4.
 */
public class ThreadA extends Thread{
    private MyOneList list;

    public ThreadA(MyOneList list){
        super();
        this.list=list;
    }

    @Override
    public void run() {
        MyService service=new MyService();
        service.addServiceMethod(list,"A");
    }
}

/**
 * @author wuyoushan
 * @date 2017/4/4.
 */
public class ThreadB extends Thread{

    private MyOneList list;

    public ThreadB(MyOneList list){
        super();
        this.list=list;
    }

    @Override
    public void run() {
        MyService service=new MyService();
        service.addServiceMethod(list,"B");
    }
}

/**
 * @author wuyoushan
 * @date 2017/3/20.
 */
public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyOneList list=new MyOneList();

        ThreadA a=new ThreadA(list);
        a.setName("A");
        a.start();
        ThreadB b=new ThreadB(list);
        b.setName("B");
        b.start();
        Thread.sleep(6000);
        System.out.println("listSize="+list.getSzie());
    }
}

程序運行后的結果為:

listSize=2

臟讀出現了。出現的原因是兩個線程以異步的方式返回list參數的size()大小。解決辦法就是“同步化”
更改MyService.java類文件代碼:

/**
 * @author wuyoushan
 * @date 2017/4/19.
 */
public class MyService {
    public MyOneList addServiceMethod(MyOneList list,String data){
        try{
            synchronized (list) {
                if (list.getSzie() < 1) {
                    Thread.sleep(2000);     //模擬從遠程花費2秒取回數據
                    list.add(data);
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return list;
    }
}

由于list參數對象在項目中是一份實例,是單例的,而且也正需要對list參數的getSize()方法做同步的調用,所以就對list參數進行同步處理。
程序的運行結果為:

listSize=1

摘選自 java多線程核心編程技術-2.2.7

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容