多线程

多线程

线程的创建方式一

定义一个子类继承 Thread 类,重写 run 方法

创建 Thread 的子类对象

调用 start 方法启动线程(线程启动后会自动执行run方法中的代码)

示例代码:

//创建线程方式一:继承Thread类
 public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "执行第" + i + "次");
        }
    }
}

//定义一个测试类,在测试类中创建MyThread线程对象,并启动线程
public class Demo{
    public static void main(String[] args) throws Exception {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "方法中第:" + i + "次执行");
        }
    }
}
线程的创建方式二

写一个 Runnable 接口实现类,重写run方法

再创建一个Runnable 实现类对象

创建 Thread 对象,把Runnable 实现类的对象传递给 Thread 

调用 Thread 对象的 start()方法启动线程

示例代码:

//创建线程方式二:实现Runnable接口
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " ---> " + i);
        }
    }
}

//写一个测试类,在测试类中创建线程对象,并执行线程
public class Demo {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable, "小明").start();
        Thread.currentThread().setName("小白");
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() +  " ---> " + i);
        }
    }
}
线程的创建方式三
应用场景

线程执行完毕之后有一些数据需要返回,前两种方式重写的run方法均没有返回结果

定义一个 Callable 接口实现类,重写 call 方法

创建 Callable 实现类的对象

创建 FutureTask (未来任务类) 的对象,将 Callable 对像传递给 FutureTask

创建 Thread 对象,将 Future 对象传递给 Thread

调用 Thread 的start 方法启动线程 (启动后会自动执行call方法,执行完后会自动将返回值结果封装到 FutureTask 对象中)

调用 FutureTask 对象的get()方法获取返回结果

示例代码:

//先准备一个Callable接口的实现类
public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Integer sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

//再定义一个测试类,在测试类中创建线程并启动线程,获取返回结果
public class Demo {
    public static void main(String[] args) throws Exception {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();

        //此处会自动等待线程执行结束并取到结果
        Integer sum = futureTask.get();
        System.out.println(sum);
    }
}

线程安全问题

概述

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

线程安全问题代码演示
//定义一个共享的账户类
public class Account {
    private int ID;
    private double money;

    public Account() {
    }

    public Account(int ID, double money) {
        this.ID = ID;
        this.money = money;
    }

    public int getID() {
        return ID;
    }

    public void setID(int ID) {
        this.ID = ID;
    }

    public double getMoney() {
        return money;
    }

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

    public void draw_money(double money){
        Thread thread = Thread.currentThread();
        if (this.money >= money){
            System.out.println(thread.getName() + "的账户余额为:" + this.money);
            this.money -= money;
            System.out.println(thread.getName() + "的剩余金额为:" + this.money);
        }else {
            System.out.println(thread.getName() + "的余额不足");
        }
    }
}

//定义一下取钱线程类
public class Fetch extends Thread{
    private Account account;

    public Fetch(Account account) {
        this.account = account;
    }

    @Override
    public void run() {

        //调用取钱方法,每次取钱10万
        account.draw_money(100000);
    }
}

//写一个测试类,在测试类中创建两个线程对象
public class Demo {
    public static void main(String[] args) {

        //创建共享账户对象,多个线程同时操作
        Account account = new Account(1,100000);
        new Fetch(account).start();
        new Fetch(account).start();
    }
}

//运行结果为两次都取钱成功,余额 -10万,发生线程安全问题
线程同步方案

为了解决前面的线程安全问题,可以使用线程同步思想。最常见的方案就是加锁,意思是每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动释放锁,然后其他线程才能再加锁进来。

Java 提供了三种加锁方案:

同步代码块

同步方法

Lock锁

Account类中的代码三种加锁示例:

//同步代码块 —— 锁的范围小
public void draw_money(double money){
    Thread thread = Thread.currentThread();
    synchronized (this){
        if (this.money >= money){
            System.out.println(thread.getName() + "的账户余额为:" + this.money);
            this.money -= money;
            System.out.println(thread.getName() + "的剩余金额为:" + this.money);
        }else {
            System.out.println(thread.getName() + "的余额不足");
        }
    }
}

//同步方法 —— 锁住整个方法,只能等当前线程结束,下个线程才能继续执行
public synchronized void draw_money(double money){
    Thread thread = Thread.currentThread();
    if (this.money >= money){
        System.out.println(thread.getName() + "的账户余额为:" + this.money);
        this.money -= money;
        System.out.println(thread.getName() + "的剩余金额为:" + this.money);
    }else {
        System.out.println(thread.getName() + "的余额不足");
    }
}

//Lock锁,JDK5版本专门提供的一种锁对象,通过这个锁对象的方法来达到加锁,和释放锁的目的,使用起来更加灵活。

//将锁对象定义为常量
private final Lock lock = new ReentrantLock();

public void draw_money(double money){
    Thread thread = Thread.currentThread();
    lock.lock();
    if (this.money >= money){
        System.out.println(thread.getName() + "的账户余额为:" + this.money);
        this.money -= money;
        System.out.println(thread.getName() + "的剩余金额为:" + this.money);
    }else {
        System.out.println(thread.getName() + "的余额不足");
    }
    lock.unlock();
}
锁对象如何选择

建议把共享资源作为锁对象, 不要将随便无关的对象当做锁对象

对于实例方法,建议使用this作为锁对象

对于静态方法,建议把类的字节码(类名.class)当做锁对象

同步方法有没有锁对象

同步方法也是有锁对象,只不过这个锁对象没有显示的写出来而已。

对于实例方法,锁对象其实是this(方法的调用者)

对于静态方法,锁对象时类的字节码对象(类名.class)

同步代码块和同步方法的区别

不存在哪个好与不好,只是一个锁住的范围大,一个范围小

同步方法是将方法中所有的代码锁住

同步代码块是将方法中的部分代码锁住

线程通信

什么是线程通信

当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺

目的是确保线程能够协调地访问和操作共享资源,避免数据不一致和其他并发问题

线程通信案例

需求

两个线程同时操作共享数据,实现共享变量交替进行加1和减1操作

//提供一个数字操作类OptNum,分别提供两个方法,一个负责加1操作,一个负责减1操作
public class OptNum {
    int n = 0;
    public synchronized void plus() throws Exception {
        while (true){
            n++;
            System.out.println(n);
            //使用当前同步锁对象调用,notify方法,唤醒正在等待的单个线程
            this.notify();
            //使自己等待并释放锁
            this.wait();
        }

    }

    public synchronized void subtract() throws Exception {
        while (true){
            n--;
            System.out.println(n);
            this.notify();
            this.wait();
        }
    }
}

//定义一个线程类OptNumThread1,接收OptNum对象,负责处理加1操作
public class OptNumThread1 extends Thread{
    private OptNum optNum;

    public OptNumThread1(OptNum optNum) {
        this.optNum = optNum;
    }

    @Override
    public void run() {
        try {
            optNum.plus();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

//定义一个线程类OptNumThread2,接收OptNum对象,负责处理减1操作
public class OptNumThread2 extends Thread{
    private OptNum optNum;

    public OptNumThread2(OptNum optNum) {
        this.optNum = optNum;
    }

    @Override
    public void run() {
        try {
            optNum.subtract();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

//定义测试类,在测试类中创建2个线程,传入同一个数字操作对象OptNum给2个线程处理,启动线程
public class Demo {
    public static void main(String[] args) {
        OptNum optNum = new OptNum();
        new OptNumThread1(optNum).start();
        new OptNumThread2(optNum).start();
    }
}

线程池—ExecutorService

线程池的七个参数

ExecutorService pool = new ThreadPoolExecutor(

3, //核心线程数有3个

5, //最大线程数有5个。 临时线程数=最大线程数-核心线程数=5-3=2

8, //临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。

TimeUnit.SECONDS,//时间单位(秒)

new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在任务队列中等待

Executors.defaultThreadFactory(), //用于创建线程的工厂对象

new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略

);

注意事项

临时线程什么时候创建?

新任务提交时,发现核心线程都在忙、任务队列满了、并且还可以创建临时线程,此时会创建临时线程。

什么时候开始拒绝新的任务?

核心线程和临时线程都在忙、任务队列也满了、新任务过来时才会开始拒绝任务。

线程池执行 Runnable 任务
//准备一个 Runnable 任务类
public class MyRunnable implements Runnable{
    /**
     * 线程启动后会执行此方法,此方法不需要我们主动调用
     * 注意:当前类并不是线程类,只是一个任务类
     */
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("["+Thread.currentThread().getName()+"]线程代码正在执行..." + i);
            try {
                Thread.sleep(1l);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//在测试类中创建线程池
public class ThreadPoolDemo {

    public static void main(String[] args) throws Exception {
        //创建线程池对象,通过线程池来维护一定数量的线程
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3, //核心线程数
                5, //最大线程数
                60l, //临时线程最大空闲时间
                TimeUnit.SECONDS, //空闲时间单位
                new ArrayBlockingQueue<>(15),//任务队列
                Executors.defaultThreadFactory(), //线程工厂,负责创建线程对象
                new ThreadPoolExecutor.AbortPolicy() //无法处理任务时的拒绝测试
        );

        /**
         * 线程池处理Runnable任务
         */
        for (int i = 0; i < 10; i++) {
            MyRunnable runnableTask = new MyRunnable();
            pool.execute(runnableTask);
        }

        //关闭线程池
        pool.shutdown();
    }
}
线程池执行 Callable 任务
//准备一个 Callable 线程任务类
public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Integer sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum += i;
            System.out.println("当前在:"+Thread.currentThread().getName()+"计算0到100的和");
        }
        return sum;
    }
}

// 在测试类中创建线程池,并执行callable任务
public class ThreadPoolDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程池对象,通过线程池来维护一定数量的线程
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3, //核心线程数
                5, //最大线程数
                60l, //临时线程最大空闲时间
                TimeUnit.SECONDS, //空闲时间单位
                new ArrayBlockingQueue<>(15),//任务队列
                Executors.defaultThreadFactory(), //线程工厂,负责创建线程对象
                new ThreadPoolExecutor.AbortPolicy() //无法处理任务时的拒绝测试
        );

        /**
         * 线程池处理Callable任务
         */
        for (int i = 0; i < 10; i++) {
            MyCallable callableTask = new MyCallable();
            Future<Integer> future = pool.submit(callableTask);
            Integer sum = future.get();
            System.out.println(sum);
        }

        //关闭线程池
        pool.shutdown();
    }
}
线程的生命周期

NEW: 新建状态,线程还没有启动

RUNNABLE: 可以运行状态,线程调用了start()方法后处于这个状态

BLOCKED: 锁阻塞状态,没有获取到锁处于这个状态

WAITING: 无限等待状态,线程执行时被调用了wait方法处于这个状态

TIMED_WAITING: 计时等待状态,线程执行时被调用了sleep(毫秒)或者wait(毫秒)方法处于这个状态

TERMINATED: 终止状态, 线程执行完毕或者遇到异常时,处于这个状态。

并发和并行

什么是进程、线程

正常运行的程序(软件)就是一个独立的进程

线程是属于进程,一个进程中包含多个线程

进程中的线程其实并发和并行同时存在

什么是并发

进程中的线程由CPU负责调度执行,但是CPU同时处理线程的数量是优先的,为了保证全部线程都能执行到,CPU采用轮询机制为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。(简单记:并发就是多条线程交替执行)

什么是并行

并行指的是,多个线程同时被CPU调度执行。如下图所示,多个CPU核心在执行多条线程

多线程是并发还是并行

多个线程在我们的电脑上执行,并发和并行是同时存在的。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏

面向对象其他知识点

2024-3-25 21:29:14

JavaSE

JavaSE:面向对象

2024-3-22 20:19:20

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索