多线程

221 阅读3分钟

1.线程的概念

1.什么是线程?

image.png

2.多线程是什么?

image.png

2.线程创建的三种方式

image.png

1.多线程创建方式一:继承Thread类

image.png


//MyThread类
package com.java03.thread;

/**
 *1. 让子类继承Thread类
 */
public class MyThread extends Thread{
    //2.必须重写Thread类的run方法


    @Override
    public void run() {

        //描述线程的执行任务
        for (int i = 0; i < 5; i++) {
            System.out.println(getName()+"子线程MyThread输出:"+i);
        }
    }
}
//测试类
package com.java03.thread;

/**
 * 掌握线程创建方式一:继承Thread类
 */
public class TestThread01 {
    //main方法是由一条默认的主线程负责执行的
    public static void main(String[] args) {
        //3.创建一个线程对象
        Thread t=new MyThread();
        //启动线程
        t.start();//main线程 t线程

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程输出:"+i);
        }
    }
}

2.多线程创建方式二:实现Runnable

2.1直接实现Runable接口创建

image.png 代码实现如下:

实现接口类

package com.java03.thread;

/**
 * 1.定义一个任务类,实现Runnable接口
 */
public class MyRunnable implements Runnable {
    //2.重写runnable方法
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程输出:"+i);
        }
    }
}

测试类

package com.java03.thread;

public class TestThread02 {
    public static void main(String[] args) {
        //3.创建任务对象
        Runnable target=new MyRunnable();
        //4.把任务对象交给一个线程对象处理
        //  public Thread(Runnable target)
        new Thread(target).start();


        //利用匿名内部类来写线程
        new Thread(()-> {
                for (int i = 1; i < 5; i++) {
                    System.out.println("子线程2" + i);
                }
        }).start();

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程输出:"+i);
        }

    }

}

问题小结

image.png

2.2 使用匿名内部类来实现

image.png 代码实现如下 image.png

3.线程创建方式三:利用Callable接口、FutureTask类来实现

前两种创建线程的方式都存在一个问题

image.png 怎么解决这个问题?

image.png 第三种创建线程的方式 image.png

image.png 代码实现如下: 实现Callable接口类

package com.java03.thread;

import java.util.concurrent.Callable;
//1.让这个类实现Callable接口
public class MyCallable implements Callable<String> {
    private int n;

    public MyCallable() {
    }

    public MyCallable(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        int sum=0;
        for (int i = 1; i <= n; i++) {
            sum+=i;
        }
        return "线程求出1-"+n+"的和是:"+sum;
    }
}

测试类

package com.java03.thread;

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

public class TestThread03 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //3.创建一个Callable对象
        Callable<String> call=new MyCallable(100);
        //4.把Callable对象封装成一个FutureTask
        //1.是一个任务对象,实现了Runnable对象
        //2.可以在线程执行完毕后,用未来任务对象调用get方法获取线程执行完毕后的返回值
        FutureTask<String> f1=new FutureTask(call);
        //5.把任务对象交给一个Thread类
        new Thread(f1).start();
        //6.获取线程执行完毕后返回的结果
        // 注意:如果执行到这,加入上面的线程还没执行完毕
        //这里代码会暂停等待上面线程执行完毕后才会获取结果
        String rs=  f1.get();
        System.out.println(rs);

        Callable<String> call1=new MyCallable(200);

        FutureTask<String> f2=new FutureTask<>(call1);

        new Thread(f2).start();

        System.out.println(f2.get());

    }

4.多线程的注意事项:

image.png

3.Thread常用方法

image.png 代码实现

image.png

image.png Thread其他方法说明

image.png

4.线程安全问题

1.取钱的线程安全问题

image.png

image.png

代码实现 image.png 银行

package com.java03.thread00;

public class Account {
    private  String  cardId;
    private  double money;

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    public Account() {
    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    public void drawMoney(double money) {
        //先搞清楚谁来取钱
        String  name=Thread.currentThread().getName();
        //1.判断余额是否足够
        if (this.money>=money){
            System.out.println(name+"余额足够,可以取钱,取钱"+money);
            this.money-=money;
            System.out.println(name+"取钱后余额"+this.money);
        }else {
            System.out.println(name+"余额不足,无法取钱"+this.money);
        }

    }
}

线程,实现取钱操作

package com.java03.thread00;

public class ThreadSafeCase {

    public static void main(String[] args) {
        //1.创建一个银行账户对象
        Account acc=new Account("id5125",100000);
        //2.创建两个线程,分别代表小明和小红去同一个账户中进行取钱操作
        new  DrawThread("小明",acc).start();//小明
        new  DrawThread("小红",acc).start();//小红
    }
}

主方法

package com.java03.thread00;

public class ThreadSafeCase {

    public static void main(String[] args) {
        //1.创建一个银行账户对象
        Account acc=new Account("id5125",100000);
        //2.创建两个线程,分别代表小明和小红去同一个账户中进行取钱操作
        new  DrawThread("小明",acc).start();//小明
        new  DrawThread("小红",acc).start();//小红
    }
}

image.png

2.解决线程安全的办法

image.png

image.png

2.1同步代码块

image.png 代码实现

image.png 小结问题

image.png

image.png

2.2同步方法

image.png

image.png

小结问题

image.png

image.png

2.3Lock锁

image.png

image.png

3.线程通信

3.1什么是线程通信

image.png

3.2线程通信的案例及常用方法

image.png

image.png 代码实现如下 image.png

image.png

Desk:桌子对象,用来给线程放包子和拿包子

package com.java03.thread_communication;

import java.util.ArrayList;

public class Desk {
    //用来判断是否放入包子
    private ArrayList<String> list=new ArrayList<>();

    public synchronized void put(){
        System.out.println(this);
        //判断是桌子上是否有包子
        try {
            if (list.size() == 0) {
                //没有包子,往桌子上放包子
                list.add(Thread.currentThread().getName()+"一个肉包子");
                System.out.println(Thread.currentThread().getName() + "放了一个肉包子");
                //放完之后,唤醒别人,自己等待

                Thread.sleep(2000);
                this.notifyAll();
                this.wait();
            }else {
                //有包子不做了
                //唤醒别人,自己等待
                this.notifyAll();
                this.wait();
            }
        }catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }



    public synchronized void  get(){
        try {
            if (list.size()==1){
                //有包子,可以吃
                System.out.println(Thread.currentThread().getName()+"吃了"+list.get(0));
                //吃完后移除包子
                list.remove(0);
                Thread.sleep(1000);
                //唤醒别人,自己等待
                this.notifyAll();
                this.wait();
            }else {
                //唤醒别人,自己等待
                this.notifyAll();
                this.wait();
            }

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

主函数

package com.java03.thread_communication;

public class ThreadTest {
    public static void main(String[] args) {
        //需求:三个生产者线程,负责生产包子,每个线程每次往桌子上放一个包子
        //    两个消费者,负责吃包子,每个线程每次从桌子上拿一个包子
        //创建一个桌子对象
        Desk desk=new Desk();
        //三个厨师
        new Thread(()-> {
                while (true){
                    desk.put();
                }
            },"厨师一").start();

        new Thread(()-> {
            while (true){
                desk.put();
            }
        },"厨师二").start();

        new Thread(()-> {
            while (true){
                desk.put();
            }
        },"厨师三").start();
        //两个吃货吃包子
        new Thread(()-> {
            while (true){
                desk.get();
            }
        },"吃货一").start();

        new Thread(()-> {
            while (true){
                desk.get();
            }
        },"吃货二").start();
    }
}

4.线程池

4.1什么是线程池?不使用线程池会出现哪些问题?

image.png

4.2线程池的工作原理

image.png

4.3 Java中谁代表线程池,如何获得线程池对象?

image.png

4.4 ThreadPoolExecutor构造器

image.png 代码

image.png

4.5线程池创建的注意事项

image.png

4.6小结

image.png

5.线程池处理方法

5.1处理Runnable任务

image.png

代码实现 image.png

5.2新任务拒绝策略

image.png

5.3处理Callable任务

image.png 代码实现 image.png

6.使用Executors工具创建线程池

6.1如何使用Executors创建线程池

image.png

代码实现如下

image.png

6.2注意事项

image.png

6.3Executors可能存在的陷阱

image.png

7.并发,生命周期

7.1并发的含义和理解

image.png

image.png

7.2线程的生命周期

image.png

7.3Java线程的六种状态的相互转换

image.png 总结

image.png

8.乐观锁,悲观锁,练习题

8.1悲观锁

image.png

8.2乐观锁(不懂)

image.png

8.3练习题

需求

image.png

代码实现

image.png 如果要统计出小明和小红各自发了多少个礼物应该如何实现
利用到了join方法,待线程执行完成后继续执行之后的代码

image.png