併發編程總結之多線程基礎

  • 線程安全

  1. 當多個線程訪問訪問某一個類(對象或方法)時,這個類或對象或方法始終能表現出正確的行爲我們想要的結果,那麼這個類(對象或方法)就是線程安全的。
  2. synchronized:可以在任意的對象方法上加鎖,而加鎖的這段代碼稱之爲互斥區或者臨界區

代碼示例說明1

運行main方法,main方法裏有5個線程t1到t5,同一時間啓動去訪問MyThread類的Run方法。

1:不加synchronized關鍵字修飾run()方法的代碼

public class MyThread extends Thread {
    private int count = 5;
    public void run() {
        count--;
        System.out.println(this.currentThread().getName() + " count = " + count);
    }
    public static void main(String[] args) {
        /**
         * 分析:當多個線程訪問myThread的run方法時,以排隊的方式進行處理(這裏排對是按照CPU分配的先後順序而定的), 一個線程想要執行synchronized修飾的方法裏的代碼: 1 嘗試獲得鎖 2
         * 如果拿到鎖,執行synchronized代碼體內容;拿不到鎖,這個線程就會不斷的嘗試獲得這把鎖,直到拿到爲止, 而且是多個線程同時去競爭這把鎖。(也就是會有鎖競爭的問題)
         */
        MyThread myThread = new MyThread();
        Thread t1 = new Thread(myThread, "t1");
        Thread t2 = new Thread(myThread, "t2");
        Thread t3 = new Thread(myThread, "t3");
        Thread t4 = new Thread(myThread, "t4");
        Thread t5 = new Thread(myThread, "t5");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

運行結果如下,沒有出現我們想要的結果,打印出來的線程名是無序的 count值也沒按正常–,運行多次不能保證count打印的值每次一致,因此出現了線程安全問題。

t1 count = 2
t2 count = 2
t5 count = 0
t3 count = 2
t4 count = 1

 

代碼示例說明2

1:當我們加上synchronized關鍵字修飾run()方法後,代碼如下。

public class MyThread extends Thread {
    private int count = 5;
    public synchronized void run() {
        count--;
        System.out.println(this.currentThread().getName() + " count = " + count);
    }
    public static void main(String[] args) {
        /**
         * 分析:當多個線程訪問myThread的run方法時,以排隊的方式進行處理(這裏排對是按照CPU分配的先後順序而定的), 一個線程想要執行synchronized修飾的方法裏的代碼: 1 嘗試獲得鎖 2
         * 如果拿到鎖,執行synchronized代碼體內容;拿不到鎖,這個線程就會不斷的嘗試獲得這把鎖,直到拿到爲止, 而且是多個線程同時去競爭這把鎖。(也就是會有鎖競爭的問題)
         */
        MyThread myThread = new MyThread();
        Thread t1 = new Thread(myThread, "t1");
        Thread t2 = new Thread(myThread, "t2");
        Thread t3 = new Thread(myThread, "t3");
        Thread t4 = new Thread(myThread, "t4");
        Thread t5 = new Thread(myThread, "t5");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

加上synchronized運行結果如下,線程名無序,無論你執行多少次程序,count–的值都是顯示我們想要的正確結果。

t1 count = 4
t3 count = 3
t4 count = 2
t5 count = 1
t2 count = 0

線程安全小結:

當多個線程訪問Mythread的run方法時,以排隊的方式進行處理(排隊的方式是按照CPU分配的餓先後順序而定的),一個線程想要執行synchronized修飾的方法裏的代碼,首先嚐試獲得鎖,如果拿到鎖,執行synchronized中代碼體內容;拿不到鎖,這個線程就會不斷的嘗試獲得這把鎖,直到拿到爲止,而且是多個線程同時去競爭這把鎖,也就是會有競爭鎖的問題。

 


 

  • 多個線程多個鎖

多個線程多個鎖:多個線程,每個線程都可以拿到自己指定的鎖,分別獲得鎖之後,執行synchronized方法體的內容。

代碼示例說明1

1:兩個線程t1,t2分別依次start,訪問兩個對象的synchronized修飾的printNum方法,Code如下:

/**
 * 關鍵字synchronized取得的鎖都是對象鎖,而不是把一段代碼(方法)當做鎖,
 * 所以代碼中哪個線程先執行synchronized關鍵字的方法,哪個線程就持有該方法所屬對象的鎖(Lock),
 * 
 * 在靜態方法上加synchronized關鍵字,表示鎖定.class類,類一級別的鎖(獨佔.class類)。
 * @author xujin
 *
 */
public class MultiThread {
    private int num = 0;
    public synchronized void printNum(String tag) {
        try {
            if (tag.equals("a")) {
                num = 100;
                System.out.println("tag a, set num over!");
                Thread.sleep(1000);
            } else {
                num = 200;
                System.out.println("tag b, set num over!");
            }
            System.out.println("tag " + tag + ", num = " + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    // 注意觀察run方法輸出順序
    public static void main(String[] args) {
        // 兩個不同的對象
        final MultiThread m1 = new MultiThread();
        final MultiThread m2 = new MultiThread();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                m1.printNum("a");
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                m2.printNum("b");
            }
        });
        t1.start();
        t2.start();
    }
}

執行結果如下:

tag a, set num over!
tag b, set num over!
tag b, num = 200
tag a, num = 100

關鍵字synchronized取得的鎖都是對象鎖,而不是把一段代碼(方法)當做鎖,所以代碼中哪個線程先執行synchronized關鍵字的方法,哪個線程就持有該方法所屬對象的鎖(Lock)

 

代碼示例說明2

1:在靜態方法上printNum()加一個synchronized關鍵字修飾的話,那這個線程調用printNum()獲得鎖,就是這個類級別的鎖。這是時候無論你實例化出多少個對象m1,m2都是沒有任何關係的,代碼Demo如下所示:

public class MultiThread {
    // ②修改爲static關鍵字修飾
    private static int num = 0;
    // ①修改爲static修飾該方法
    public static synchronized void printNum(String tag) {
        try {
            if (tag.equals("a")) {
                num = 100;
                System.out.println("tag a, set num over!");
                Thread.sleep(1000);
            } else {
                num = 200;
                System.out.println("tag b, set num over!");
            }
            System.out.println("tag " + tag + ", num = " + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    // 注意觀察run方法輸出順序
    public static void main(String[] args) {
        // 倆個不同的對象
        final MultiThread m1 = new MultiThread();
        final MultiThread m2 = new MultiThread();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                m1.printNum("a");
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                m2.printNum("b");
            }
        });
        t1.start();
        t2.start();
    }
}

運行結果如下,可以看出t1執行完了,然後執行t2,他們之間有一個順序:

tag a, set num over!
tag a, num = 100
tag b, set num over!
tag b, num = 200

多個線程多個鎖小結:

  1. 關鍵字synchronized取得的都是對象鎖,而不是把一段代碼或方法當做,所以示例中代碼中的哪個線程先執行synchronized關鍵字的方法哪個線程就持有該方法對象的鎖,也就是Lock,兩個對象,線程獲得的就是兩個不同的鎖,他們互不影響。
  2. 有一種情況則是相同的鎖,即在靜態方法上加synchronized關鍵字,表示鎖定.class,類一級別的鎖獨佔.class類。

 


 

  • 對象鎖的同步和異步

鎖同步和異步的概念

  1. 同步-synchronized同步的概念就是共享,需要記住共享這個概念,如果不是共享的資源,就沒有必要同步。
  2. 異步-asynchronized異步是相互獨立的,相互之間不受任何約制,類似於http中的Ajax請求。
  3. 同步的目的就是爲了線程安全,其實對於線程安全來說,需要滿足兩個特性:原子性可見性

代碼示例1

public class TestObject {
    /** synchronized */
    public synchronized void method1() {
        try {
            System.out.println(Thread.currentThread().getName());
            //休眠4秒
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void method2() {
        System.out.println(Thread.currentThread().getName());
    }
    public static void main(String[] args) {
        final TestObject mo = new TestObject();
        /**
         * 分析: t1線程先持有TestObject對象的Lock鎖,t2線程可以以異步的方式調用對象中的非synchronized修飾的方法
         * t1線程先持有TestObject對象的Lock鎖,t2線程如果在這個時候調用對象中的同步(synchronized)方法則需等待,也就是同步
         */
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                mo.method1();
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                mo.method2();
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}

運行結果如下,因爲t1,t2兩個線程訪問TestObject對象的mo的method1,method2方法是異步的,所以直接打出:

t2
t1

分析: t1線程若先持有TestObject對象的Lock鎖,t2線程可以以異步的方式調用對象中的非synchronized修飾的方法,這就是異步。

代碼示例2

把上面代碼中的method2,也加上synchronized去修飾,代碼如下:

public class TestObject {
    public synchronized void method1() {
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /** 加上synchronized修飾method2 */
    public synchronized void method2() {
        System.out.println(Thread.currentThread().getName());
    }
    public static void main(String[] args) {
        final TestObject mo = new TestObject();
        /**
         * 分析: t1線程先持有TestObject對象的Lock鎖,t2線程可以以異步的方式調用對象中的非synchronized修飾的方法
         * t1線程先持有TestObject對象的Lock鎖,t2線程如果在這個時候調用對象中的同步(synchronized)方法則需等待,也就是同步
         */
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                mo.method1();
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                mo.method2();
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}

打印結果如下,由於CPU隨機分配,若t1線程先執行,先打印t1,然後t1線程先休眠4s,後釋放了Lock,然後打印t2。

t1
t2

t1線程先持有TestObject對象的Lock鎖,t2線程如果在這個時候調用對象中的同步(synchronized)方法則需等待,也就是同步

 

本文爲轉載博客,作者爲許進,鏈接爲http://xujin.org/bf/bf-multithread/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章