Java 多线程

1 简介

程序-静态概念
进程-程序执行一次的过程
Process:进程
Thread:线程

2 线程的创建

2.1 Thread类

新建类继承Thread类并重写run方法,在主线程中使用start函数开启子线程。

 	public class Main extends java.lang.Thread {
    public Main() {
    }

    @Override
    public void run() {
        super.run();
    }

    public static void main(String[] args) {

    }
}

Demo示例
run()方法中写了线程要执行的函数,在主线程中调用start方法开启线程函数

public class Main extends java.lang.Thread {
    public Main() {
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("Thread:" + i);
        }
    }

    public static void main(String[] args) {

        Main main = new Main();
        main.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("Main:" + i);
        }
    }
}

如果调用run方法而不是start方法,那么程序会先执行完run方法再执行主线程的程序

2.2 网图下载

百度搜索commons-io.jar包,apache开发,导入到项目中,并将其作为library添加
在这里插入图片描述
编写下载工具类WebDownloader

	class WebDownloader{
    public void downloader(String url, String name) throws IOException {
        FileUtils.copyURLToFile(new URL(url), new File(name));
    }
}

编写多线程类,重写run方法,并在主线程中生成多个线程实现文件下载

public class Main extends java.lang.Thread {

    private String url;
    private String name;

    public Main(String url, String name) {
        this.url = url;
        this.name = name;
    }

    public Main() {
    }

    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        try {
            webDownloader.downloader(url, name);
            System.out.println("下载了文件名为:"+name);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        Main main1 = new Main("https://cdn1.zzzmh.cn/blog/image/posts/full/86.jpg", "1.jpg");
        Main main2 = new Main("https://www.mrhelloworld.com/resources/articles/articles_header/2020/06/03/header.jpg", "2.jpg");
        Main main3 = new Main("https://img-blog.csdnimg.cn/img_convert/cd58f38c70d92834e645c3adeb1138fe.png", "3.jpg");

        main1.start();
        main2.start();
        main3.start();
    }
}

2.3 Runnable接口

类实现Runnable接口,并重写run方法,在主线程中新建Thread对象并将此类对象作为参数传入并调用thread对象的start方法

public class Main implements Runnable{


    public static void main(String[] args) {
        Main main = new Main();
        new Thread(main).start();

        for (int i = 0; i < 20; i++) {
            System.out.println("Main:" + i);
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("Thread:" + i);
        }
    }
}

个人感觉:与2.1相比只是Thread类的初始化方式不同,

推荐使用Runnable接口,因为Java单继承,继承自Thread类的话限制了灵活性

2.4 多人买火车票-并发

Thread.currentThread().getName():获取线程名字

public class Main implements Runnable{

    private int ticketNums = 10;

    public static void main(String[] args) {
        Main main = new Main();

        new Thread(main, "111").start();
        new Thread(main, "222").start();
        new Thread(main, "333").start();
    }

    @Override
    public void run() {
        while (true){
            if(ticketNums <= 0){
                break;
            }
            System.out.println(Thread.currentThread().getName() + " The " + ticketNums-- + " ticket!");
        }
    }

存在问题:同一张票会被多个人抢到!!!

2.5 龟兔赛跑

线程函数:跑,并且在跑完后判断是否游戏结束

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            boolean flag = gameOver(i);
            if(flag){
                break;
            }
            System.out.println(Thread.currentThread().getName() + " run " + i + " step!");
        }
    }

设置赢家标志位winner,判断比赛结束函数:

    private boolean gameOver(int steps){
        if(winner != null){
            return true; // 已有赢家
        }{
            if(steps == 100) {
                winner = Thread.currentThread().getName();
                System.out.println("Winner is  " + winner);
                return true;
            }
        }
        // 没跑到100步,那游戏就不暂停
        return false;
    }

编写主线程:

    public static void main(String[] args) {
        Main main = new Main();

        new Thread(main, "Rabbit").start();
        new Thread(main, "Turtle").start();
    }

如何控制两者有不同的速度?怎么让兔子睡觉?

修改run方法,如果当前线程为兔子的话,就Thread.sleep(0

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {

            // 兔子睡觉
            if(Thread.currentThread().getName() == "Rabbit" && i%10 == 0){
                try {
                    Thread.sleep(10); // 睡觉
                    System.out.println("Rabbit Sleep");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

            boolean flag = gameOver(i);
            if(flag){
                break;
            }
            System.out.println(Thread.currentThread().getName() + " run " + i + " step!");
        }
    }

2.6 Callable接口(了解)

**好处:**可以有返回值
类实现Callable接口并重写call方法替代run方法

// 下载工具类
class WebDownloader{
    public void downloader(String url, String name) throws IOException {
        FileUtils.copyURLToFile(new URL(url), new File(name));
    }
}
public class Main implements Callable 
	@Override
    public Boolean call() throws Exception {
        WebDownloader webDownloader = new WebDownloader();
        try {
            webDownloader.downloader(url, name);
            System.out.println("下载了文件名为:"+name);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return true;
    }

在主线程中,生成子线程对象,开启服务,提交子线程任务,并获取结果,然后关闭服务

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Main main1 = new Main("https://cdn1.zzzmh.cn/blog/image/posts/full/86.jpg", "1.jpg");
        Main main2 = new Main("https://www.mrhelloworld.com/resources/articles/articles_header/2020/06/03/header.jpg", "2.jpg");
        Main main3 = new Main("https://img-blog.csdnimg.cn/img_convert/cd58f38c70d92834e645c3adeb1138fe.png", "3.jpg");

        // 创建执行服务
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // 提交执行
        Future submit1 = executorService.submit(main1);
        Future submit2 = executorService.submit(main2);
        Future submit3 = executorService.submit(main3);

        // 获取结果
        boolean rs1 = (boolean) submit1.get();
        boolean rs2 = (boolean) submit2.get();
        boolean rs3 = (boolean) submit3.get();

        executorService.shutdownNow();
    }

3 静态代理

静态代理与Thread
例子:婚庆公司代理你的婚礼,本质上是你结婚,但婚庆公司负责婚前和婚后

interface Marry{
    void HappyMarry();
}

// 真实角色
class You implements Marry{
    @Override
    public void HappyMarry() {
        System.out.println("You Marry!");
    }
}

代理类:

class WeddingCompany implements Marry{

    private Marry target;

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

    @Override
    public void HappyMarry() {
        before();   // 代理内容
        this.target.HappyMarry();   // 真实操作
        after();    // 代理内容
    }

    private void before(){
        System.out.println("yaoxi");
    }

    private void after(){
        System.out.println("not yaoxi");
    }

}

代理类实现代理流程:

public class Main{

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        WeddingCompany weddingCompany = new WeddingCompany(new You());
        weddingCompany.HappyMarry();
    }

}
  1. 代理对象和真实角色要实现同一接口
  2. 代理对象代理真实角色
    好处:代理对象可以做真实对象做不了的事情

4 Lambda表达式

为什么在多线程里讲?
因为Runnable接口中只有一个run方法,非常适合lambda表达式

4.1 函数式接口

接口中只有一个抽象方法就叫函数式接口,可以用Lambda表达式

public interface Runnable{
	public abstract void run();
}

4.2 普通思路

定义一个函数式接口:

interface ILike{
    void lambda();
}

定义接口实现类:

class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("this is a lambda");
    }
}

普通思路调用方法:

public class Main{

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ILike like = new Like();
        like.lambda();
    }
}

4.3 静态内部类优化

public class Main{

    static class Like2 implements ILike{
        @Override
        public void lambda() {
            System.out.println("this is a lambda2");
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ILike like = new Like();
        like.lambda();

        like = new Like2();
        like.lambda();
    }
}

4.4 局部内部类优化

类的定义放在函数中

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ILike like = new Like();
        like.lambda();

        like = new Like2();
        like.lambda();

        class Like3 implements ILike{
            @Override
            public void lambda() {
                System.out.println("this is a lambda3");
            }
        }
        
        like = new Like3();
        like.lambda();
    }
}

4.5 匿名内部类

调用的时候现场写代码

public class Main{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("this is a lambda4");
            }
        };
        like.lambda();
    }
}

4.6 lambda表达式简化

将匿名内部类中lambda函数名后面的部分保留,剩下的删掉,即为lambda表达式

public static void main(String[] args) throws ExecutionException, InterruptedException {
        ILike like;
        like = () -> {
            System.out.println("this is a lambda5");
        };
        like.lambda();
    }

分析:怎么实现的?
因为函数式接口中只有一个方法,所以要实现只能实现这一个接口,所以这种写法默认实现这个方法,所以前面的部分予以省略,like的类型已经指定接口信息

4.7 带参lambda表达式

public class Main{

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ILike like;
        like = (int a) -> {
            System.out.println("this is lambda " + a);
        };
        like.lambda(33);
    }
}

// 定义函数式接口
interface ILike{
    void lambda(int a);
}

4.7.1 简化1:去掉参数类型

public class Main{

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ILike like;
        like = (a) -> {
            System.out.println("this is lambda " + a);
        };
        like.lambda(33);
    }
}

// 定义函数式接口
interface ILike{
    void lambda(int a);
}

4.7.2 简化2:去掉参数括号(适用于单个参数)

public class Main{

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ILike like;
        like = a -> {
            System.out.println("this is lambda " + a);
        };
        like.lambda(33);
    }
}

// 定义函数式接口
interface ILike{
    void lambda(int a);
}

4.7.3 简化3:去掉lambda表达式的花括号(只适用于一行代码)

public class Main{

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ILike like;
        like = a -> System.out.println("this is lambda " + a);
        like.lambda(33);
    }
}

// 定义函数式接口
interface ILike{
    void lambda(int a);
}

5 停止线程

  1. 建议利用次数使线程正常停止
  2. 建议使用标志位让线程停止
  3. 不要用stop或者destroy等过时方法停止线程
public class Main implements Runnable{

    // 设置标志位
    private boolean flag = true;

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Main main = new Main();
        new Thread(main).start();

        for (int i = 0; i < 10000; i++) {

            if(i == 9000){
                // 自己编写的stop函数更换标志位
                main.stop();
                System.out.println("Thread Stop!");
            }
        }
    }

    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.out.println("run : " + i++);
        }
    }

    public void stop(){
        this.flag = false;
    }
}

使用外部标志位flag控制run方法,在外部通过控制条件实现flag标志位的改变

6 线程休眠

Thread.sleep() :放大问题的发生性

作用:模拟网络延时,或者倒计时

使用sleep函数打印当前系统时间

public class Main{

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Date startTime = new Date(System.currentTimeMillis());

        while (true){
            Thread.sleep(1000);
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
            startTime = new Date(System.currentTimeMillis());
        }
    }
}

7 线程礼让

重新竞争CPU,具体结果看CPU心情,有可能礼让后,保持原执行顺序不变。
Thread.yield()方法让当前线程礼让一下

public class Main{

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

        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() + "Start");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "End");
    }
}

8 Join

先执行调用join的线程,然后执行其他的线程

	public class Main implements Runnable{

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Main main = new Main();
        Thread thread = new Thread(main);
        thread.start();

        for (int i = 0; i < 500; i++) {
            if(i == 200){
                thread.join();  //  thread 线程插队,该线程执行完后,其他线程再执行
            }
            System.out.println("Main : " + i);
        }

    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("Thread " + i);
        }
    }
}

当主线程执行200次循环时,不管子线程执行到什么地步,因子线程join,主线程让步于子线程,等子线程执行完再执行完剩下的步骤

9 观测线程状态

Thread.getState()获取线程状态

public class Main{

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("666");
        });

        Thread.State state = thread.getState();
        System.out.println(state);

        thread.start();
        state = thread.getState();
        System.out.println(state);

        while(state != Thread.State.TERMINATED){
            Thread.sleep(100);
            state = thread.getState();
            System.out.println(state);
        }
    }
}

10 线程优先级

优先级高的线程通常会被优先执行,但优先级高的不是100%在优先级低的前被执行,先设置优先级,后start启动

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyPriority myPriority1 = new MyPriority();

        Thread thread1 = new Thread(myPriority1);
        Thread thread2 = new Thread(myPriority1);
        Thread thread3 = new Thread(myPriority1);
        Thread thread4 = new Thread(myPriority1);


        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());

        thread1.start();

        thread2.setPriority(8);
        thread2.start();

        thread3.setPriority(3);
        thread3.start();

        thread4.setPriority(7);
        thread4.start();

    }
}

class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
    }
}

11 守护线程

线程分为用户线程和守护线程

守护线程作用:
负责垃圾回收等。。

不需要管守护线程,它会随着主线程结束而自动处理

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        God god = new God();
        You you = new You();

        Thread thread = new Thread(god);
        thread.setDaemon(true); // 设置为守护线程
        thread.start();

        new Thread(you).start();    // 用户线程

    }
}


class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("God protect you!");
        }
    }
}


class You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("Happy every day");
        }
        System.out.println("Goodbye!");
    }
}

12 线程同步安全

多线程操作统一资源时,需要线程同步,规定各线程的执行顺序。使用锁保证了安全性,也损失了一定的性能。

12.1 线程不安全1:买票

买票:

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        BuyTicket buyTicket = new BuyTicket();
        Thread thread1 = new Thread(buyTicket, "A");
        Thread thread2 = new Thread(buyTicket, "B");
        Thread thread3 = new Thread(buyTicket, "C");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class BuyTicket implements Runnable{

    private int ticketNums = 10;

    // 线程停止方式
    boolean flag = true;

    @Override
    public void run() {
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void buy() throws InterruptedException {
        // 是否有票
        if(ticketNums <= 0){
            flag = false;
            return;
        }
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName() + " buys " + ticketNums--);
    }
}

有人会拿到-1,或者有重票,这就是线程不安全的表现
原因:并发,没有让线程排队

12.2 线程不安全2:取钱

分析思路:账户里有100,双方均看见里面有100,均可以从里面取50,如果一方取50,另一方取100,在各自看来这是合法的操作,如果两者同时取款,那将取出150万,明显余额不够取,此时为线程不安全。

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Account account = new Account("基金", 100);
        Drawing girlFriend = new Drawing(account, 100, "girlFriend");
        Drawing you = new Drawing(account, 50, "你");

        girlFriend.start();
        you.start();
    }
}

class Account{
    String name;
    int money;

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

// 银行 模拟取款
class Drawing extends Thread{
    Account account;

    // 取了多少钱
    int drawingMoney;
    //现有的钱
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);    // 调用父类构造方法,初始化名字
        this.account = account;
        this.drawingMoney = drawingMoney;

    }

    @Override
    public void run() {
        if(account.money - drawingMoney < 0){
            System.out.println(Thread.currentThread().getName() + " no enough money!");
            return;
        }

        // 双方均等待,均看到账户里有100
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        account.money = account.money - drawingMoney;
        nowMoney = nowMoney + drawingMoney;

        System.out.println(account.name + " rest money is: " + account.money);
        System.out.println(this.getName() + "now money is: " + nowMoney); // this.getName()获取线程名字
    }
}

会出现账户余额为-50的情况,明显有问题

12.3 线程不安全3:集合

创建20000个线程,将各线程的名字存到list中,主线程停等2秒,足够线程创建完成

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 20000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        Thread.sleep(2000);
        System.out.println(list.size());
    }
}

发现:list中的个数不足20000,因为并发写入时,有线程同时向同一位置写入,导致个数不足,是线程不安全的表现

13 线程同步-解决线程不安全

synchronized

13.1 买票

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        BuyTicket buyTicket = new BuyTicket();
        Thread thread1 = new Thread(buyTicket, "A");
        Thread thread2 = new Thread(buyTicket, "B");
        Thread thread3 = new Thread(buyTicket, "C");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class BuyTicket implements Runnable{

    private int ticketNums = 10;

    // 线程停止方式
    boolean flag = true;

    @Override
    public void run() {
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private synchronized void buy() throws InterruptedException {
        // 是否有票
        if(ticketNums <= 0){
            flag = false;
            return;
        }
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName() + " buys " + ticketNums--);
    }
}

在buy方法的定义中加了synchronized

13.2 取钱

synchronized该锁谁?默认锁this,实际该锁被操作的资源 ,所以,此处该使用synchronized块

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Account account = new Account("基金", 100);
        Drawing girlFriend = new Drawing(account, 100, "girlFriend");
        Drawing you = new Drawing(account, 50, "你");

        girlFriend.start();
        you.start();
    }
}

class Account{
    String name;
    int money;

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

// 银行 模拟取款
class Drawing extends Thread{
    Account account;

    // 取了多少钱
    int drawingMoney;
    //现有的钱
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);    // 调用父类构造方法,初始化名字
        this.account = account;
        this.drawingMoney = drawingMoney;

    }

    @Override
    public void run() {

        // 锁account
        synchronized (account){
            if(account.money - drawingMoney < 0){
                System.out.println(Thread.currentThread().getName() + " no enough money!");
                return;
            }

            // 双方均等待,均看到账户里有100
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            account.money = account.money - drawingMoney;
            nowMoney = nowMoney + drawingMoney;

            System.out.println(account.name + " rest money is: " + account.money);
            System.out.println(this.getName() + "now money is: " + nowMoney); // this.getName()获取线程名字
        }

    }
}

synchronized块,括号中指明要同步的对象,本例中需要锁的是account,保证让其他线程依次访问account中的余额

13.3 集合

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        List<String> list = new ArrayList<String>();
            for (int i = 0; i < 20000; i++) {
                new Thread(()->{
                    synchronized (list){
                        list.add(Thread.currentThread().getName());
                    }
                }).start();
            }

        Thread.sleep(2000);
        System.out.println(list.size());
    }
}

在run方法中添加synchronized块,如果在for循环外添加,不work

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        List<String> list = new ArrayList<String>();
        synchronized (list){
            for (int i = 0; i < 20000; i++) {
                new Thread(()->{
                    list.add(Thread.currentThread().getName());
                }).start();
            }
        }

        Thread.sleep(2000);
        System.out.println(list.size());
    }
}
output:19998 

14 死锁

互相等待,想拿对方锁住的资源

public class Main {
    public static void main(String[] args) {
        MakeUp a = new MakeUp(0, "A");
        MakeUp b = new MakeUp(1, "B");

        a.start();
        b.start();
    }
}

class LipStick{

}

class Mirror{

}

class MakeUp extends Thread{
    static LipStick lipStick = new LipStick();
    static Mirror mirror = new Mirror();

    int choice;
    String name;

    public MakeUp(int choice, String name) {
        this.choice = choice;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    // 互相持有对方的锁
    private void makeup() throws InterruptedException {
        if(choice == 0){
            synchronized (lipStick){
                // 获得口红的锁
                System.out.println(this.name + " 获得了口红的锁");
                Thread.sleep(1000);
                synchronized (mirror){
                    // 1s后想拿镜子
                    System.out.println(this.name + " 1s后拿到镜子的锁");
                }
            }
        }else {
            synchronized (mirror){
                // 获得镜子的锁
                System.out.println(this.name + " 获得了镜子的锁");
                Thread.sleep(1000);
                synchronized (lipStick){
                    // 1s后想拿口红
                    System.out.println(this.name + " 1s后拿到口红的锁");
                }
            }
        }
    }
}

此时,A等B释放镜子锁,才能执行完毕,而B等A释放口红锁才能执行完毕
该如何解决?
修改代码,用完就放锁

public class Main {
    public static void main(String[] args) {
        MakeUp a = new MakeUp(0, "A");
        MakeUp b = new MakeUp(1, "B");

        a.start();
        b.start();
    }
}

class LipStick{

}

class Mirror{

}

class MakeUp extends Thread{
    static LipStick lipStick = new LipStick();
    static Mirror mirror = new Mirror();

    int choice;
    String name;

    public MakeUp(int choice, String name) {
        this.choice = choice;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    // 互相持有对方的锁
    private void makeup() throws InterruptedException {
        if(choice == 0){
            synchronized (lipStick) {
                // 获得口红的锁
                System.out.println(this.name + " 获得了口红的锁");
                Thread.sleep(1000);
            }
            synchronized (mirror){
                // 1s后想拿镜子
                System.out.println(this.name + " 1s后拿到镜子的锁");
            }
        }else {
            synchronized (mirror){
                // 获得镜子的锁
                System.out.println(this.name + " 获得了镜子的锁");
                Thread.sleep(1000);
            }
            synchronized (lipStick){
                // 1s后想拿口红
                System.out.println(this.name + " 1s后拿到口红的锁");
            }
        }
    }
}

各自把用完的资源释放,就不影响后续运行

15 Lock锁

显式定义同步锁,取代synchronized
Lock本身是个接口,而ReentrantLock类(可重入锁)实现了这个接口
不加锁时:

public class Main {
    public static void main(String[] args) {
        Buy buy = new Buy();
        new Thread(buy, "A").start();
        new Thread(buy, "B").start();
        new Thread(buy, "C").start();
    }
}

class Buy implements Runnable{

    private int ticketNums = 10;

    @Override
    public void run() {
        while (true){
            if(ticketNums > 0){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);

            }else{
                break;
            }
        }

    }
}

上例会出现线程不安全情况

使用lock锁
锁通常被定义为私有常量,并写在try语句中,在finally语句中写解锁语句

public class Main {
    public static void main(String[] args) {
        Buy buy = new Buy();
        new Thread(buy, "A").start();
        new Thread(buy, "B").start();
        new Thread(buy, "C").start();
    }
}

class Buy implements Runnable{

    private int ticketNums = 10;

    // 可重入锁
    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) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);

                }else{
                    break;
                }
            }finally {
                lock.unlock();
            }

        }

    }
}

synchronized可以锁方法
Lock锁性能更好

16 线程通信-生产者与消费者

16.1 管程法

利用生产者与消费者之间的缓冲区解决通信问题

public class Main {
    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();

        new Producer(synContainer).start();
        new Consumer(synContainer).start();
    }
}

// 生产者
class Producer extends Thread{
    SynContainer container;

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

    @Override
    public void run() {
        // 生产
        for (int i = 0; i < 100; i++) {
            try {
                container.push(new Chicken(i));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("Produce " + i + " chicken");
        }
    }
}

// 消费者
class Consumer extends Thread{
    SynContainer container;

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                container.pop();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("Consume " + i + " chicken");
        }
    }
}

// 产品

class Chicken{
    int id;

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

// 缓冲区
class SynContainer{
    // 容器大小
    Chicken[] chickens = new Chicken[10];

    // 计数器
    int count = 0;

    // 生产者放入产品
    public synchronized void push(Chicken chicken) throws InterruptedException {
        // 容器满,等待消费
        if(count == chickens.length){
            // 通知消费者消费,生产暂停
            wait();
        }
        //容器未满,push产品
        chickens[count] = chicken;
        count++;

        // 通知消费
        notifyAll();
    }

    // 消费者消费产品
    public synchronized Chicken pop() throws InterruptedException {
        if(count == 0){
            // 等待生产
            wait();
        }
        count--;
        Chicken chicken = chickens[count];

        // 通知生产者生产
        notifyAll();
        return chicken;
    }
}

需要暂停的时候使用wait等待另一方操作,在本方操作完毕后使用notify方法通知对方可以操作

16.2 信号灯法

使用标志位实现

public class Main {
    public static void main(String[] args) {
        TV tv = new TV();

        new Player(tv).start();
        new Watcher(tv).start();
    }
}

// 生产者 -- 演员
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) {
                try {
                    this.tv.play("AAAAA");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }else {
                try {
                    this.tv.play("BBBBBBBBB");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}


// 消费者 -- 观众
class Watcher extends Thread{
    TV tv;

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

    @Override
    public void run() {
        try {
            for (int i = 0; i < 20; i++) {
                this.tv.watch();
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}


// 产品 -- 节目
class TV{
    // 演员表演,观众等待 true
    // 观众观看,演员等待 false
    String voice;
    boolean flag = true;

    // 表演
    public  synchronized void play(String voice) throws InterruptedException {
        // 演员需要等待
        if(!flag){
            this.wait();
        }

        System.out.println("Player:" + voice);

        // 通知观众
        this.notifyAll();
        this.voice = voice;

        this.flag = !this.flag;
    }

    public synchronized void watch() throws InterruptedException {
        if(flag){
            this.wait();
        }
        System.out.println("Watch:" + this.voice);

        // 看完,通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

使用flag控制各自该做的事情,使用flag判断什么时候用wait什么时候用notify

17 线程池

经常创建销毁线程会消耗很大的资源,线程池能够提高性能

public class Main {
    public static void main(String[] args) {
        // 创建服务,创建线程池
        // newFixedThreadPool : 参数 :线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        // 执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        // 关闭链接
        service.shutdownNow();
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
  • 14
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值