第十二章 多线程

什么是线程?

线程Thread是一个程序内部

什么是多线程?

大白话:多线程,是允许多个线程并发并行执行,而不是像单线程一样,顺序执行,前面的执行完了才能执行后面的语句。
并发:交替执行 (一般在单核处理器上)
并行:同时执行 (多核处理器上,多个程序可以同时执行)
在这里插入图片描述

如何在程序中创建多条线程?

创建方式一:通过继承Thread,并重写run方法

将类声明为Thread的子类。 此子类应覆盖类Thread的run方法。
然后可以分配和启动子类的实例
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

创建方式二:通过实现Runnable接口创建,重写run方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
简化写法:
在这里插入图片描述
在这里插入图片描述

创建方式三:实现callable接口(这种方式常用)

在这里插入图片描述
创建步骤如下:
在这里插入图片描述
在这里插入图片描述

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class threadDemo {
    public static void main(String[] args) {
        // 2、创建一个Callable接口的实现类
        Callable<String> c1 = new MyCallable(100);
        // 3、把Callable对象封装成一个真正的线程任务对象FutureTask对象
        /*
         * 未来任务对象的作用:
         * a、本质是一个Runnable线程任务对象,可以交给Thread线程对象处理
         * b、可以获取到线程任务的执行结果
         * **/
        FutureTask<String> f1 = new FutureTask<>(c1);
        // 4、创建一个线程对象,把未来任务对象作为构造方法参数传递给Thread对象
        Thread t1 = new Thread(f1);
        // 5、启动线程
        t1.start();

        //创建第二个线程
        Callable<String> c2 = new MyCallable(200);
        FutureTask<String> f2 = new FutureTask<>(c2);
        Thread t2 = new Thread(f2);
        t2.start();

        // 6、获取线程执行完毕后返回的结果
        try {
            //如果主线程执行到这里,发现第一个线程还没执行完,就会一直阻塞,让出cpu,直到第一个线程执行完,才会继续往下执行
            System.out.println(f1.get());

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

        try {
            System.out.println(f2.get());
        } catch (Exception e) {
            e.printStackTrace();

        }
    }
}

// 1.定义一个实现类实现Callable接口
class MyCallable implements Callable<String> {

    // 定义一个int类型的变量,用于接收外部传递进来的参数
    private final int n;

    // 构造方法
    public MyCallable(int n) {
        this.n = n;
    }
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += i;
        }
        return "子线程运算结果为1-"+n+":"+sum;
    }
}

线程常用方法

在这里插入图片描述

import java.lang.Thread;
public class Demo1 {
    public static void main(String[] args) {
                    // 创建线程1
                Thread t1 = new MyThread();  // 默认线程名称是Thread-0
                // 设置线程名称
                t1.setName("1号县城");
                // 启动线程
                t1.start();
                // 打印线程名称
                System.out.println(t1.getName()); 

                // 创建线程2
                Thread t2 = new MyThread(); // 默认线程名称是Thread-1
                t2.setName("2号线程");
                t2.start();
                System.out.println(t2.getName());

                // 哪个线程调用这个代码,这个代码就拿到哪个线程
                Thread m = Thread.currentThread(); // 主线程main在调用这句代码,主线程拿到这个代码
                m.setName("主线程");
                System.out.println(m.getName());
            }
        }
        class  MyThread extends Thread{
            @Override
            public void run(){
                for(int i=0;i<5;i++){
                    System.out.println(Thread.currentThread().getName()+"子线程输出:"+i);
                }
            }
        }

在这里插入图片描述
在这里插入图片描述

Thread的 join方法:线程插队,让调用这个方法的线程先执行

import java.lang.Thread;
public class Demo1 {
    public static void main(String[] args) {
                    // 创建线程1
                Thread t1 = new MyThread();
               t1.setName("1号线程");
                // 启动线程
                t1.start();
                for(int i=0;i<5;i++){
                    System.out.println(Thread.currentThread().getName()+"主线程输出:"+i);
                    if (i==2){
                        try {
                            // 插队,等待线程1执行完,再执行后面的语句
                            t1.join();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        class  MyThread extends Thread{
            @Override
            public void run(){
                for(int i=0;i<5;i++){
                    System.out.println(Thread.currentThread().getName()+"子线程输出:"+i);
                }
            }
        }

线程安全问题

1、什么是线程安全问题?

多个线程同时操作同一个共享资源的时候,可能会出现业务安全问题。

//模拟线程安全问题
// 需求描述:小明和小红同时去同一个公共账户取钱
// 创建账户类
public class Account {
    private  double money;
    private String carId;
    public Account() {
    }
    public Account(double money, String carId) {
        this.money = money;
        this.carId = carId;
    }
    public double getMoney() {
        return money;
    }
    public void setMoney(double money) {
        this.money = money;
    }
    public String getCarId() {
        return carId;
    }
    public void setCarId(String carId) {
        this.carId = carId;
    }

    public void DrawMoney(double money) {
        String name = Thread.currentThread().getName();
        if (this.money >= money) {
             System.out.println(name + "取钱成功,吐出为:" + money);
             this.money -= money;
            System.out.println(name + "取钱成功,余额为:" + this.money);
        } else {
            System.out.println(name + "取钱失败,余额不足");
        }
    }
}

// 创建线程类
public class DrawThread extends Thread {
    // 定义一个变量,类型为 Account
    private Account acc;
    // 构造器
    public DrawThread(String name, Account acc) {
        super(name);
        this.acc = acc;
    }
    @Override
    public void run() {
        acc.DrawMoney(100000);
    }
}

// 应用
public class Test {
    public static void main(String[] args) {
        // 账户类
        Account acc = new Account( 100000, "123456");
        // 线程类,创建小明和小红2个线程,模拟小明和小红2个人同时去取钱
         new DrawThread("小明", acc).start();
         new DrawThread( "小红", acc).start();

    }
}
// 运行后打印结果为:
/**
小明取钱成功,吐出为:100000.0
小红取钱成功,吐出为:100000.0
小明取钱成功,余额为:0.0
小红取钱成功,余额为:-100000.0
**/

2、如何解决线程安全问题?线程同步

线程同步的核心思想:
让多个线程先后依次访问共享资源,这样就可以避免出现线程安全问题
线程同步方案:加锁
在这里插入图片描述

2.1 线程同步方式一:同步代码块

syn /sɪn/ abbr. 同步
synch /sɪntʃ/ n.同步;同时,同步
synchro /ˈsɪŋkroʊ/ adj.同步的 n.【电】(自动)同步机
synchronize /ˈsɪŋkrənaɪz/ v.(使)同步,在时间上一致,同速进行
synchronized /ˈsɪŋkrənaɪzd/ v.(使)同步,在时间上一致,同速进行 adj.同步的;同步化的
在这里插入图片描述
在这里插入图片描述
此时再执行Test类,打印结果为:
小明取钱成功,吐出为:100000.0
小明取钱成功,余额为:0.0
小红取钱失败,余额不足

在这里插入图片描述

// 1、实例方法
public class Account { 
 synchronized (this) {
 }
}

// 2、静态方法
public static class Account { 
 synchronized (Account .class) {
 }
}

在这里插入图片描述

2.2 线程同步方式二:同步方法

在这里插入图片描述
在这里插入图片描述

2.3 线程同步方式三:Lock锁

reentrant /riːˈɛntrənt/ 可重入;可重入的;重入;可再入的
在这里插入图片描述
在这里插入图片描述

线程池

在这里插入图片描述

1、线程工作原理

创建几个线程,把需要处理的程序放进任务队列,当有空闲的线程就执行任务队列里面的任务,当线程处于忙碌状态,队列里面的其他任务就等待排序等待
在这里插入图片描述

2、如何创建线程池

executor /ɪɡˈzekjətə®/ n.遗嘱执行人(或银行等)
在这里插入图片描述

2.1 方式一:通过ThreadPoolExecutor创建线程池

在这里插入图片描述
在这里插入图片描述

2.1.1 处理Runnable任务
// 定义一个线程任务类实现Runnable接口
public class MyRunnable implements Runnable {
    // 重写run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}
//  主类
public class ExecutorServiceDemo1 {
    public static void main(String[] args) {
        // 1.创建线程池对象
        ExecutorService pool = new ThreadPoolExecutor(
                3,
                5,
                2,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        // 2.使用线程池处理任务
        Runnable target = new MyRunnable();
        pool.execute(target); // 提交第1个任务,创建第1个线程,并启动线程处理这个任务
        pool.execute(target); // 提交第2个任务,创建第2个线程,并启动线程处理这个任务
        pool.execute(target); // 提交第3个任务,创建第3个线程,并启动线程处理这个任务
        pool.execute(target); // 复用线程
        pool.execute(target); // 复用线程

        // 3.关闭线程池对象:一般不关闭线程池
      //  pool.shutdown(); // 线程池不再接受新的任务,但会继续处理已经提交的任务
       // pool.shutdownNow(); // 线程池不再接受新的任务,并立即停止正在处理的任务
    }
}

在这里插入图片描述
在这里插入图片描述

// 定义一个线程任务类实现Runnable接口
public class MyRunnable implements Runnable {
    // 重写run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                Thread.sleep(Integer.MAX_VALUE); // 线程休眠
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
// 主类

public class ExecutorServiceDemo1 {
    public static void main(String[] args) {
        // 1.创建线程池对象
        ExecutorService pool = new ThreadPoolExecutor(

                3,
                5,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),  //任务队列3个
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        // 2.使用线程池处理任务
        Runnable target = new MyRunnable();
        pool.execute(target); // 提交第1个任务,创建第1个线程,并启动线程处理这个任务
        pool.execute(target); // 提交第2个任务,创建第1个线程,并启动线程处理这个任务
        pool.execute(target); // 提交第3个任务,创建第1个线程,并启动线程处理这个任务
        pool.execute(target); // 线程复用
        pool.execute(target); // 线程复用
        pool.execute(target); // 线程复用
        // 3个核心线程在忙,3个任务队列也排满了,由于设置的maximumPoolSize=5,此时到了创建临时线程的时机
        pool.execute(target); // 线程池已满,创建临时线程的时机出现
        pool.execute(target); // 线程池已满,创建临时线程的时机出现
        // 3个核心线程在忙,3个任务队列也排满了,临时线程也满了,此时到了拒绝策略的时机
        pool.execute(target);

        // 3.关闭线程池对象:一般不关闭线程池
       //  pool.shutdown(); // 线程池不再接受新的任务,但会继续处理已经提交的任务
       // pool.shutdownNow(); // 线程池不再接受新的任务,并立即停止正在处理的任务
    }
}

2.1.2 处理Callable任务

在这里插入图片描述


// 定义一个类实现Callable接口
public class MyCallable implements Callable<String> {

    private  int n;
    public MyCallable(int n)
    {
        this.n = n;
    }
    // 实现call方法,定义线程执行体
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return Thread.currentThread().getName() + "计算了1-" + n + "个数相加,结果是:" + sum;
    }

}
// 主类

public class ExecutorServiceDemo2 {
    public static void main(String[] args) {
        // 1.创建线程池对象
        ExecutorService pool = new ThreadPoolExecutor(
                3,
                5,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),  //任务队列3个
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        // 2.使用线程池处理Callable任务
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));
        Future<String> f5 = pool.submit(new MyCallable(500));

        try {
            System.out.println(f1.get());
            System.out.println(f2.get());
            System.out.println(f3.get());
            System.out.println(f4.get());
            System.out.println(f5.get());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

2.2 方式二:通过Executors创建线程池

Executors是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3 并发和并行

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

练习

在这里插入图片描述
在这里插入图片描述
解答上诉问题:
1、当使用 synchronized 时,需要指定一个锁对象。
2、锁对象可以是任何对象,但必须是所有线程都能访问到的对象(通常是共享资源本身或与共享资源相关的对象)。
3、在代码中,redPacket 是一个共享的集合对象,所有线程都需要从中取出红包金额。因此,redPacket 是多个线程竞争的资源。
4、如果使用 this 作为锁对象,那么每个线程的锁将是自己的实例(即每个 PeopleGetPacket 对象的锁互不相关),无法实现线程间的同步

// 抢红包类
import java.util.List;
public class PeopleGetPacket extends  Thread{
    private final List<Integer> redPacket;
    // 构造函数
    public PeopleGetPacket(List<Integer> redPacket, String name) {
        super(name);
        this.redPacket = redPacket;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        while (true) {
            synchronized (redPacket) {
                if (redPacket.isEmpty()) {

                   break;
                }
                // 随机获取集合下标,并通过下标得到集合中的红包金额
                int index = (int) (Math.random() * redPacket.size());
                Integer money = redPacket.remove(index);
                System.out.println(name + ":抢到了" + money + "元");
                if(redPacket.isEmpty()){
                    System.out.println("活动结束");
                    break;
                }
            }
            // 这段try catch是为了模拟抢红包写得,实际开发中不会写这段代码
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
// 主类
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Test {
    public static void main(String[] args) {
        // 1.创建红包
        List<Integer> redPacket =  getRedPacket();

        // 2.创建100个线程,竞争同一个集合红包
        for(int i = 0; i < 100; i++){
     new PeopleGetPacket(redPacket,"人"+i).start();
        }
    }

    public static List<Integer> getRedPacket() {
        // 创建200个红包,1~30元占80%,31~100元占20%
        // 创建一个list集合变量redPacket存放200个红包
        Random r = new Random();
       List<Integer> redPacket = new ArrayList<>();
       for(int i = 0; i < 160; i++){
           // 产生1~30的随机红包,并存放到redPacket集合中
           redPacket.add(r.nextInt(31));
       }

       for(int i = 0; i < 40; i++){
           // 产生31~100的随机红包,并存放到redPacket集合中
           redPacket.add(r.nextInt(70) + 31);
       }
        return redPacket;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值