多線程筆記(一)

1.1線程安全

是多線程訪問時,採用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程纔可使用。不會出現數據不一致或者數據污染。 線程不安全就是不提供數據訪問保護,有可能出現多個線程先後更改數據造成所得到的數據是髒數據。

public class MyThread extends Thread{
private int count = 5 ;
//synchronized加鎖
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();
}

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

1.2多個線程多個鎖

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

/**
* 關鍵字synchronized取得的鎖都是對象鎖,而不是把一段代碼(方法)當做鎖,
* 所以代碼中哪個線程先執行synchronized關鍵字的方法,哪個線程就持有該方法所屬對象的鎖(Lock),
*
* 在靜態方法上加synchronized關鍵字,表示鎖定.class類,類一級別的鎖(獨佔.class類)。
* @author alienware
*
*/
public class MultiThread {
private int num = 0;

/** static */
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();

}

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

1.3對象鎖的同步和異步

同步:synchronized
同步指兩個或兩個以上隨時間變化的量在變化過程中保持一定的相對關係。同步(英語:Synchronization),指對在一個系統中所發生的事件(event)之間進行協調,在時間上出現一致性與統一化的現象。在系統中進行同步,也被稱爲及時(in time)、同步化的(synchronous、in sync)。
異步asynchronized
異步的概念和同步相對。當一個異步過程調用發出後,調用者不能立刻得到結果。實際處理這個調用的部件在完成後,通過狀態、通知和回調來通知調用者。異步雙方不需要共同的時鐘,也就是接收方不知道發送方什麼時候發送,所以在發送的信息中就要有提示接收方開始接收的信息,如開始位,同時在結束時有停止位。
同步的目的就是爲了線程的安全,對於線程安全,需要滿足兩個特性:原子性和可見性。事例:
/**
* 對象鎖的同步和異步問題
* @author alienware
*
*/
public class MyObject {

public synchronized void method1(){
    try {
        System.out.println(Thread.currentThread().getName());
        Thread.sleep(4000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

/** synchronized */
public void method2(){
        System.out.println(Thread.currentThread().getName());
}

public static void main(String[] args) {

    final MyObject mo = new MyObject();

    /**
     * 分析:
     * t1線程先持有object對象的Lock鎖,t2線程可以以異步的方式調用對象中的非synchronized修飾的方法
     * t1線程先持有object對象的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();

}

}
A線程先持有object對象的Lock鎖,B線程如果在這個時候調用對象中的同步(synchronized)方法則需要等待,也就是同步.
A線程先持有object對象中的鎖,B線程可以已異步的方式調用對象中的非synchronized修飾的方法。

1.4 synchronized關鍵字

synchronized擁有鎖重入的功能,也就是在使用synchronized時,當一個線程得到了一個對象的鎖後,再次請求次對象是可以再次得到該對象的鎖。

/**
* synchronized的重入
* @author alienware
*
*/
public class SyncDubbo1 {

public synchronized void method1(){
    System.out.println("method1..");
    method2();
}
public synchronized void method2(){
    System.out.println("method2..");
    method3();
}
public synchronized void method3(){
    System.out.println("method3..");
}

public static void main(String[] args) {
    final SyncDubbo1 sd = new SyncDubbo1();
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            sd.method1();
        }
    });
    t1.start();
}

}
/**
* synchronized的重入
* @author alienware
*
*/
public class SyncDubbo2 {

static class Main {
    public int i = 10;
    public synchronized void operationSup(){
        try {
            i--;
            System.out.println("Main print i = " + i);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

static class Sub extends Main {
    public synchronized void operationSub(){
        try {
            while(i > 0) {
                i--;
                System.out.println("Sub print i = " + i);
                Thread.sleep(100);      
                this.operationSup();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public static void main(String[] args) {

    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            Sub sub = new Sub();
            sub.operationSub();
        }
    });

    t1.start();
}

}
1.5 synchronized代碼塊

使用synchronized聲明的方法在某些情況下是有弊端的。比如A線程調用同步的方法執行一個很長時間的任務,那麼B線程就必須等待比較長的時間才能執行,這樣的情況下可以使用synchronized代碼塊去優化代碼執行時間,也就是減少鎖的粒度。

/**
* 使用synchronized代碼塊減小鎖的粒度,提高性能
* @author alienware
*
*/
public class Optimize {

public void doLongTimeTask(){
    try {

        System.out.println("當前線程開始:" + Thread.currentThread().getName() + 
                ", 正在執行一個較長時間的業務操作,其內容不需要同步");
        Thread.sleep(2000);

        synchronized(this){
            System.out.println("當前線程:" + Thread.currentThread().getName() + 
                ", 執行同步代碼塊,對其同步變量進行操作");
            Thread.sleep(1000);
        }
        System.out.println("當前線程結束:" + Thread.currentThread().getName() +
                ", 執行完畢");

    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    final Optimize otz = new Optimize();
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            otz.doLongTimeTask();
        }
    },"t1");
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            otz.doLongTimeTask();
        }
    },"t2");
    t1.start();
    t2.start();

}

}
synchronized可以使用任意的對象進行加鎖。
另外特別注意一個問題,不要一個String的常量加鎖,會出現死循環問題。
鎖對象的改變的問題,當使用一個對象進行加鎖時要注意對象本身發生改變的時候,那麼持有的鎖就不同,如果對象本身不發生改變沒那麼依然是同步的,既對象的屬性發生改變。
死鎖問題:死鎖是兩個甚至多個線程被永久阻塞時的一種運行局面,這種局面的生成伴隨着至少兩個線程和兩個或者多個資源。
1.6 volatile關鍵字

volatile關鍵字使系統中所有線程對該關鍵字修飾的變量共享可見,可以禁止線程的工作內存對volatile修飾的變量進行緩存。

public class RunThread extends Thread{

private volatile boolean isRunning = true;
private void setRunning(boolean isRunning){
    this.isRunning = isRunning;
}

public void run(){
    System.out.println("進入run方法..");
    int i = 0;
    while(isRunning == true){
        //..
    }
    System.out.println("線程停止");
}

public static void main(String[] args) throws InterruptedException {
    RunThread rt = new RunThread();
    rt.start();
    Thread.sleep(1000);
    rt.setRunning(false);
    System.out.println("isRunning的值已經被設置了false");
}

}
1.Java提供了volatile關鍵字來保證可見性。 當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內存中讀取新值。 而普通的共享變量不能保證可見性,因爲普通共享變量被修改之後,什麼時候被寫入主存是不確定的,當其他線程去讀取時,此時內存中可能還是原來的舊值,因此無法保證可見性。 2.volatile關鍵雖然有多個線程之間的可見性,但是卻不具備同步性(也就是原子性),可以算上是一個輕量級的synchronized,性能要比synchronized強很多,不會造成阻塞(在很多開源的架構中,比如netty的底層中就大量的使用volatile,可見netty性能一定是非常不錯的。)這裏需要注意:一般volatile用於針對與多個線程可見的變量操作,不能代替synchronized的同步功能。

import java.util.concurrent.atomic.AtomicInteger;

/**
* volatile關鍵字不具備synchronized關鍵字的原子性(同步)
* @author alienware
*
*/
public class VolatileNoAtomic extends Thread{
//private static volatile int count;
private static AtomicInteger count = new AtomicInteger(0);
private static void addCount(){
for (int i = 0; i < 1000; i++) {
//count++ ;
count.incrementAndGet();
}
System.out.println(count);
}

public void run(){
    addCount();
}

public static void main(String[] args) {

    VolatileNoAtomic[] arr = new VolatileNoAtomic[100];
    for (int i = 0; i < 10; i++) {
        arr[i] = new VolatileNoAtomic();
    }

    for (int i = 0; i < 10; i++) {
        arr[i].start();
    }
}

}
volatile關鍵字只有可見性,沒有原子性。要實現原子性建議使用atomic類的系列對象,支持原子性操作(atomic類只保證本身方法原子性,並不保證多次操作的原子性)

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicUse {

private static AtomicInteger count = new AtomicInteger(0);

//多個addAndGet在一個方法內是非原子性的,需要加synchronized進行修飾,保證4個addAndGet整體原子性
/**synchronized*/
public synchronized int multiAdd(){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count.addAndGet(1);
        count.addAndGet(2);
        count.addAndGet(3);
        count.addAndGet(4); //+10
        return count.get();
}


public static void main(String[] args) {

    final AtomicUse au = new AtomicUse();

    List<Thread> ts = new ArrayList<Thread>();
    for (int i = 0; i < 100; i++) {
        ts.add(new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(au.multiAdd());
            }
        }));
    }

    for(Thread t : ts){
        t.start();
    }
}

}

發佈了28 篇原創文章 · 獲贊 7 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章