線程

線程 Thread

應用程序以進程爲單位運行,一個進程之內可以分爲一到多個線程

window下可以通過任務管理器查看進程

linux 下可以通過ps -fe

進程、線程都可以並行執行, cpu

---程序1
              ---程序2
                             ---程序1

操作系統中有一個組件叫做任務調度器,將cpu的時間片分給不同的程序使用, 微觀串行(單核),宏觀並行.(多核cpu可以並行)

好處:
1) 多進程,多線程可以讓程序不被阻塞.
2) 充分利用多核cpu的優勢,提高運行效率

1.java中的多線程

Thread 類

創建線程方法1:

Thread t = new Thread() {
    public void run() { // 運行
        // 線程需要執行的代碼
        // 不能拋出檢查異常,只能自己處理檢查異常
    }
};

啓動線程 t.start()

並行(parallel)和併發(concurrent)

創建線程的方法2:

把線程和要執行的代碼分開
Thread 代表線程
Runnable 可運行的(線程要執行的代碼)

Runnable runnable = new Runnable() {
    public void run(){
        // 要執行的代碼
    }
};
Thread t = new Thread( runnable );
t.start();

例子:

/*Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        };*/

        Runnable r = () -> System.out.println("hello");

        // () -> System.out.println("hello");

        Thread t = new Thread( r );
        t.start();

2. 線程中的常見方法

Thread.sleep(long n); // 讓當前線程休眠n毫秒
Thread.currentThread(); // 找到當前線程

main 方法實際是由主線程(main)來調用的

注意 可以使用jconsle 來查看某個java進程中線程的運行情況

實例方法:
start() 讓線程啓動, 只能調用一次,如果調用了多次會出現IllegalThreadStateException

直接調用run和使用start間接調用run的區別:
直接調用run是在主線程中執行了run,沒有啓動新的線程
使用start是啓動新的線程,通過新的線程間接執行run

join() 等待某個線程執行結束,例:

static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1= new Thread(()->{
            System.out.println("t1運行開始");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1運行結束");
            r = 100;
        });
        t1.start();

        /*System.out.println("main開始");
        t1.join();
        System.out.println("main結束");*/
        t1.join();
        System.out.println(r);
    }

join(long n) 表示等待線程結束,但是最多等待n毫秒

其它方法:
getName() 得到線程的名稱
yield() 謙讓

不推薦使用的方法
.stop() 讓線程停止
.suspend() 讓線程暫停
.resume() 讓線程繼續

默認情況下,java進程需要等待所有線程都運行結束,纔會結束.
有一種特殊的線程叫做守護(守護的是主線程)線程,只要主線程運行結束,即使守護線程的代碼沒有執行完,也會跟着主線程一起結束,例:

Thread t1= new Thread(()->{
    System.out.println("守護線程開始執行...");
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("守護線程結束");
});
t1.setDaemon(true); // 設置該線程爲守護線程
t1.start();

Thread.sleep(1000);

interrupt() 可以打斷正在等待的線程(包括sleep, join的等待)例如:

Thread t = new Thread(()->{
    try {
        Thread.sleep(5000); // 被打斷線程會拋出InterruptedException
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("結束 。。。");
});
t.start();

Thread.sleep(1000);
System.out.println("打斷t線程。。。");
t.interrupt();

3. 線程的併發(Concurrent)

synchronized (同步關鍵字)

語法

synchronized(對象) {
    要作爲原子操作代碼
}

用synchronized 解決併發問題:

static int i = 0;
static Object obj = new Object(); // 房間,能容納一個人

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> { // 甲
        for (int j = 0; j < 5000; j++) {
            synchronized (obj) { // 甲會鎖住這個房間
                i++;
            } // 甲從房間出來解開了鎖
        }
    });

    Thread t2 = new Thread(() -> { // 乙
        for (int j = 0; j < 5000; j++) {
            synchronized (obj) { // 乙   在門外等待
                i--;
            } //
        }
    });
    t1.start();
    t2.start();

    t1.join();
    t2.join();
    System.out.println(i);
}

每個對象都有一個自己的monitor(監視器),當一個線程調用synchronized(對象),就相當於進入了這個對象的監視器。要檢查有沒有owner,如果沒有,此線程成爲owner; 但如果已經有owner了,這個線程在entryset的區域等待owner的位置空出來。

成爲owner可以理解爲獲得了對象的鎖

在競爭的時候,是非公平的

synchronized必須是進入同一個對象的monitor 纔有上述的效果
enter description here

4. volatile 易變的

可以用來修飾成員變量和靜態成員變量,他可以防止線程從自己的高速緩存中查找變量的值,必須到主存中獲取它的值。
它保證的是變量在多個線程之間的可見性, 不能保證原子性

static boolean run = true;

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(()->{
        while(run){
            // ....
        }
    });
    t.start();

    Thread.sleep(1000);
    run = false;
}

一個線程對run變量的修改對於另一個線程不可見,導致了另一個線程無法停止
enter description here

synchronized 語句塊既可以保證代碼塊的原子性,也可以保證代碼塊內變量的可見性。但缺點是synchronized是屬於重量級操作,性能會受到影響。

5. synchronized的另外兩種寫法

public synchronized void test() {

}
等價於
public void test() {
    synchronized(this) {

    }
}
class Test{
    public synchronized static void test() {

    }
}
等價於
public static void test() {
    synchronized(Test.class) {

    }
}

6. 線程死鎖

a 線程 獲得 A 對象 鎖
接下來獲取B對象的鎖
b 線程獲得 B對象 鎖
接下來獲取A對象的鎖
例:

Object A = new Object();
Object B = new Object();

Thread a = new Thread(()->{
    synchronized (A) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (B) {
            System.out.println("操作...");
        }
    }
});

Thread b = new Thread(()->{
    synchronized (B) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (A) {
            System.out.println("操作...");
        }
    }
});
a.start();
b.start();

檢測死鎖可以使用 jconsole工具

7. wait() notify() notifyAll()

都屬於Object對象的方法

wait() 等待
notify() 喚醒
它們都是線程之間進行協作的手段

obj.wait(); 讓object監視器的線程等待
obj.notify(); 讓object上正在等待的線程中挑一個喚醒
obj.notifyAll(); 讓object上正在等待的線程全部喚醒

必須獲得此對象的鎖,才能調用這幾個方法

Object obj = new Object();

new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-0線程執行....");
        try {
            obj.wait(); // 讓線程在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-0其它代碼....");
    }
}).start();

new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-1線程執行....");
        try {
            obj.wait(); // 讓線程在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-1其它代碼....");
    }
}).start();

try {
    Thread.sleep(2000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("喚醒obj上其它線程");
synchronized (obj){
//            obj.notify();
    obj.notifyAll();
}

}

wait() 方法實際是會釋放對象的鎖,進入WaitSet等待區,從而讓其他線程就機會獲取對象的鎖。無限制等待,直到notify爲止

wait(long n) 有時限的等待, 到n毫秒後結束等待,或是被notify

面試題:sleep(long n) 睡眠n毫秒, wait(long n) 等待n毫秒
1) sleep是Thread方法,而wait是Object的方法
2) sleep不需要強制和synchronized配合使用,但wait需要和synchronized一起用
3) sleep 在睡眠的同時,不會釋放對象鎖的,但wait在等待的時候會釋放對象鎖。

enter description here

8. 線程的狀態

NEW(新建) 線程剛被創建,但是還沒有調用 start方法

RUNNABLE(可運行) 當調用了start() 方法之後

BLOCKED(阻塞) 當線程進入了monitor監視器區,處於entrySet裏準備競爭鎖的時候,處於阻塞狀態

WAITING(等待) 當調用了對象的wait方法,或調用了線程對象的join方法,進入了WaitSet,處於等待狀態

TIMED_WAITING 當調用wait(long n) join(long n) 進入了WaitSet,處於有限時的等待狀態
當調用sleep(long n) 是讓當前線程放棄cpu的時間片,睡眠一會

TERMINATED (終止)當線程代碼運行結束

五種狀態:
NEW(新建), RUNNABLE(可運行) , RUNNING(正在運行), 阻塞(BLOCKED,WAITING, TIMED_WAITING )TERMINATED(終止)

9. 如何讓兩個線程以固定的順序運行

static Object obj = new Object();
static boolean t2runed = false;// t2是否執行過
// 打印 2, 1
public static void main(String[] args) {

    Thread t1 = new Thread(() -> {
        synchronized (obj) {
            while(!t2runed) { // 如果t2沒有執行過
                try {
                    obj.wait(); // 線程t1 先等一會
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(1);
    });

    Thread t2 = new Thread(()->{
        System.out.println(2);
        synchronized (obj) {
            t2runed = true;
            obj.notify();
        }
    });

    t1.start();
    t2.start();
}
```# 線程

t1  輸出 ++++++++++++++
t2 輸出 -----------------------
t3 輸出  ****************

t1 obj.wait()  flag==1
t2 obj.wait() flag ==2
t3 obj.wait() flag ==3 

jdk 1.5  5.0

## java.util.concurrent.* java併發工具包

### 1. 創建線程的第三種方式
```java
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

與Runnable的區別,1)有返回結果,2)可以拋出檢查異常

創建代碼:

// 代表一個任務對象
// Callable代表線程中要執行的代碼
FutureTask task = new FutureTask(new Callable() {
    @Override
    public Object call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"開始執行");
        Thread.sleep(2000);
        return "ok";
    }
});

// 創建和啓動新線程
new Thread(task).start();

// 獲取返回結果
System.out.println(task.get());

2. 線程池

創建有限的線程資源爲更多的任務提供服務。享元模式

// java中對線程池的抽象:ExecutorService  創建一個固定大小的線程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);

/*for (int i = 0; i < 10; i++) {
    threadPool.submit(()->{
        System.out.println(Thread.currentThread().getName()+"任務執行");
    });
}*/
// 執行帶有返回結果的任務
Future future = threadPool.submit(() -> {
    System.out.println(Thread.currentThread().getName()+"執行計算...");
    Thread.sleep(1000);
    return 10;
});
System.out.println(future.get());

threadPool.shutdown(); // 不接收新任務,當所有任務運行結束,整個線程池關閉

一個核心的ExecutorService的實現類:ThreadPoolExecutor

ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)

corePoolSize 核心線程數目 (最多保留的線程數)
5
maximumPoolSize
10

workQueue 阻塞隊列 如果任務超過了核心線程數,進入隊列進行排隊,直到有空閒的線程
10

如果任務過多,阻塞隊列都放不下了,還會創建新的線程來救急
corePoolSize+救急的線程 <= maximumPoolSize(最大線程數)
21 會拋出拒絕提交任務異常

keepAliveTime 生存時間- 針對救急線程
60
unit 時間單位 秒

// 創建固定大小的線程池
Executors.newFixedThreadPool(2);
核心線程數=最大線程數(沒有救急線程被創建)
阻塞隊列 ×××,可以放任意數量的任務,
適合執行數量有限,長時間運行的任務

// 創建緩衝線程池
Executors.newCachedThreadPool()
核心線程數是0, 最大線程數是Integer的最大值(救急線程可以無限創建)
生存時間是60s
適合任務數比較密集,但每個任務執行時間較短的情況

// 創建單線程線程池
Executors.newSingleThreadExecutor()
使用場景:希望多個任務排隊執行

區別:
Executors.newSingleThreadExecutor() 線程個數始終爲1,不能修改
Executors.newFixedThreadPool(1) 初始時爲1,以後還可以修改

// 帶有日程安排功能的線程池

ScheduledExecutorService service = Executors.newScheduledThreadPool(5);

// 讓任務推遲一段時間執行
// 參數1.任務對象, 參數2,3 推遲的時間
/*service.schedule(()->{
    System.out.println("執行任務...");
}, 10L, TimeUnit.SECONDS);*/

// 以一定的頻率反覆執行任務(任務不會重疊)
// 參數1,任務對象, 參數2,初始推遲時間, 參數3,4 時間間隔和單位
/*service.scheduleAtFixedRate(()->{
    try {
        Thread.sleep(1200);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("hello");
}, 0, 1, TimeUnit.SECONDS);*/

// delay表示從上一個任務結束,到下一個任務開始之間的時間
service.scheduleWithFixedDelay(()->{
    System.out.println("hello");
}, 0, 1, TimeUnit.SECONDS);

//        service.shutdown();

3. 原子操作類

AtomicInteger
AtomicBoolean
...

// 創建原子整數類
private static AtomicInteger i = new AtomicInteger(0);

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int j = 0; j < 5000; j++) {
            i.getAndIncrement();  // 獲取並且自增  i++
//                i.incrementAndGet();  // 自增並且獲取  ++i
        }
    });

    Thread t2 = new Thread(() -> {
        for (int j = 0; j < 5000; j++) {
            i.getAndDecrement(); // 獲取並且自減  i--
        }
    });

    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(i);
}

4. 線程安全集合類

StringBuffer 線程安全
String 不可變類 , 都是線程安全的
Random 線程安全
Vector 實現了List,並且線程安全
Hashtable 實現了Map,並且線程安全

5.0新增的線程安全集合類
ConcurrentHashMap 實現了Map,並且線程安全
ConcurrentSkipListMap 實現了Map(可排序),並且線程安全
CopyOnWriteArrayList 實現了List,並且線程安全

enter description here

enter description here

BlockingQueue 阻塞隊列
隊列 FIFO , first in first out

Queue --> LinkedList

private static BlockingQueue<Product> queue = new ArrayBlockingQueue<>(5);
public static void main(String[] args) {

    // 生產者線程
    new Thread(()->{
        for (int i = 0; i < 10; i++) {
            Product p = new Product(i);
            System.out.println(Thread.currentThread().getName()+"生產了:"+p);
            try {
                queue.put(p);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();

    // 消費者線程
    for (int j = 0; j < 5; j++) {
        new Thread(()->{
            for (int i = 0; i < 2; i++) {
                try {
                    Product p = queue.take();
                    System.out.println(Thread.currentThread().getName()+"消費了:"+p);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

}

5. ThreadLocal

//             線程局部變量
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal() {
@Override        // 初始值
protected Object initialValue() {
    return new SimpleDateFormat("yyyy-MM-dd"); // 存入當前線程
}
};

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
    new Thread(()->{
        try {
            SimpleDateFormat sdf = local.get(); // 獲取本線程自己的局部變量
            Date date = sdf.parse("1951-10-09"); // 每個線程使用的是自己的SimpleDateFormat因此沒有爭用
            System.out.println(Thread.currentThread().getName() + " " + date);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }).start();
}

/*for (int i = 0; i < 10; i++) {
    new Thread(()->{
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date date = sdf.parse("1951-10-09");
            System.out.println(Thread.currentThread().getName() + " " + date);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }).start();
}*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章