Java 基礎(chǔ) 10. Java 多線程

一栅盲、線程簡(jiǎn)介

  • 多任務(wù):用戶(hù)在同一時(shí)間內(nèi),運(yùn)行多個(gè)應(yīng)用程序废恋;
  • 多線程:指程序(一個(gè)進(jìn)程)運(yùn)行時(shí)產(chǎn)生了不止一個(gè)線程谈秫。
    • 進(jìn)程:操作系統(tǒng)中運(yùn)行的程序;
    • 線程:一個(gè)進(jìn)程里面可以有多個(gè)線程鱼鼓,如視頻中拟烫,同時(shí)有聲音,圖像等迄本。
  1. 調(diào)用方法與調(diào)用多線程的區(qū)別:
  • 調(diào)用方法:run() 方法硕淑,需要執(zhí)行的方法體;
  • 調(diào)用多線程:start() 方法,開(kāi)啟多線程的方法體嘉赎。
  1. Process(進(jìn)程) 與 Thread(線程)
1. 程序進(jìn)程置媳、線程
  • 在操作系統(tǒng)中,運(yùn)行的程序公条,就是進(jìn)程拇囊,而進(jìn)程通常采用多進(jìn)程執(zhí)行;
  • 程序:指令和數(shù)據(jù)的有序集合靶橱,本身沒(méi)有任何運(yùn)行的含義寥袭,是靜態(tài)概念;
  • 進(jìn)程:是執(zhí)行程序的一次執(zhí)行過(guò)程关霸,是一個(gè)動(dòng)態(tài)概念传黄,是系統(tǒng)資源分配的單位;
  • 線程:一個(gè)進(jìn)程通常包括多個(gè)線程队寇,線程是 CPU 調(diào)度和執(zhí)行的單位尝江。

注意

  • 很多多線程,是模擬出來(lái)的英上,真正的多線程是指炭序,有多個(gè) CPU啤覆,即多核,如服務(wù)器;如果是模擬出來(lái)的多線程朱庆,即在一個(gè) CPU 的情況下巧颈,在同一個(gè)時(shí)間點(diǎn),CPU 只能執(zhí)行一個(gè)代碼笨觅,因?yàn)榍袚Q的很快,所以耕腾,就有同時(shí)執(zhí)行的錯(cuò)覺(jué)见剩。
2. 線程的核心概念
  • 線程,就是獨(dú)立的執(zhí)行路徑扫俺;
  • 在程序運(yùn)行時(shí)苍苞,即使沒(méi)有自己創(chuàng)建線程,后臺(tái)也會(huì)有多個(gè)線程狼纬,比如主線程羹呵,GC 線程;
  • main() 稱(chēng)之為主線程疗琉,為系統(tǒng)的入口冈欢,用于執(zhí)行整個(gè)程序;
  • 在一個(gè)進(jìn)程中盈简,如果開(kāi)辟了多個(gè)線程凑耻,線程的運(yùn)行是由調(diào)度器(cpu)安排調(diào)度的,調(diào)度器是與操作系統(tǒng)緊密相關(guān)的柠贤,先后順序是不能人為干預(yù)的拳话。
  • 對(duì)同一份資源操作時(shí),會(huì)存在資源搶奪的問(wèn)題种吸,需要加入并發(fā)控制弃衍;
  • 線程會(huì)帶來(lái)額外的開(kāi)銷(xiāo),如 CPU 調(diào)度時(shí)間坚俗,并發(fā)控制開(kāi)銷(xiāo)镜盯;
  • 每個(gè)線程,在自己的工作內(nèi)存交互猖败,內(nèi)存控制不當(dāng)速缆,會(huì)造成數(shù)據(jù)不一致。

二恩闻、線程創(chuàng)建

1. 線程創(chuàng)建三種方式:
  1. 繼承 Thread 類(lèi)(重要)不建議使用:避免 OOP 單繼承局限性
  • 自定義線程類(lèi)繼承 Thread 類(lèi)艺糜;
  • 重寫(xiě) run() 方法,編寫(xiě)線程執(zhí)行體;
  • 創(chuàng)建線程對(duì)象破停,調(diào)用 start() 方法啟動(dòng)線程翅楼。
package com.xxx.demo01;

/**
 * 創(chuàng)建線程方式 1:繼承 Thread 類(lèi)
 * 1.重寫(xiě) run() 方法
 * 2.調(diào)用 start 開(kāi)啟線程
 */
public class TestThread01 extends Thread {
    // 重寫(xiě) run() 方法
    // 線程入口點(diǎn)
    @Override
    public void run() {
        // run 方法線程體
        for (int i = 0; i < 20; i++) {
            System.out.println("run 方法線程 -- " + i);
        }
    }

    // main 線程:主線程
    public static void main(String[] args) {
        // 創(chuàng)建線程對(duì)象
        TestThread01 testThread01 = new TestThread01();
        // 調(diào)用 start() 開(kāi)啟線程
        testThread01.start();
        for (int i = 0; i < 200; i++) {
            System.out.println("main 主線程 -- " + i);
        }
    }
}
  • 運(yùn)行結(jié)果:多線程時(shí),線程交替執(zhí)行

總結(jié):線程不一定立即執(zhí)行真慢,由 CPU 安排調(diào)度毅臊。

  • 實(shí)例:下載網(wǎng)絡(luò)圖片
package com.xxx.demo01;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

/**
 * 多線程同步下載圖片
 * 需下載 Commons IO 包
 */
public class TestThread02 extends Thread {
    private String url; // 網(wǎng)絡(luò)圖片地址
    private String name;    // 保存的文件名

    // 有參構(gòu)造器
    public TestThread02(String url, String name) {
        this.url = url;
        this.name = name;
    }
    // 下載圖片的線程執(zhí)行體
    @Override
    public void run() {
        WebDownload webDownload = new WebDownload();
        webDownload.download(url, name);
        System.out.println("下載了文件名為:" + name);
    }

    public static void main(String[] args) {
        TestThread02 t1 = new TestThread02("圖片地址1", "1.jpg");
        TestThread02 t2 = new TestThread02("圖片地址2", "2.jpg");
        TestThread02 t3 = new TestThread02("圖片地址3", "3.jpg");
        // 開(kāi)啟多線程:實(shí)際執(zhí)行順序,不確定(CPU 調(diào)度)
        t1.start();
        t2.start();
        t3.start();
    }
}

// 下載器
class WebDownload {
    public void download(String url, String name) {
        // 下載方法
        try {
            //調(diào)用 Commons_io 包里面的 copyURLToFile 方法
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO異常黑界,downloader下載方法異常");
        }
    }
}
  • 結(jié)果:

Commons IO:針對(duì)開(kāi)發(fā) IO 功能的工具類(lèi)庫(kù) 下載地址

  • 將下載的 jar 包管嬉,復(fù)制到 lib 包(創(chuàng)建)下,并右鍵 添加為庫(kù)...
  1. 實(shí)現(xiàn) Runnable 接口(推薦使用)
  • 推薦使用 Runnable 對(duì)象朗鸠,因?yàn)?Java 單繼承的局限性蚯撩;
  1. 自定義線程類(lèi),實(shí)現(xiàn) Runnable 接口烛占;
  2. 實(shí)現(xiàn) run() 方法胎挎,編寫(xiě)線程執(zhí)行體;
  3. 創(chuàng)建線程對(duì)象扰楼,調(diào)用 start() 方法呀癣,啟動(dòng)對(duì)象美浦。
package com.xxx.demo01;

/**
 * 創(chuàng)建線程方式 2:實(shí)現(xiàn) Runnable 接口
 * 1.重寫(xiě) run() 方法
 * 2.執(zhí)行線程需要弦赖,丟入 Runnable 接口實(shí)現(xiàn)類(lèi)
 * 3.調(diào)用 start 開(kāi)啟線程
 */
public class TestThread03 implements Runnable {
    // 重寫(xiě) run() 方法
    // 線程入口點(diǎn)
    @Override
    public void run() {
        // run 方法線程體
        for (int i = 0; i < 20; i++) {
            System.out.println("run 方法線程 -- " + i);
        }
    }

    // main 線程:主線程
    public static void main(String[] args) {
        // 創(chuàng)建 Runnable 接口的實(shí)現(xiàn)類(lèi)對(duì)象
        TestThread03 testThread03 = new TestThread03();
        // 代理:創(chuàng)建線程對(duì)象,通過(guò)線程對(duì)象開(kāi)啟線程
//        Thread thread = new Thread(testThread03);
//        thread.start();
        // 簡(jiǎn)化
        new Thread(testThread03).start();
        for (int i = 0; i < 200; i++) {
            System.out.println("main 主線程 -- " + i);
        }
    }
}
  • 運(yùn)行結(jié)果:線程交替執(zhí)行

總結(jié):兩種方式對(duì)比

  • 實(shí)例:并發(fā)問(wèn)題
package com.xxx.demo01;

/**
 * 多個(gè)線程同時(shí)操作同一個(gè)對(duì)象:買(mǎi)火車(chē)票案例
 * 發(fā)現(xiàn)問(wèn)題:多個(gè)線程操作同一個(gè)資源的情況下,線程不安全,數(shù)據(jù)紊亂
 */
public class TestThread04 implements Runnable {
    // 票數(shù)
    private int ticketNums = 10;

    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) {
                break;
            }
            // 模擬延時(shí)(需要捕獲異常)
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // Thread.currentThread().getName() 獲取線程名
            System.out.println(Thread.currentThread().getName() +
                    "-->拿到了第 " + ticketNums-- + " 張票");
        }
    }

    public static void main(String[] args) {
        TestThread04 thread04 = new TestThread04();
        new Thread(thread04, "用戶(hù)1").start();
        new Thread(thread04, "用戶(hù)2").start();
        new Thread(thread04, "用戶(hù)3").start();
    }
}
  • 結(jié)果:發(fā)現(xiàn)問(wèn)題浦辨,數(shù)據(jù)重復(fù)
  • 實(shí)例:龜兔賽跑
package com.xxx.demo01;

/**
 * 模擬龜兔賽跑
 */
public class Race implements Runnable {
    // 勝利者
    private static String winner;

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            // 模擬兔子休息
            if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 判斷比賽是否結(jié)束
            boolean flag = gameOver(i);
            // 如果比賽結(jié)束蹬竖,停止程序
            if (flag) {
                break;
            }
            System.out.println(Thread.currentThread().getName() + " -->跑了 " + i + " 步");
        }
    }

    // 判斷是否完成比賽
    private boolean gameOver(int steps) {
        // 判斷是否有勝利者
        if (winner != null) {   // 不為空:已經(jīng)有勝利者
            return true;
        } else {
            if (steps >= 100) {
                winner = Thread.currentThread().getName();
                System.out.println("Winner is " + winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race, "兔子").start();
        new Thread(race, "烏龜").start();
    }
}
  1. 實(shí)現(xiàn) Callable 接口(了解)
  • 實(shí)現(xiàn) Callable 接口,需要返回值類(lèi)型流酬;
  • 重寫(xiě) call 方法币厕,需要拋出異常;
  • 創(chuàng)建目標(biāo)對(duì)象芽腾;
  • 創(chuàng)建執(zhí)行服務(wù):ExecutorService ser = Executors.newFixedThreadPool(1);
  • 提交執(zhí)行:Future<> result1 = ser.submit(t1);
  • 獲取結(jié)果:boolean r1 = result1.get();
  • 關(guān)閉服務(wù):ser.shutdownNow();
  • 實(shí)例:
package com.xxx.demo02;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

/**
 * 線程創(chuàng)建方式 3:實(shí)現(xiàn) Callable 接口
 */
public class TestCallable implements Callable<Boolean> {

    private String url; // 網(wǎng)絡(luò)圖片地址
    private String name;    // 保存的文件名

    // 有參構(gòu)造器
    public TestCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }

    // 下載圖片的線程執(zhí)行體
    @Override
    public Boolean call() {
        WebDownload webDownload = new WebDownload();
        webDownload.download(url, name);
        System.out.println("下載了文件名為:" + name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("圖片地址1", "1.jpg");
        TestCallable t2 = new TestCallable("圖片地址2", "2.jpg");
        TestCallable t3 = new TestCallable("圖片地址3", "3.jpg");

        // 創(chuàng)建執(zhí)行服務(wù):
        ExecutorService ser = Executors.newFixedThreadPool(3);
        // 提交執(zhí)行:
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);
        // 獲取結(jié)果:
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();

        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);
        // 關(guān)閉服務(wù):
        ser.shutdownNow();
    }
}

// 下載器
class WebDownload {
    public void download(String url, String name) {
        // 下載方法
        try {
            //調(diào)用 Commons_io 包里面的 copyURLToFile 方法
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO異常旦装,downloader下載方法異常");
        }
    }
}
  • Callable 優(yōu)缺點(diǎn):
    • 優(yōu)點(diǎn):可以定義返回值、可以拋出異常摊滔;
    • 缺點(diǎn):實(shí)現(xiàn)方式比較復(fù)雜阴绢。
  1. Thread 和 Runnable 對(duì)比
  • 繼承 Thred 類(lèi):
    • 子類(lèi)繼承 Thread 類(lèi)具備多線程能力;
    • 啟動(dòng)線程:子類(lèi)對(duì)象.start()艰躺;
    • 不建議使用:避免OOP單繼承局限性呻袭。
  • 實(shí)現(xiàn) Runnable 接口
    • 實(shí)現(xiàn) Runnable 接口,具有多線程能力腺兴;
    • 啟動(dòng)線程:傳入目標(biāo)對(duì)象 + Thread 對(duì)象.start()左电;
    • 推薦使用:避免單繼承局限性,靈活方便,方便同一個(gè)對(duì)象被多個(gè)線程使用篓足。
2. 靜態(tài)代理
  • 實(shí)例:實(shí)現(xiàn)靜態(tài)代理段誊,對(duì)比 Thread
package com.xxx.demo02;

/**
 * 靜態(tài)代理
 */
public class StaticProxy {
    public static void main(String[] args) {

//        WeddingCompany weddingCompany = new WeddingCompany(new You());
//        weddingCompany.HappyMarry();
        // 簡(jiǎn)化
        new WeddingCompany(new You()).HappyMarry();
    }
}

// 自定義接口
interface Marry {
    void HappyMarry();
}

// 真實(shí)角色:自定義類(lèi),并實(shí)現(xiàn) Marry 接口
class You implements Marry {
    @Override
    public void HappyMarry() {
        System.out.println("You Marry...");
    }
}

// 代理角色:自定義類(lèi)纷纫,并實(shí)現(xiàn) Marry 接口(代理角色幫助真實(shí)角色執(zhí)行)
class WeddingCompany implements Marry {
    // 代理誰(shuí)枕扫?--->真實(shí)目標(biāo)角色(幫誰(shuí)實(shí)現(xiàn))
    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void HappyMarry() {
        before();
        // 真實(shí)對(duì)象
        this.target.HappyMarry();
        after();
    }

    private void before() {
        System.out.println("前:布置現(xiàn)場(chǎng)");
    }

    private void after() {
        System.out.println("后:收尾款");
    }
}
  • 優(yōu)化:使用線程,Lamda 表達(dá)式辱魁。(線程的實(shí)現(xiàn)原理烟瞧,就是代理模式)
package com.xxx.demo02;

/**
 * 線程中的代理模式
 */
public class StaticProxy02 {
    public static void main(String[] args) {
        // 線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("線程中的代理模式");
            }
        }).start();
        // 簡(jiǎn)化:Lambda 表達(dá)式
        new Thread(() -> System.out.println("線程,Lambda 表達(dá)式")).start();
        // 靜態(tài)代理模式
        new WeddingCompany(new You()).HappyMarry();
    }
}
  • 總結(jié):
    • 真實(shí)對(duì)象和代理對(duì)象染簇,都要實(shí)現(xiàn)同一個(gè)接口参滴;
    • 代理對(duì)象要代理真實(shí)角色。
  • 好處
    • 代理對(duì)象可以做很多真實(shí)對(duì)象做不了的事情锻弓;
    • 真實(shí)對(duì)象專(zhuān)注做自己的事砾赔。
3. Lambda 表達(dá)式
  • λ 希臘字母表中,排序第十一位的字母青灼,英語(yǔ)名稱(chēng)為 Lambda暴心;
  • 避免匿名內(nèi)部類(lèi)定義過(guò)多;
  • 其實(shí)質(zhì)屬于函數(shù)式編程的概念杂拨;
  • 去掉了一堆沒(méi)有意義的代碼专普,只留下核心邏輯。
  • (params) -> expression [表達(dá)式]
  • (params) -> statement [語(yǔ)句]
  • (params) -> {statements}
  • a -> System.out.println("i like lamda-->"+a)
  • new Thread (()->System.out.println(“多線程學(xué)習(xí)弹沽。檀夹。。策橘≌ǘ桑”)).start();
  • 理解 Functional Interface (函數(shù)式接口)丽已,是學(xué)習(xí) Java 8 lambda 表達(dá)式的關(guān)鍵蚌堵。

函數(shù)式接口的定義

  • 任何接口,如果 只包含唯一 1 個(gè)抽象方法沛婴,那么它就是一個(gè)函數(shù)式接口吼畏。
public interface Runnable{
    public abstract void run();
}
  • 對(duì)于函數(shù)式接口,我們可以通過(guò) Lambda 表達(dá)式來(lái)創(chuàng)建該接口的對(duì)象瘸味。

推導(dǎo) Lambda 表達(dá)式

  • 案例 1:
package com.xxx.lambda;

/**
 * 推導(dǎo) Lambda 表達(dá)式
 */
public class TestLambda01 {
    public static void main(String[] args) {
        // 接口方式創(chuàng)建對(duì)象
        ILike like = new Like();
        like.lambda();
    }
}

// 1.定義一下函數(shù)式接口
interface ILike {
    void lambda();
}

// 2.定義接口實(shí)現(xiàn)類(lèi)
class Like implements ILike {
    @Override
    public void lambda() {
        System.out.println("I like lambda...");
    }
}
  • 優(yōu)化一:靜態(tài)內(nèi)部類(lèi)方式
package com.xxx.lambda;

/**
 * 推導(dǎo) Lambda 表達(dá)式
 */
public class TestLambda01 {
    // 3.靜態(tài)內(nèi)部類(lèi)
    static class Like2 implements ILike {
        @Override
        public void lambda() {
            System.out.println("I like lambda 2");
        }
    }

    public static void main(String[] args) {
        // 靜態(tài)內(nèi)部類(lèi)方式
        Like2 like2 = new Like2();
        like2.lambda();
    }
}

// 1.定義一下函數(shù)式接口
interface ILike {
    void lambda();
}
  • 優(yōu)化二:局部?jī)?nèi)部類(lèi)方式
package com.xxx.lambda;

/**
 * 推導(dǎo) Lambda 表達(dá)式
 */
public class TestLambda01 {   
    public static void main(String[] args) {        
        // 4.局部?jī)?nèi)部類(lèi)
        class Like3 implements ILike {
            @Override
            public void lambda() {
                System.out.println("I like lambda 3");
            }
        }
        Like3 like3 = new Like3();
        like3.lambda();
    }
}

// 1.定義一下函數(shù)式接口
interface ILike {
    void lambda();
}
  • 優(yōu)化三:匿名內(nèi)部類(lèi)方式
package com.xxx.lambda;

/**
 * 推導(dǎo) Lambda 表達(dá)式
 */
public class TestLambda01 {
    public static void main(String[] args) {
        // 5.匿名內(nèi)部類(lèi):沒(méi)有類(lèi)的名稱(chēng)宫仗,必須借助接口或者父類(lèi)
        ILike like4 = new ILike() {
            @Override
            public void lambda() {
                System.out.println("I like lambda 4");
            }
        };
        like4.lambda();
    }
}

// 1.定義一下函數(shù)式接口
interface ILike {
    void lambda();
}
  • 最終版:用 Lambda 簡(jiǎn)化
package com.xxx.lambda;

/**
 * 推導(dǎo) Lambda 表達(dá)式
 */
public class TestLambda01 {
    public static void main(String[] args) {
        // 6.用 Lambda 簡(jiǎn)化
        ILike like5 = () -> {
            System.out.println("I like lambda 5");
        };
        like5.lambda();
    }
}

// 1.定義一下函數(shù)式接口
interface ILike {
    void lambda();
}
  • 案例 2:Lambda 表達(dá)式簡(jiǎn)化
package com.xxx.lambda;

/**
 * Lambda 表達(dá)式簡(jiǎn)化
 */
public class TestLambda02 {
    public static void main(String[] args) {
        ILove love = null;
        // 1.Lambda 表達(dá)式簡(jiǎn)化
        love = (int a) -> {
            System.out.println("I love you -> " + a);
        };
        // 簡(jiǎn)化1:去掉參數(shù)類(lèi)型
        love = (a) -> {
            System.out.println("I love you -> " + a);
        };
        // 簡(jiǎn)化2:去掉括號(hào)
        love = a -> {
            System.out.println("I love you -> " + a);
        };
        // 簡(jiǎn)化3:去掉花括號(hào)
        love = a -> System.out.println("I love you -> " + a);

        /*
        總結(jié):
        1.{} 簡(jiǎn)略的條件是,只能有一行代碼,多行代碼旁仿,{} 就不能簡(jiǎn)略
        2.前提是接口為函數(shù)式接口(只能有一個(gè)方法)
        3.多個(gè)參數(shù)也可以去掉參數(shù)類(lèi)型,要去掉就全部去掉,但必須加上()
        */
        love.love(1);
    }
}
// 自定義接口
interface ILove {
    void love(int a);
}
  • lambda 表達(dá)式只有一行代碼才能簡(jiǎn)化成藕夫,多行得用代碼塊孽糖。
    hello = a->System.out.println("hello " + a);
  • 多個(gè)參數(shù)需要括號(hào),要去掉類(lèi)型毅贮,所有參數(shù)都要去掉類(lèi)型办悟。
    hello = (a, b)->System.out.println(a + " Hello " + b);

三、線程狀態(tài)

1. 線程的五大狀態(tài)
2. 線程方法
方法 說(shuō)明
setPriority(int newPriority) 更改線程的 優(yōu)先級(jí)
static void sleep(long millis) 在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程 休眠
void join() 等待該線程終止(插隊(duì)
static void yield() 禮讓)暫停當(dāng)前正在執(zhí)行的線程對(duì)象滩褥,并執(zhí)行其他線程
void interrupt() 中斷線程病蛉,不推薦用這個(gè)方式
boolean isAlive() 測(cè)試線程是否處于活動(dòng)狀態(tài)
  1. 停止線程
  • 實(shí)例:設(shè)置標(biāo)志位,讓線程停止
package com.xxx.state;

/**
 * 線程停止:測(cè)試 stop
 * 1.建議線程正常停止 --> 利用次數(shù),不建議死循環(huán)
 * 2.建議使用標(biāo)志位 --> 設(shè)置一個(gè)標(biāo)志位
 * 3.不要使用 stop 或者 destroy 等過(guò)時(shí)或者 JDK 不建議使用的方法
 */
public class TestStop implements Runnable {
    // 1.設(shè)置一個(gè)標(biāo)志位
    private Boolean flag = true;

    @Override
    public void run() {
        int i = 0;
        // 2.線程體使用該標(biāo)志位
        while (flag) {
            System.out.println("run......Thread" + i++);
        }
    }

    // 3.自定義一個(gè)公開(kāi)的方法瑰煎,停止線程铺然,轉(zhuǎn)換標(biāo)志位
    public void stop() {
        this.flag = false;
    }

    public static void main(String[] args) {
        // 創(chuàng)建線程對(duì)象
        TestStop testStop = new TestStop();
        new Thread(testStop).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("main " + i);
            if (i == 900) {
                // 調(diào)用自定義 stop(),切換標(biāo)志位酒甸,讓線程終止
                testStop.stop();
                System.out.println("線程該停止了魄健!");
            }
        }
    }
}
  1. 線程休眠
  • 實(shí)例:模擬網(wǎng)絡(luò)延時(shí):放大問(wèn)題的發(fā)生性
package com.xxx.state;

/**
 * 模擬網(wǎng)絡(luò)延時(shí):放大問(wèn)題的發(fā)生性
 */
public class TestSleep implements Runnable {
    // 票數(shù)
    private int ticketNums = 10;

    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) {
                break;
            }
            // 模擬延時(shí)(需要捕獲異常)
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // Thread.currentThread().getName() 獲取線程名
            System.out.println(Thread.currentThread().getName() +
                    "-->拿到了第 " + ticketNums-- + " 張票");
        }
    }

    public static void main(String[] args) {
        TestSleep thread = new TestSleep();
        new Thread(thread, "用戶(hù)1").start();
        new Thread(thread, "用戶(hù)2").start();
        new Thread(thread, "用戶(hù)3").start();
    }
}
  • 實(shí)例:模擬倒計(jì)時(shí)
package com.xxx.state;

/**
 * 模擬倒計(jì)時(shí)
 */
public class TestSleep02 {
    public static void main(String[] args) {
        try {
            tenDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 模擬倒計(jì)時(shí)
    public static void tenDown() throws InterruptedException {
        int num = 10;
        while (true) {
            Thread.sleep(1000);
            System.out.println(num--);
            if (num <= 0) {
                break;
            }
        }
    }
}
  • 實(shí)例:每隔一秒,獲取當(dāng)前時(shí)間
package com.xxx.state;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 每隔一秒插勤,獲取當(dāng)前時(shí)間
 */
public class TestSleep03 {
    public static void main(String[] args) {
        // 獲取系統(tǒng)當(dāng)前時(shí)間
        Date startTime = new Date(System.currentTimeMillis());
        while (true) {
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                // 更新當(dāng)前時(shí)間
                startTime = new Date(System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  1. 線程禮讓
  • 禮讓線程沽瘦,讓當(dāng)前正在執(zhí)行的線程暫停,但不阻塞农尖;
  • 將線程從運(yùn)行狀態(tài)轉(zhuǎn)為就緒狀態(tài)析恋;
  • 讓 CPU 重新調(diào)試,禮讓不一定成功盛卡,主要看 CPU助隧。
  • 實(shí)例:
package com.xxx.state;

/**
 * 禮讓線程
 * 禮讓不一定成功,取決于 CPU
 */
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield, "a").start();
        new Thread(myYield, "b").start();
    }
}

class MyYield implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "線程開(kāi)始執(zhí)行");
        Thread.yield(); // 禮讓線程
        System.out.println(Thread.currentThread().getName() + "線程停止執(zhí)行");
    }
}
  • 禮讓成功
圖片.png
  • 禮讓不成功
  1. 線程插隊(duì)
  • Join 合并線程窟扑,只能是當(dāng)前線程執(zhí)行完之后喇颁,才能執(zhí)行其他線程漏健,對(duì)其他線程造成阻塞嚎货;
  • 可以想象成插隊(duì)(強(qiáng)制執(zhí)行)
  • 實(shí)例:
package com.xxx.state;

/**
 * Join 插隊(duì)(強(qiáng)制執(zhí)行)
 */
public class TestJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("線程VIP " + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 啟動(dòng)線程
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();

        // 主線程
        for (int i = 0; i < 500; i++) {
            if (i == 200) {
                thread.join();  // 插隊(duì):需要捕獲異常
            }
            System.out.println("main " + i);
        }
    }
}
3. 線程狀態(tài)觀測(cè)
public static enum Thread.State
extends Enum<Thread.State>

線程狀態(tài)。 線程可以處于以下?tīng)顟B(tài)之一:

  • NEW:尚未啟動(dòng)的線程處于此狀態(tài)蔫浆。
  • RUNNABLE:在Java虛擬機(jī)中執(zhí)行的線程處于此狀態(tài)殖属。
  • BLOCKED:被阻塞等待監(jiān)視器鎖定的線程處于此狀態(tài)。
  • WAITING:正在等待另一個(gè)線程執(zhí)行特定動(dòng)作的線程處于此狀態(tài)瓦盛。
  • TIMED_WAITING:正在等待另一個(gè)線程執(zhí)行動(dòng)作達(dá)到指定等待時(shí)間的線程處于此狀態(tài)洗显。
  • TERMINATED:已退出的線程處于此狀態(tài)。
  • 一個(gè)線程可以在給定時(shí)間點(diǎn)處于一個(gè)狀態(tài)原环。 這些狀態(tài)是不反映任何操作系統(tǒng)線程狀態(tài)的虛擬機(jī)狀態(tài)挠唆。
  • 實(shí)例:
package com.xxx.state;

/**
 * 觀測(cè)線程狀態(tài)
 */
public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("---------------------");
        });
        // 觀測(cè)狀態(tài)
        Thread.State state = thread.getState();
        System.out.println(state);  // NEW
        // 觀察啟動(dòng)后
        thread.start();
        state = thread.getState();
        System.out.println(state);  // RUN
        // 只要線程不終止,就一直輸出狀態(tài)
        while (state != Thread.State.TERMINATED) {
            Thread.sleep(100);
            state = thread.getState();  // 更新線程狀態(tài)
            System.out.println(state);  // 輸出狀態(tài)
        }
        // 線程只開(kāi)啟一次:死亡后的線程不能再啟動(dòng)了嘱吗,啟動(dòng)會(huì)報(bào)異常
        // thread.start();
    }
}
4. 線程優(yōu)先級(jí)
  • Java 提供一個(gè)線程調(diào)度器玄组,來(lái)監(jiān)控程序中啟動(dòng)后,進(jìn)入就緒狀態(tài)的所有線程,線程調(diào)度器按照優(yōu)先級(jí)俄讹,決定應(yīng)該調(diào)度哪個(gè)線程來(lái)執(zhí)行哆致。
  • 線程的優(yōu)先級(jí)用數(shù)字表示,范圍從1~10患膛。
    • 最小優(yōu)先級(jí):Thread.MIN_PRIORITY= 1;
    • 最大優(yōu)先級(jí):Thread.MAX_PRIORITY = 10;
    • 默認(rèn)優(yōu)先級(jí):Thread.NORM_PRIORITY= 5;
  • 使用以下方式獲取或改變優(yōu)先級(jí)
    • getPriority() 摊阀,setPriority(int xxx)

注意

  1. 先設(shè)置優(yōu)先級(jí)再啟動(dòng),優(yōu)先級(jí)的設(shè)定踪蹬,建議在 start() 調(diào)度前胞此;
  2. main 方法的默認(rèn)優(yōu)先級(jí)為 5;
  3. 理論上來(lái)說(shuō)跃捣,優(yōu)先級(jí)越高的越先執(zhí)行豌鹤,哪怕 start() 更晚;
  4. 優(yōu)先級(jí)低枝缔,只意味著獲得調(diào)度的概率低布疙,并不是優(yōu)先級(jí)低就不會(huì)被調(diào)用了,這都是看 CPU 的調(diào)度愿卸。
  • 實(shí)例:
package com.xxx.state;

/**
 * 測(cè)試線程優(yōu)先級(jí):getPriority()
 */
public class TestPriority {
    public static void main(String[] args) {
        // 主線程優(yōu)先級(jí) main(Thread-0 --> 5)
        System.out.println(Thread.currentThread().getName() +
                " --> " + Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        Thread t6 = new Thread(myPriority);

        // 先設(shè)置優(yōu)先級(jí)灵临,再啟動(dòng)
        t1.start();
        t2.setPriority(1);
        t2.start();
        t3.setPriority(4);
        t3.start();
        // MAX_PRIORITY=10 最大優(yōu)先級(jí)
        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();
//        t5.setPriority(-1);   報(bào)錯(cuò)
//        t5.start();
//        t6.setPriority(11);   報(bào)錯(cuò)
//        t6.start();
    }
}

class MyPriority implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() +
                " --> " + Thread.currentThread().getPriority());
    }
}
5. 守護(hù)(daemon)線程
  • 線程分為 用戶(hù)線程守護(hù)線程
  • 虛擬機(jī) 必須確保用戶(hù)線程 執(zhí)行完畢趴荸;
  • 虛擬機(jī) 不用等待守護(hù)線程 執(zhí)行完畢儒溉;
  • 守護(hù)線程,如:后臺(tái)記錄操作日志发钝,監(jiān)控內(nèi)存垃圾回收等待……
  • 實(shí)例:
package com.xxx.state;

/**
 * 守護(hù)線程
 */
public class TestDaemon {
    public static void main(String[] args) {
        Guard guard = new Guard();
        You you = new You();
        Thread thread = new Thread(guard);
        // 默認(rèn) false:表示是用戶(hù)線程顿涣,正常線程都是用戶(hù)線程
        thread.setDaemon(true);
        // 守護(hù)線程啟動(dòng)
        thread.start();
        // 用戶(hù)線程啟動(dòng)
        new Thread(you).start();
    }
}

// 守護(hù)線程
class Guard implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("守護(hù)線程一直運(yùn)行");
        }
    }
}

// 用戶(hù)線程
class You implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("用戶(hù)線程 " + i);
        }
        System.out.println("------用戶(hù)線程結(jié)束------");
    }
}

四、線程同步

1. 介紹
  • 多個(gè)線程操作同一個(gè)資源 酝豪;
  • 并發(fā):同一個(gè)對(duì)象涛碑,被多個(gè)線程同時(shí)操作。
  • 現(xiàn)實(shí)生活中孵淘,我們會(huì)遇到 同—個(gè)資源蒲障,多個(gè)人都想使用 的問(wèn)題,比如:食堂排隊(duì)打飯瘫证,每個(gè)人都想吃飯揉阎,最天然的解決辦法就是:排隊(duì),一個(gè)個(gè)來(lái)背捌。
  • 處理多線程問(wèn)題時(shí)毙籽,多個(gè)線程訪問(wèn)同一個(gè)對(duì)象,并且毡庆,某些線程還想修改這個(gè)對(duì)象坑赡,這時(shí)巡扇,就需要線程同步。
  • 線程同步:其實(shí)就是一種等待機(jī)制垮衷,多個(gè)需要同時(shí)訪問(wèn)此對(duì)象的線程厅翔,進(jìn)入這個(gè) 對(duì)象的等待池 形成隊(duì)列,等待前面線程使用完畢搀突,下一個(gè)線程再使用刀闷。

隊(duì)列和鎖

  • 隊(duì)列:排隊(duì)
  • 鎖:每個(gè)對(duì)象都有把鎖,當(dāng)獲取對(duì)象時(shí)仰迁,獨(dú)占資源甸昏,其他線程必須等待,使用結(jié)束后才釋放徐许。

線程同步:

  • 由于同一進(jìn)程的多個(gè)線程施蜜,共享同一塊存儲(chǔ)空間,在帶來(lái)方便的同時(shí)雌隅,也帶來(lái)了訪問(wèn)沖突問(wèn)題翻默,為了保證數(shù)據(jù),在方法中被訪問(wèn)時(shí)的正確性恰起,在訪問(wèn)時(shí)加入 鎖機(jī)制 synchronized修械。
  • 當(dāng)一個(gè)線程獲得對(duì)象的排它鎖,獨(dú)占資源检盼,其他線程必須等待肯污,使用后釋放鎖即可。
  • 鎖機(jī)制吨枉,存在以下問(wèn)題:
    • 一個(gè)線程持有鎖蹦渣,會(huì)導(dǎo)致其他所有需要此鎖的線程掛起;
    • 在多線程競(jìng)爭(zhēng)下貌亭,加鎖柬唯,釋放鎖,會(huì)導(dǎo)致比較多的上下文切換属提,和調(diào)度延時(shí)权逗,引起性能問(wèn)題美尸;
    • 如果一個(gè)優(yōu)先級(jí)高的線程冤议,等待一個(gè)優(yōu)先級(jí)低的線程釋放鎖,會(huì)導(dǎo)致優(yōu)先級(jí)倒置师坎,引起性能問(wèn)題恕酸。
2. 不安全的線程案例
  • 實(shí)例 1:
package com.xxx.syn;

/**
 * 多線程不安全實(shí)例:不安全買(mǎi)票
 * 線程不安全:有負(fù)數(shù)
 */
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station, "用戶(hù)1").start();
        new Thread(station, "用戶(hù)2").start();
        new Thread(station, "用戶(hù)3").start();
    }
}

class BuyTicket implements Runnable {
    // 票
    private int ticketNums = 10;
    // 外部停止標(biāo)志
    private Boolean flag = true;

    @Override
    public void run() {
        // 買(mǎi)票
        while (flag) {
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 買(mǎi)票
    private void buy() throws InterruptedException {
        // 判斷是否有票
        if (ticketNums <= 0) {
            flag = false;
            return;
        }
        // 模擬延時(shí)
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName() +
                "拿到了第 " + ticketNums-- + " 張票");
    }
}
  • 實(shí)例 2:
package com.xxx.syn;

/**
 * 不安全取錢(qián):兩個(gè)人同時(shí)取一個(gè)賬戶(hù)的錢(qián)
 */
public class UnsafeBank {
    public static void main(String[] args) {
        // 賬戶(hù)
        Account account = new Account(100, "個(gè)人賬戶(hù)");
        Drawing youA = new Drawing(account, 50, "YouA");
        Drawing youB = new Drawing(account, 100, "YouB");
        youA.start();
        youB.start();
    }
}

//賬戶(hù)
class Account {
    int money;  // 余額
    String cardName;    // 卡名

    public Account(int money, String cardName) {
        this.money = money;
        this.cardName = cardName;
    }
}

// 銀行:模擬取款
class Drawing extends Thread {
    // 賬戶(hù)
    Account account;
    // 取錢(qián)
    int drawingMoney;
    // 手里的錢(qián)
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        // super(name) =  父類(lèi)構(gòu)造方法(name)
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    // 取錢(qián)
    @Override
    public void run() {
        // 判斷有沒(méi)有錢(qián)
        if (account.money - drawingMoney < 0) {
            System.out.println(Thread.currentThread().getName() + " 錢(qián)不夠,取不了胯陋!");
            return;
        }
        // 延時(shí):可以放大問(wèn)題的發(fā)生性
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 卡內(nèi)余額 = 余額 - 取的錢(qián)
        account.money = account.money - drawingMoney;
        // 手里的錢(qián)
        nowMoney = nowMoney + drawingMoney;
        System.out.println(account.cardName + "余額為:" + account.money);
        // Thread.currentThread().getName() = this.getName()
        System.out.println(this.getName() + "手里的錢(qián):" + nowMoney);
    }
}
  • 實(shí)例 3:
package com.xxx.syn;

import java.util.ArrayList;
import java.util.List;

/**
 * 線程不安全的集合
 */
public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> list.add(Thread.currentThread().getName())).start();
        }
        // 延時(shí)
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}
3. 同步方法
  • 由于可以通過(guò) private 關(guān)鍵字蕊温,來(lái)保證數(shù)據(jù)對(duì)象袱箱,只能被方法訪問(wèn),所以只需要針對(duì)方法义矛,提岀一套機(jī)制发笔,這套機(jī)制就是: synchronized 關(guān)鍵字,它包括兩種用法: synchronized 方法synchronized 塊凉翻。

    • 同步方法:public synchronized void method (int args) {}
  • synchronized 方法了讨,控制對(duì) “對(duì)象” 的訪問(wèn),每個(gè)對(duì)象對(duì)應(yīng)一把鎖制轰,每個(gè) synchronized 方法前计,都必須獲得調(diào)用該方法的對(duì)象的鎖,才能執(zhí)行垃杖,否則線程會(huì)阻塞男杈,方法一旦執(zhí)行,就獨(dú)占該鎖调俘,直到該方法返回伶棒,才釋放鎖,后面被阻塞的線程彩库,才能獲得這個(gè)鎖苞冯,繼續(xù)執(zhí)行。

    • 缺陷:若將一個(gè)大的方法侧巨,申明為 synchronized 將會(huì)影響效率舅锄。
  • 實(shí)例 1:同步方法

package com.xxx.syn;

/**
 * 安全買(mǎi)票
 * 同步方法:synchronized
 */
public class SafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station, "用戶(hù)1").start();
        new Thread(station, "用戶(hù)2").start();
        new Thread(station, "用戶(hù)3").start();
    }
}

class BuyTicket implements Runnable {
    // 票
    private int ticketNums = 10;
    // 外部停止標(biāo)志
    private Boolean flag = true;

    @Override
    public void run() {
        // 買(mǎi)票
        while (flag) {
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 買(mǎi)票
    // synchronized 同步方法,鎖的是 this
    private synchronized void buy() throws InterruptedException {
        // 判斷是否有票
        if (ticketNums <= 0) {
            flag = false;
            return;
        }
        // 模擬延時(shí)
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName() +
                "拿到了第 " + ticketNums-- + " 張票");
    }
}
4. 同步塊
  • 同步塊:synchronized (Obj) {}
  • Obj 稱(chēng)之為同步監(jiān)視器
    • Obj 可以是任何對(duì)象司忱,但是推薦使用共享資源皇忿,作為同步監(jiān)視器。
    • 同步方法中坦仍,無(wú)需指定同步監(jiān)視器鳍烁,因?yàn)橥椒椒ǖ耐奖O(jiān)視器,就是 this繁扎,就是這個(gè)對(duì)象本身幔荒,或者是 class。
  • 同步監(jiān)視器的執(zhí)行過(guò)程:
    1. 第一個(gè)線程訪問(wèn)梳玫,鎖定同步監(jiān)視器爹梁,執(zhí)行其中代碼;
    2. 第二個(gè)線程訪問(wèn)提澎,發(fā)現(xiàn)同步監(jiān)視器被鎖定姚垃,無(wú)法訪問(wèn);
    3. 第一個(gè)線程訪問(wèn)完畢盼忌,解鎖同步監(jiān)視器积糯;
    4. 第二個(gè)線程訪問(wèn)掂墓,發(fā)現(xiàn)同步監(jiān)視器沒(méi)有鎖,然后鎖定并訪問(wèn)看成。
  • 同步塊:鎖的對(duì)象就是變化的量君编,需要增、刪川慌、改的對(duì)象
  • 實(shí)例 2:同步塊
package com.xxx.syn;

/**
 * 安全取錢(qián):兩個(gè)人同時(shí)取一個(gè)賬戶(hù)的錢(qián)
 * 同步塊:synchronized (Obj) {}
 * 鎖的對(duì)象就是變化的量,需要增啦粹、刪、改的對(duì)象
 */
public class SafeBank {
    public static void main(String[] args) {
        // 賬戶(hù)
        Account account = new Account(1000, "個(gè)人賬戶(hù)");
        Drawing youA = new Drawing(account, 50, "YouA");
        Drawing youB = new Drawing(account, 100, "YouB");
        youA.start();
        youB.start();
    }
}

//賬戶(hù)
class Account {
    int money;  // 余額
    String cardName;    // 卡名

    public Account(int money, String cardName) {
        this.money = money;
        this.cardName = cardName;
    }
}

// 銀行:模擬取款
class Drawing extends Thread {
    // 賬戶(hù)
    Account account;
    // 取錢(qián)
    int drawingMoney;
    // 手里的錢(qián)
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        // super(name) =  父類(lèi)構(gòu)造方法(name)
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    // 取錢(qián)
    // synchronized 默認(rèn)鎖的是 this
    @Override
    public void run() {
        // 同步塊:鎖的對(duì)象就是變化的量,需要增窘游、刪唠椭、改的對(duì)象
        synchronized (account) {
            // 判斷有沒(méi)有錢(qián)
            if (account.money - drawingMoney < 0) {
                System.out.println(Thread.currentThread().getName() + " 錢(qián)不夠,取不了忍饰!");
                return;
            }
            // 延時(shí):可以放大問(wèn)題的發(fā)生性
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 卡內(nèi)余額 = 余額 - 取的錢(qián)
            account.money = account.money - drawingMoney;
            // 手里的錢(qián)
            nowMoney = nowMoney + drawingMoney;
            System.out.println(account.cardName + "余額為:" + account.money);
            // Thread.currentThread().getName() = this.getName()
            System.out.println(this.getName() + "手里的錢(qián):" + nowMoney);
        }
    }
}
  • 方法里面有需要修改的內(nèi)容贪嫂,才需要鎖,鎖的太多艾蓝,會(huì)浪費(fèi)資源力崇。
  • 實(shí)例 3:同步塊,線程安全的集合
package com.xxx.syn;

import java.util.ArrayList;
import java.util.List;

/**
 * 線程安全的集合
 * 同步塊:synchronized (Obj) {}
 */
public class SafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                // 同步塊
                synchronized (list) {
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        // 延時(shí)
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

JUC 安全集合類(lèi)型擴(kuò)充

  • 實(shí)例:
package com.xxx.syn;

import java.util.concurrent.CopyOnWriteArrayList;

/**
 * concurrent 并發(fā):測(cè)試 JUC安全類(lèi)型的集合
 */
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
            }).start();
        }
        // 延時(shí)
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}
5. 死鎖
  • 多個(gè)線程赢织,各自占有一些共享資源亮靴,并且,互相等待其他線程占有的資源于置,才能運(yùn)行茧吊,而導(dǎo)致兩個(gè)或者多個(gè)線程,都在等待對(duì)方釋放資源八毯,都停止執(zhí)行的情形搓侄。某一個(gè)同步塊,同時(shí)擁有 兩個(gè)以上對(duì)象的鎖 時(shí),就可能會(huì)發(fā)生 死鎖 的問(wèn)題。
  • 實(shí)例:程序鎖死
package com.xxx.syn;

/**
 * 死鎖:多個(gè)線程互相抱著對(duì)方需要的資源懈息,然后形成僵持
 * 解決:一個(gè)鎖只鎖一個(gè)對(duì)象
 */
public class DeadLock {
    public static void main(String[] args) {
        Makeup g1 = new Makeup(0, "girl001");
        Makeup g2 = new Makeup(1, "girl002");
        g1.start();
        g2.start();
    }
}

// 口紅
class Lipstick {
}

// 鏡子
class Mirror {
}

// 化妝
class Makeup extends Thread {
    // 需要的資源只有一份,用 static 保證只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();
    // 選擇
    int choice;
    // 使用化妝品的人
    String girlName;

    public Makeup(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        // 化妝
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 化妝:互相持有對(duì)方的鎖,就是需要拿到對(duì)方的資源
    private void makeup() throws InterruptedException {
        if (choice == 0) {
            // 獲得口紅的鎖
            synchronized (lipstick) {
                System.out.println(this.girlName + "獲得口紅的鎖");
                // 延時(shí)
                Thread.sleep(1000);
                // 1 秒后儿奶,獲得鏡子的鎖
                synchronized (mirror) {
                    System.out.println(this.girlName + "獲得鏡子的鎖");
                }
            }
        } else {
            // 獲得鏡子的鎖
            synchronized (mirror) {
                System.out.println(this.girlName + "獲得鏡子的鎖");
                // 延時(shí)
                Thread.sleep(2000);
                // 2 秒后,獲得口紅的鎖
                synchronized (lipstick) {
                    System.out.println(this.girlName + "獲得口紅的鎖");
                }
            }
        }
    }
}
  • 實(shí)例:解決死鎖問(wèn)題
package com.xxx.syn;

/**
 * 死鎖:多個(gè)線程互相抱著對(duì)方需要的資源,然后形成僵持
 * 解決:一個(gè)鎖只鎖一個(gè)對(duì)象
 */
public class DeadLock {
    public static void main(String[] args) {
        Makeup g1 = new Makeup(0, "girl001");
        Makeup g2 = new Makeup(1, "girl002");
        g1.start();
        g2.start();
    }
}

// 口紅
class Lipstick {
}

// 鏡子
class Mirror {
}

// 化妝
class Makeup extends Thread {
    // 需要的資源只有一份,用 static 保證只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();
    // 選擇
    int choice;
    // 使用化妝品的人
    String girlName;

    public Makeup(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        // 化妝
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 化妝:互相持有對(duì)方的鎖,就是需要拿到對(duì)方的資源
    private void makeup() throws InterruptedException {
        if (choice == 0) {
            // 獲得口紅的鎖
            synchronized (lipstick) {
                System.out.println(this.girlName + "獲得口紅的鎖");
                // 延時(shí)
                Thread.sleep(1000);
            }
            // 1 秒后云石,獲得鏡子的鎖
            synchronized (mirror) {
                System.out.println(this.girlName + "獲得鏡子的鎖");
            }
        } else {
            // 獲得鏡子的鎖
            synchronized (mirror) {
                System.out.println(this.girlName + "獲得鏡子的鎖");
                // 延時(shí)
                Thread.sleep(2000);
            }
            // 2 秒后,獲得口紅的鎖
            synchronized (lipstick) {
                System.out.println(this.girlName + "獲得口紅的鎖");
            }
        }
    }
}

避免死鎖的辦法

  • 產(chǎn)生死鎖的四個(gè)必要條件
    1. 互斥條件:一個(gè)資源毎次只能被一個(gè)進(jìn)程使用白指;
    2. 請(qǐng)求與保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí)留晚,對(duì)已獲得的資源保持不放;
    3. 不剝奪條件∶進(jìn)程已獲得的資源告嘲,在末使用完之前错维,不能強(qiáng)行剝奪;
    4. 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系橄唬。
  • 上面列出了死鎖的四個(gè)必要條件赋焕,只要想辦法破其中的任意一個(gè),或多個(gè)條件仰楚,就可以避免死鎖發(fā)生隆判。
6. Lock(鎖)
  • 從 JDK 5.0 開(kāi)始,Java 提供了更強(qiáng)大的線程同步機(jī)制:通過(guò)顯式定義同步鎖對(duì)象僧界,來(lái)實(shí)現(xiàn)同步侨嘀。同步鎖使用 Lock 對(duì)象充當(dāng)。
  • java.util.concurrent.locks.Lock 接口捂襟,是控制多個(gè)線程咬腕,對(duì)共享資源進(jìn)行訪問(wèn)的工具。鎖葬荷,提供了對(duì)共享資源的獨(dú)占訪問(wèn)涨共,每次只能有一個(gè)線程,對(duì) Lock 對(duì)象加鎖宠漩,線程開(kāi)始訪問(wèn)共享資源之前举反,應(yīng)先獲得 Lock 對(duì)象。
  • ReentrantLock 類(lèi)扒吁,實(shí)現(xiàn)了 Lock火鼻,它擁有與 synchronized 相同的并發(fā)性和內(nèi)存語(yǔ)義,在實(shí)現(xiàn)線程安全的控制中雕崩,比較常用的是 ReentrantLock凝危,可以顯式加鎖、釋放鎖晨逝。
  • 鎖代碼格式
class A{
    private final ReentrantLock lock = new ReentrantLock();
    public void m(){
        lock.lock();
        try{
            // 保證線程安全的代碼
        }
        finally{
            lock.unlock();
            // 如果同步代碼有異常蛾默,要將unlock()寫(xiě)入finally語(yǔ)句塊
        } 
    }
}
  • 實(shí)例:測(cè)試 Lock 鎖
package com.xxx.gaoji;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 測(cè)試 Lock 鎖
 */
public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}

class TestLock2 implements Runnable {
    // 票
    private int ticketNums = 10;
    // 定義 Lock 鎖
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                // 加鎖
                lock.lock();
                if (ticketNums > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                } else {
                    break;
                }
            } finally {
                // 解鎖
                lock.unlock();
            }
        }
    }
}
7. synchroized 與 Lock 對(duì)比
  • Lock 是顯式鎖 (手動(dòng)開(kāi)啟和關(guān)閉鎖,別忘記關(guān)閉鎖)捉貌, synchronized 是隱式鎖支鸡, 出了作用域自動(dòng)釋放;
  • Lock 只有代碼塊鎖趁窃,synchronized 有代碼塊鎖和方法鎖牧挣;
  • 使用 Lock 鎖,JVM 將花費(fèi)較少的時(shí)間來(lái)調(diào)度線程醒陆,性能更好瀑构,并且具有更好的擴(kuò)展性 (提供更多的子類(lèi));
  • 優(yōu)先使用順序:
    • Lock > 同步代碼塊 (已經(jīng)進(jìn)入了方法體刨摩,分配了相應(yīng)資源) > 同步方法 (在方法體之外)

五寺晌、線程通信問(wèn)題

  • 應(yīng)用場(chǎng)景:生產(chǎn)者和消費(fèi)者問(wèn)題
    • 假設(shè)倉(cāng)庫(kù)中只能存放一件產(chǎn)品世吨,生產(chǎn)者將生產(chǎn)出來(lái)的產(chǎn)品放入倉(cāng)庫(kù),消費(fèi)者將倉(cāng)庫(kù)中產(chǎn)品取走消費(fèi)呻征;
    • 如果倉(cāng)庫(kù)中沒(méi)有產(chǎn)品耘婚,則生產(chǎn)者將產(chǎn)品放入倉(cāng)庫(kù),否則停止生產(chǎn)并等待陆赋,直到倉(cāng)庫(kù)中的產(chǎn)品被消費(fèi)者取走為止沐祷;
    • 如果倉(cāng)庫(kù)中放有產(chǎn)品,則消費(fèi)者可以將產(chǎn)品取走消費(fèi)攒岛,否則停止消費(fèi)并等待赖临,直到倉(cāng)庫(kù)中再次放入產(chǎn)品為止。
1. 線程通信方法
  • Java提供了幾個(gè)方法解決線程之間的通信問(wèn)題灾锯。
方法名 作用
wait() 表示線程一直等待兢榨,直到其他線程通知,與 sleep 不同挠进,會(huì)釋放鎖色乾。
wait(long timeout) 指定等待的毫秒數(shù)。
notify() 喚醒一個(gè)處于等待狀態(tài)的線程领突。
notifyAll() 喚醒同一個(gè)對(duì)象上暖璧,所有調(diào)用 wait() 方法的線程,優(yōu)先級(jí)別高的線程君旦,優(yōu)先調(diào)度澎办。
  • 注意:均是 Object 類(lèi)的方法,都只能在同步方法金砍,或者同步代碼塊中使用局蚀,否則會(huì)拋出異常 IIIegalMonitorStateException
  • 這是一個(gè)線程同步問(wèn)題恕稠,生產(chǎn)者和消費(fèi)者共享同一個(gè)資源琅绅,并且生產(chǎn)者和消費(fèi)者之間相互依賴(lài),互為條件
    • 對(duì)于生產(chǎn)者鹅巍,沒(méi)有生產(chǎn)產(chǎn)品之前千扶,要通知消費(fèi)者等待。而生產(chǎn)了產(chǎn)品之后骆捧,又需要馬上通知消費(fèi)者消費(fèi)澎羞;
    • 對(duì)于消費(fèi)者,在消費(fèi)之后敛苇,要通知生產(chǎn)者已經(jīng)結(jié)束消費(fèi)妆绞,需要生產(chǎn)新的產(chǎn)品以供消費(fèi);
    • 在生產(chǎn)者消費(fèi)者問(wèn)題中,僅有 synchronized 是不夠的:
      • synchronized 可阻止并發(fā)更新同一個(gè)共享資源括饶,實(shí)現(xiàn)了同步株茶;
      • synchronized 不能用來(lái)實(shí)現(xiàn)不同線程之間的消息傳遞(通信)。
2. 線程通信問(wèn)題解決方式

解決方式 1:管程法

  • 并發(fā)協(xié)作模型巷帝,生產(chǎn)者/消費(fèi)者模式 —> 管程法:
    • 生產(chǎn)者:負(fù)責(zé)生產(chǎn)數(shù)據(jù)的模塊(可能是方法忌卤,對(duì)象扫夜,線程楞泼,進(jìn)程);
    • 消費(fèi)者:負(fù)責(zé)處理數(shù)據(jù)的模塊(可能是方法笤闯,對(duì)象堕阔,線程,進(jìn)程)颗味;
    • 緩沖區(qū):消費(fèi)者不能直接使用生產(chǎn)者的數(shù)據(jù)超陆,他們之間有個(gè) 緩沖區(qū)
  • 生產(chǎn)者將生產(chǎn)好的數(shù)據(jù)放入緩沖區(qū)浦马,消費(fèi)者從緩沖區(qū)拿出數(shù)據(jù)时呀。
  • 實(shí)例:管程法
package com.xxx.gaoji;

/**
 * 測(cè)試:生產(chǎn)者消費(fèi)者模型-->利用緩沖區(qū)解決:管程法
 * 生產(chǎn)者、消費(fèi)者晶默、產(chǎn)品谨娜、緩沖區(qū)
 */
public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();
        new Productor(container).start();
        new Consumer(container).start();
    }
}

// 生產(chǎn)者
class Productor extends Thread {
    // 緩沖區(qū)
    SynContainer container;

    public Productor(SynContainer container) {
        this.container = container;
    }

    // 生產(chǎn)
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Product(i));
            System.out.println("生產(chǎn)了 " + i + " 件商品");
        }
    }
}

// 消費(fèi)者
class Consumer extends Thread {
    // 緩沖區(qū)
    SynContainer container;

    public Consumer(SynContainer container) {
        this.container = container;
    }

    // 消費(fèi)
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消費(fèi)了--> " + container.pop().id + " 件商品");
        }
    }
}

// 產(chǎn)品
class Product {
    // 產(chǎn)品編號(hào)
    int id;

    public Product(int id) {
        this.id = id;
    }
}

// 緩沖區(qū)
class SynContainer {
    // 需要一個(gè)容器大小
    Product[] products = new Product[10];
    // 容器計(jì)數(shù)器
    int count = 0;

    // 生產(chǎn)者放入產(chǎn)品
    public synchronized void push(Product product) {
        // 如果容器滿(mǎn)了,就需要等待消費(fèi)者消費(fèi)
        /*
        如果是 if 的話磺陡,假如消費(fèi)者1 消費(fèi)了最后一個(gè)趴梢,
        這時(shí) index 變成 0 此時(shí)釋放鎖,被消費(fèi)者2 拿到币他,而不是生產(chǎn)者拿到坞靶,
        這時(shí)消費(fèi)者的 wait 是在 if 里,所以它就直接去消費(fèi) index-1 下標(biāo)越界蝴悉,
        如果是 while 就會(huì)再去判斷一下 index 得值是不是變成 0 了
        */
        while (count == products.length) {
            // 生產(chǎn)等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果沒(méi)有滿(mǎn)彰阴,就需要放入產(chǎn)品
        products[count] = product;
        count++;
        // 通知消費(fèi)者消費(fèi)
        this.notify();
    }

    // 消費(fèi)者消費(fèi)產(chǎn)品
    public synchronized Product pop() {
        // 判斷能否消費(fèi)
        while (count <= 0) {
            // 等待生產(chǎn)者生產(chǎn),消費(fèi)者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果可以消費(fèi)
        count--;
        Product product = products[count];
        // 消費(fèi)完了拍冠,通知生產(chǎn)者生產(chǎn)
        this.notify();
        return product;
    }
}

解決方式 2:信號(hào)燈法

  • 并發(fā)協(xié)作模型:生產(chǎn)者/消費(fèi)者模式 —> 信號(hào)燈法尿这。
  • 實(shí)例:
package com.xxx.gaoji;

/**
 * 測(cè)試:生產(chǎn)者消費(fèi)者模型 2 -->信號(hào)燈法,標(biāo)志位解決
 */
public class TestPC2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}

// 生產(chǎn)者 --> 演員
class Player extends Thread {
    TV tv;

    public Player(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                this.tv.play("節(jié)目--1");
            } else {
                this.tv.play("節(jié)目--2");
            }
        }
    }
}

// 消費(fèi)者 --> 觀眾
class Watcher extends Thread {
    TV tv;

    public Watcher(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            this.tv.watch();
        }
    }
}

// 產(chǎn)品 --> 節(jié)目
class TV {
    // 演員表演,觀眾等待 T
    // 觀眾觀看倦微,演員等待 F
    // 表演的節(jié)目
    String voice;
    // 標(biāo)志位
    Boolean flag = true;

    // 表演
    public synchronized void play(String voice) {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演員表演了:" + voice);
        // 通知觀眾觀看
        // 通知喚醒
        this.notifyAll();
        this.voice = voice;
        this.flag = !this.flag;
    }

    // 觀看
    public synchronized void watch() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("觀看了:" + voice);
        // 通知演員表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

六妻味、線程池

  • 背景:經(jīng)常創(chuàng)建和銷(xiāo)毀、使用量特別大的資源欣福,比如并發(fā)情況下的線程责球,對(duì)性能影響很大。
  • 思路:提前創(chuàng)建好多個(gè)線程,放入線程池中雏逾,使用時(shí)直接獲取嘉裤,使用完放回池中∑懿可以避免頻繁創(chuàng)建銷(xiāo)毀屑宠、實(shí)現(xiàn)重復(fù)利用。類(lèi)似生活中的公共交通工具仇让。
  • 好處:
    • 提高響應(yīng)速度(減少了創(chuàng)建新線程的時(shí)間)典奉;
    • 降低資源消耗(重復(fù)利用線程池中線程,不需要?dú)按味紕?chuàng)建)丧叽;
    • 便于線程管理(…)
      • corePoolSize:核心池的大形谰痢;
      • maximumPoolSize:最大線程數(shù)踊淳;
      • keepAliveTime:線程沒(méi)有任務(wù)時(shí)假瞬,最多保持多長(zhǎng)時(shí)間后會(huì)終止。
  • JDK 5.0 起提供了線程池相關(guān) API:ExecutorServiceExecutors迂尝。
  • ExecutorService:真正的線程池接口脱茉。常見(jiàn)子類(lèi) ThreadPoolExecutor
    • void execute( Runnable command):執(zhí)行任務(wù)命令垄开,沒(méi)有返回值琴许,一般用來(lái)執(zhí)行 Runnable
    • <T> Future<T> submit( Callable<T>task):執(zhí)行任務(wù)说榆,有返回值虚吟,一般又來(lái)執(zhí)行 Callable
    • void shutdown():關(guān)閉連接池签财。
  • Executors:工具類(lèi)串慰、線程池的工廠類(lèi),用于創(chuàng)建并返回不同類(lèi)型的線程池唱蒸。
  • 實(shí)例:線程池
package com.xxx.gaoji;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 測(cè)試線程池
 */
public class TestPool {
    public static void main(String[] args) {
        // 1.創(chuàng)建服務(wù)邦鲫,創(chuàng)建線程池
        // newFixedThreadPool 參數(shù)為:線程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        // 執(zhí)行 Runnable 接口的實(shí)現(xiàn)類(lèi)
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        // 2.關(guān)閉鏈接
        service.shutdown();
    }
}

class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

總結(jié):

package com.xxx.gaoji;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 回顧總結(jié)線程的創(chuàng)建
 */
public class ThreadNew {
    public static void main(String[] args) {
        // 1.繼承 Thread 類(lèi)
        new MyThread1().start();
        // 2.實(shí)現(xiàn) Runnable 接口
        new Thread(new MyThread2()).start();
        // 3.實(shí)現(xiàn) Callable 接口
        FutureTask<Integer> futureTask = new FutureTask(new MyThread3());
        new Thread(futureTask).start();
        // 獲取返回值
        try {
            Integer integer = futureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

// 1.繼承 Thread 類(lèi)
class MyThread1 extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread1");
    }
}

// 2.實(shí)現(xiàn) Runnable 接口
class MyThread2 implements Runnable {
    @Override
    public void run() {
        System.out.println("MyThread2");
    }
}

// 3.實(shí)現(xiàn) Callable 接口
class MyThread3 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("MyThread3");
        return 100;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市神汹,隨后出現(xiàn)的幾起案子庆捺,更是在濱河造成了極大的恐慌,老刑警劉巖屁魏,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滔以,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡氓拼,警方通過(guò)查閱死者的電腦和手機(jī)你画,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)抵碟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人坏匪,你說(shuō)我怎么就攤上這事拟逮。” “怎么了适滓?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵敦迄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我凭迹,道長(zhǎng)罚屋,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任蕊苗,我火速辦了婚禮沿后,結(jié)果婚禮上沿彭,老公的妹妹穿的比我還像新娘朽砰。我一直安慰自己,他們只是感情好喉刘,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布瞧柔。 她就那樣靜靜地躺著,像睡著了一般睦裳。 火紅的嫁衣襯著肌膚如雪造锅。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,475評(píng)論 1 312
  • 那天廉邑,我揣著相機(jī)與錄音哥蔚,去河邊找鬼。 笑死蛛蒙,一個(gè)胖子當(dāng)著我的面吹牛糙箍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播牵祟,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼深夯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了诺苹?” 一聲冷哼從身側(cè)響起咕晋,我...
    開(kāi)封第一講書(shū)人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎收奔,沒(méi)想到半個(gè)月后掌呜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坪哄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年质蕉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呢撞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡饰剥,死狀恐怖殊霞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情汰蓉,我是刑警寧澤绷蹲,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站顾孽,受9級(jí)特大地震影響祝钢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜若厚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一拦英、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧测秸,春花似錦疤估、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至沈撞,卻和暖如春慷荔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缠俺。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工显晶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人壹士。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓磷雇,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親墓卦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子倦春,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容