天天看點

java中的線程同步

java中多線程的同步依靠的是對象鎖機制,synchronized關鍵字的背後就是利用了封鎖來實作對共享資源的互斥通路。

下面以一個簡單的執行個體來進行對比分析。執行個體要完成的工作非常簡單,就是建立10個線程,每個線程都列印從0到99這100個數字,

我們希望線程之間不會出現交叉亂序列印,而是順序地列印。

先來看第一段代碼,這裡我們在run()方法中加入了synchronized關鍵字,希望能對run方法進行互斥通路,但結果并不如我們希望那樣,

這是因為這裡synchronized鎖住的是this對象,即目前運作線程對象本身。代碼中建立了10個線程,而每個線程都持有this對象的對象鎖,這不能實作線程的同步。

代碼

package com.thread.test;

class MyThread implements java.lang.Runnable

{

    private int threadId;

    public MyThread(int id)

    {

        this.threadId = id;

    }

    @Override

    public synchronized void run()

    {

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

        {

            System.out.println("Thread ID: " + this.threadId + " : " + i);

        }

    }

}

public class ThreadDemo

{

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

    {

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

        {

            new Thread(new MyThread(i)).start();

            Thread.sleep(1);

        }

    }

}

從上述代碼段可以得知,要想實作線程的同步,則這些線程必須去競争一個唯一的共享的對象鎖。

基于這種思想,我們将第一段代碼修改如下所示,在建立啟動線程之前,先建立一個線程之間競争使用的Object對象,

然後将這個Object對象的引用傳遞給每一個線程對象的lock成員變量。這樣一來,每個線程的lock成員都指向同一個Object對象。

我們在run方法中,對lock對象使用synchronzied塊進行局部封鎖,這樣就可以讓線程去競争這個唯一的共享的對象鎖,進而實作同步。

代碼

package com.thread.test;

class MyThread implements java.lang.Runnable

{

    private int threadId;

    private Object lock;

    public MyThread(int id, Object obj)

    {

        this.threadId = id;

        this.lock = obj;

    }

    @Override

    public void run()

    {

        synchronized(lock)

        {

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

            {

                System.out.println("Thread ID: " + this.threadId + " : " + i);

            }

        }

    }

}  

public class ThreadDemo

{

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

    {

        Object obj = new Object();

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

        {

            new Thread(new MyThread(i, obj)).start();

            Thread.sleep(1);

        }

    }

}

從第二段代碼可知,同步的關鍵是多個線程對象競争同一個共享資源即可,上面的代碼中是通過外部建立共享資源,然後傳遞到線程中來實作。

我們也可以利用類成員變量被所有類的執行個體所共享這一特性,是以可以将lock用靜态成員對象來實作,代碼如下所示:

代碼

package com.thread.test;

class MyThread implements java.lang.Runnable

{

    private int threadId;

    private static Object lock = new Object();

    public MyThread(int id)

    {

        this.threadId = id;

    }

    @Override

    public void run()

    {

        synchronized(lock)

        {

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

            {

                System.out.println("Thread ID: " + this.threadId + " : " + i);

            }

        }

    }

}

public class ThreadDemo

{

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

    {

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

        {

            new Thread(new MyThread(i)).start();

            Thread.sleep(1);

        }

    }

}

再來看第一段代碼,執行個體方法中加入sychronized關鍵字封鎖的是this對象本身,而在靜态方法中加入sychronized關鍵字封鎖的就是類本身。

靜态方法是所有類執行個體對象所共享的,是以線程對象在通路此靜态方法時是互斥通路的,進而可以實作線程的同步,代碼如下所示:

代碼

package com.thread.test;

class MyThread implements java.lang.Runnable

{

    private int threadId;

    public MyThread(int id)

    {

        this.threadId = id;

    }

    @Override

    public void run()

    {

        taskHandler(this.threadId);

    }

    private static synchronized void taskHandler(int threadId)

    {

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

        {

            System.out.println("Thread ID: " + threadId + " : " + i);

        }

    }

}

public class ThreadDemo

{

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

    {

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

        {

            new Thread(new MyThread(i)).start();

            Thread.sleep(1);

        }

    }

}