Java多线程(一文看懂!)

多线程

一,多线程的介绍
二,多线程的四种实现方式
三,多线程的五大状态
四,多线程的调度
五,线程的同步(例:多口售票问题)
六,线程的协作(例:生产者-消费者模型)
七,线程的中断

一,多线程的介绍

>>百度中多线程的介绍(multithreading):
  是指从软件或者硬件上实现多个线程并发执行的技术。

>>这里就需要了解一下什么是进程?
是操作系统进行资源分配的最小单位;
同一进程中的多个线程共享该进程中的全部系统资源,
而进程和进程之间是相互独立的,是系统进行资源分配和调度的一个独立单位;
进程是具有一定功能的程序关于某个数据集合上的一次运行活动;

>>什么是线程?
线程是cpu调度和分派的基本单位,
同一进程中的多个线程共享该进程中的全部系统资源,
线程是进程运行和执行的最小调度单位
 
>>为什么要使用多线程?
  1. 从计算机底层来说:线程是程序执行的最小单元,线程间的切换,调度成本远远小于进程,
     另外多核CPU时代意味着多个线程可以同时运行,减少了线程上下文切换的开销;
     上下文切换:多线程的上下文切换是指CPU控制权由已经正在运行的线程切换到另一个
                就绪并等待获取CPU执行权的线程的过程;
  2. 从互联网趋势来说:当下的系统频繁需要百万级,甚至是千万级的并发量,而多线程并发
      编程正是开发高并发的基础,利用多线程机制可以大大提高系统整体的并发能力和性能;
    
>>多线程可能带来的问题:
  并发编程的目的是为了提高程序的执行效率,但并发编程并不总能提高程序运行效率
  而且还有可能带来其它问题,如:内存泄漏,上下文切换,死锁,还受限于硬件和软件的资源闲置问题

二,多线程的四种实现方式

方法一:继承Thead类
  1. 用关键字extends继承Thead类

  2. 重写run方法;(获得执行线程的线程名:Thread.currentThread().getName()

  3. 在测试类中创建类的实例,调用start方法;

    >>线程介绍:
       继承了Thread类,编写简单,
       如果需要访问当前线程,无需Thread.currentTread()方法,直接使用this,即可获得当前线程。
       但由于类的单继承原则,该类不能继承其他类,灵活度不高
    
public class TheadDemo extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TheadDemo demo=new TheadDemo();
        demo.start();
    }
}
方法二:实现Runnable接口
  1. 用关键字implements实现Runnable接口

  2. 重写run方法;

  3. 在测试类中创建类的实例

  4. 创建Thead类的实例,传入上面创建的实例对象;

  5. 用Thead类的实例,调用start方法;

    >>线程介绍:
      实现Runnable接口比较容易实现多个线程共享一个对象,
      非常适合多线程处理同一份资源的场景,
      因而可以将代码,数据分开,形成清晰的代码模型,体现着Java面向对象的思想
    
public class RunnableDemo implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }

    public static void main(String[] args) {
        RunnableDemo demo=new RunnableDemo();
        Thread thread=new Thread(demo);
        thread.start();
    }
}
>>start方法和run方法的区别:
  调用start方法后,线程会被放到等待队列,等待JVM调度,并不一定马上开始执行,此时线程处于就绪状态
  该方法用来启动线程,调用后子线程就绪;
  
  run方法用来封装子线程所需要的业务代码,run方法运行结束,子线程终止
方法三:实现Callable接口
  1. 用关键字implements实现Callable接口,并规定泛型;

  2. 重写call方法;

  3. 在测试类中创建类的实例

  4. 创建FutureTask的实例,并传入上面类的实例对象;

  5. 创建Thead的实例,传入FutureTask的实例对象;

  6. 用Thead的实例对象调用start方法;

    线程介绍:
      实现Runnable接口和实现Callable接口
      比较容易实现多个线程共享一个对象,非常适合,多线程处理同一份资源的场景,
      因而可以将代码,数据分开,形成清晰的代码模型,体现着Java面向对象的思想
      而实现Runnable接口中的call方法,可以有返回值,灵活度更高
    
public class CallableTask implements Callable<Integer> {
    @Override
    public Integer call() throws InterruptedException {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
            System.out.println(Thread.currentThread().getName() + ":" + sum);
        }
        return sum;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableTask callableTask = new CallableTask();
        FutureTask<Integer> futureTask = new FutureTask<>(callableTask);
        Thread thread = new Thread(futureTask, "子线程");
        futureTask.get();//得到CallableTask中call方法的返回数据
        thread.start();
    }
}
方法四:使用线程池实现

来看看自定义的线程池构造函数(参数最多的)

public ThreadPoolExecutor(
int corePoolSize,                      //线程池中核心线程的数量
int maximumPoolSize,                   //线程池可创建的最大线程数量
long keepAliveTime,                    //线程最大等待时间
TimeUnit unit,                         //指定keepAliveTime的单位
BlockingQueue<Runnable> workQueue,     //指定存储未执行任务的队列
ThreadFactory threadFactory,           //创建线程的工厂
RejectedExecutionHandler handler       //指定拒绝策略
)
public class 自定义线程池 {
    private static final int CORE_POOL_SIZE = 5;
    private static final int MAX_POOL_SIZE = 10;
    private static final int QUEUE_CAPACITY = 100;
    private static final Long KEEP_ALIVE_TIME = 1L;

    public static void main(String[] args) {

        //通过ThreadPoolExecutor构造函数自定义参数创建
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 0; i < 10; i++) {
            //创建RunableTaskForPool对象(RunableTaskForPool类实现了Runnable 接口)
            Runnable worker = new RunableTaskForPool();
            //执行Runnable使用execute方法
            //执行Runnable和Callable类型任务用submit方法
            executor.execute(worker);
        }
        //终止线程池
        executor.shutdown();
    }
}

Java多线程(一文看懂!)_第1张图片

>>线程池获取线程优势:
  Tread/Runnable/Callable创建线程如果频繁创建和关闭会消耗大量系统资源,影响性能;
  因此更建议使用线程池来创建线程;
  由于线程池创建并管理多个线程,当程序需要使用线程时,直接从线程池获取即可,使用完成会再次放回线程池
  不需要频繁创建和销毁线程,可以大大提高系统性能,项目开发主要使用线程池的方式

>>线程池的执行流程:
  1. 提交一个线程任务,当线程池里存活的核心线程数小于线程数时,线程会创建一个核心线程去处理提交的任务
  2. 如果核心线程数已满,提交的线程就会放在任务队列中排队等待
  3. 当核心线程已满,任务队列已满,此时判断线程数是否到达最大数量,如果没有到达,则创建非核心线程执行提交的任务
  4. 如果核心线程数已到达,任务队列也已满,非核心线程数也到达,则采用拒绝策略处理

>>线程池的几种工作队列:
  ArrayBlockingQueue(有界队列):
  是一个基于数组结构的有界阻塞队列,此队列按先进先出原则排序
  
  LinkedBlockingQueue(可设置容量队列):
  一个基于链表结构的阻塞队列,也按照先进先出原则排序,吞吐量高于ArrayBlockingQueue
  
  SynchronousQueue(同步队列):
  一个不存储元素的阻塞队列,每个插入操作,必须等到另一个线程调用移除操作 否则插入操作一直处于
  阻塞状态,吞吐量通常高于LinkedBlockingQueue,CachedTreadPool线程池使用该队列
  
  PriorityBlockingQueue(优先级队列):
  一个具有优先级的无限阻塞队列
  
  DelayQueue(延迟队列):
  是一个任务定时周期的延迟执行队列,根据执行时间从小到大排序,
  否则根据插入到队列的先后顺序,newScheduledTreadPool线程池使用该队列


>>线程池的状态:
  RUNNING:线程池一旦被创建就处于该状态,任务数为0,能接收新任务,对以排队的业务进行处理
  SHUTDOWN:不接收新任务,但能处理已排队的业务;
         调用线程池的shutdown()方法,线程池由RUNNING转变为SHUTDOWN状态
  STOP:不接收新任务,不处理已排队业务,并且会中断正在处理的任务;
         调用线程池的shutdownNow()方法,线程池由RUNNING或SHUTDOWN转变为STOP状态
  TIDYING:线程在SHUTDOWN状态下任务数为空,其它所有任务已中止,线程池会变为TIDYING状态
         线程在STOP状态,线程池中执行任务为空时就会转变为TIDYING状态
  TERMINATED:线程池彻底中止,线程池在TIDYING执行完terminated()方法就会转变为TERMINATED状态

>>线程池的四种拒绝策略:
  AbortPolicy:默认策略,丢弃任务并抛出异常RejectedExectionExecption
  DiscardPolicy:丢弃任务,不抛异常
  DiscardOldestPolicy:丢弃队列最前面的任务,然后从新提交被拒绝的任务
  CallerRunPolicy:交给调用线程池的线程处理
  
  (《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建,而是通过
 ThreadPoolExecutor 构造函数的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规
 避资源耗尽的风险)
>>Executors工具类可创建的几种常见线程池:
  newCachedThreadPool:可缓存线程池
  newFixedThreadPool:固定线程数量的线程池
  newScheduledThreadPool:定时线程池,定时任务和周期任务使用
  ThreadPoolExecutor:自定义线程池,可控制:核心线程数(核心线程数不会被释放)/最大线程数
                       /最大空闲时间(到达最大空闲时间会自动回收的线程)/时间单位/等待队列
  • 用实现Callable接口的方式完成一个线程
public class CallableTaskForPool implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":线程任务被执行"+i);
        }
        return "Callable线程任务被执行";
    }
} 
  • 运用Callable接口完成的线程实现两个线程池
public class 带有缓冲区的线程池 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建一个带有缓冲区的线程池,返回一个ExecutorService对象,该对象用于对线程池进行管理
        //如:向线程池提交线程任务,关闭线程池等操作
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            Future<String> future= executorService.submit(new CallableTaskForPool());//向线程池提交任务
        }
        executorService.shutdown();//关闭线程池
    }
}
public class 固定数量的线程池 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);//传入线程数
        for (int i = 0; i < 20; i++) {
            executorService.submit(new CallableTaskForPool());
        }
        executorService.shutdown();
    }
}
  • 用实现Runnable接口的方式完成一个线程池
public class RunableTaskForPool implements Runnable {
    @Override
    public void run() {
        System.out.println("执行周期线程任务");
    }
}
  • 运用Runnable接口完成的线程实现两个线程池
public class 定时任务线程池 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        //五个参数:线程任务/延迟时间/间隔时间/时间单位
        ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(new RunableTaskForPool(), 0, 1000, TimeUnit.MILLISECONDS);
        scheduledFuture.get();//得到RunableTaskForPool()中的run方法返回的数据
        scheduledExecutorService.shutdown();
    }
}
public class 自定义线程池 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建等待队列
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);
        //参数:核心线程数(核心线程数不会被释放)/最大线程数/最大空闲时间(到达最大空闲时间会自动回收的线程)/时间单位/等待队列
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 10, 20, TimeUnit.MILLISECONDS, queue);
        for (int i = 0; i < 100; i++) {
            Future<?> future = threadPoolExecutor.submit(new RunableTaskForPool());
            future.get();//获得返回结果
        }
        threadPoolExecutor.shutdown();
    }
}

三,多线程的五种状态

    新建:当一个Thread类或者其子类被调用时,新生的线程对象处于新建状态,
         此时它已经有了相应的内存空间和其他资源,在新建状态下的线程不会被执行;
    就绪:当线程被创建,并调用了start方法,该线程就进入了就绪状态,
         该状态下的线程位于可运行池(线程池)等待获得cpu的使用权;
    运行:处于该状态的线程专用cpu
         只有处于就绪状态的线程才有机会转为运行状态;
    阻塞:放弃cpu资源,让其他资源获取,
          五种阻塞原因:
            1.位于等待池:执行wait方法,jvm就会将该线程放于等待池;
            2.位于锁池:试图获得某个对象同步锁时,如果该对象的同步锁已经被其他线程占用,
                       jvm会将这个线程放在这个对象的锁池中;
            3.执行了sleep方法;
            4.调用其他线程join方法;
            5.发出IO请求时;
    死亡:run方法结束,线程结束;

Java多线程(一文看懂!)_第2张图片

四,多线程的常用调度

  • wait:线程等待(该线程会被放在等待池,等待被唤醒,在同步锁中使用);
  • notify:唤醒一个处于等待的线程,在同步锁中使用;
  • notifyAll:唤醒所有等待的线程,在同步锁中使用;
  • sleep(静态方法):线程休眠,指定休眠一定时间,休眠期间线程处于堵塞状态,休眠结束,线程进入就绪状态等待获取cpu资源;(传入数字,默认单位为毫秒);
  • join:在一个线程中调用另一个线程的该方法,则该线程进入等待状态,等待调用线程执行结束,该线程才会继续执行;
  • yield(静态方法):线程让步,中止本次线程执行,让出cpu资源,回到就绪状态继续抢占cpu资源;
  • setPriority:设置线程优先级,只能相对的增大抢到cpu资源的概率(传入数字1-10);
  • setDaemon:守护线程,又叫精灵或者幽灵线程,当其它线程结束后该线程自动结束,如果尚未结束,jvm强行终止。通常情况可能会超出当然也就i体现了jvm强行中止(传入boolean值);
  • interrupt:其实它的作用就是传出一个线程应该中断的信号,至于该怎样中断线程,设计者来实现

五,线程的同步(多口售票问题)

>>百度的解释就很好:
  线程有可能和其他线程共享一些资源,比如,内存,文件,数据库等。
  当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,
  即各位线之间要有个先来后到(抢占cpu资源),不能一窝蜂挤上去抢作一团。
  线程同步的真实意思和字面意思恰好相反。
  线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个共享资源进行操作,而不是同时进行操作。
       
>>线程安全:
  当多个线程访问时,采用加锁机制,当一个线程访问某个类的数据时进行保护,其它线程不能访问
  直到该线程结束,其它线程才可访问,不会出现数据不一致和或者数据污染

>>保证线程安全的方式:
  1. 使用线程安全的类,如:CopyOnWriteArrayList,ConcurrentHashMap等
  2. synchronized同步代码块
  3. 同步方法
  4. 使用LOCK锁

>>线程死锁:
  多个线程同时被堵塞,他们中的一个或全部都在等待某个资源的释放,由于线程被无限期的阻塞,
  程序不能中止和运行的一种状态;
  例:A需要x,y两个资源,B也需要x,y两个资源;而抢占CPU时,A得到了x资源,B得到了y资源
        此时,A等待获取y资源,B等待获取x资源,线程未结束前又不能释放已获得的资源,
        因此造成了相互等待,形成死锁
  >线程死锁的四个条件:
  互斥条件:该资源任意一个时刻只能由一个线程占用
  请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  不剥夺条件:线程已获得的资源在未使用前不能被其他线程强行剥夺,只有自己使用完毕后才能释放资源
  循环等待条件:若干线程之间形成一种头尾相接的循环等待资源的关系
  >避免线程死锁: 破坏以上四个条件即可
  破坏互斥条件:无法破坏,因为我们用锁本身就是为了互斥访问
  破坏请求与保持条件:一次申请所有资源
  破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源
  破坏循环等待条件:使用按序申请资源来预防,按某一顺序申请资源,释放资源则反序释放,这样就可以破坏该条件
两种同步的方法
public class SynchronizedDemo {
    //方法1
    public void test1() {
        synchronized (this) {
            //使用this实现对象级同步
        }
    }

    //方法2(LOCK锁)
    private static final Object LOCK = new Object();
    public void test2() {
        synchronized (LOCK) {
            //方法级同步
        }
    }

    //方法3
    public synchronized void test3() {
        //对象级的方法同步
    }

    //方法4
    public static synchronized void test4() {
        //同步的静态方法,静态方法属于类,所以也可以实现对象级的同步
    }
}

例:多口售票(有助理解线程同步):

  描述:我只有10张票,给两个销售点去售卖,不能卖重复了,用多线程的思想解决
public class SaleTask {
    private int ticketNum;//定义票数

    //构造器设置票数
    public SaleTask(int ticketNum) {
        this.ticketNum = ticketNum;
    }

    //卖票方式
    public void doSale() {
        if (ticketNum <= 0) {
            System.out.println("没票了");
            return;
        }
        System.out.println(Thread.currentThread().getName() + ":第" + ticketNum + "张票");
        ticketNum--;
    }

    public static void main(String[] args) {
        //创建对象
        SaleTask sale1 = new SaleTask(10);
        //销售点一,匿名内部类形式实现线程的创建的法一
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    sale1.doSale();
                    try {
                        Thread.sleep(10);//让线程睡一会,方便销售点2和它抢占cpu
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "销售点1").start();
        //销售点二,匿名内部类形式实现线程的创建的法二
        new Thread("销售点2") {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    sale1.doSale();
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

Java多线程(一文看懂!)_第3张图片
很明显,票卖的五花八门; 所以就要实现线程同步

多口售票解决法一(线程同步法一,互斥同步)
    public void doSale() {
        synchronized (this) {
            if (ticketNum <= 0) {
                System.out.println("没票了");
                return;
            }
            System.out.println(Thread.currentThread().getName() + ":第" + ticketNum + "张票");
            ticketNum--;
        }
    }

多个对象时:

    //LOCK锁,指定了唯一对象
    private static final Object LOCK = new Object();
    //卖票方式
    public void doSale() {
        synchronized (LOCK) {
            if (ticketNum <= 0) {
                System.out.println("没票了");
                return;
            }
            System.out.println(Thread.currentThread().getName() + ":第" + ticketNum + "张票");
            ticketNum--;
        }
    }
多口售票解决法二(线程同步法二)
    //优化卖票方法
    public synchronized void doSale() {
        if (ticketNum <= 0) {
            System.out.println("没票了");
            return;
        }
        System.out.println(Thread.currentThread().getName() + ":第" + ticketNum + "张票");
        ticketNum--;
    }

多个对象时

    public static synchronized void doSale() {
            if (ticketNum <= 0) {
                System.out.println("没票了");
                return;
            }
            System.out.println(Thread.currentThread().getName() + ":第" + ticketNum + "张票");
            ticketNum--;
    }

六,线程的协作(生产者-消费者模型)

  • 线程协作:线程协作是在多线程互斥同步的基础上,使线程之间依照一定条件,有目的、
    有计划地交互协同工作,这是一种较高级的线程同步方式
    Java中线程间的通信是通过:wait-notify机制实现的,即waitnotifynotifyAll三个方法的灵活使用
    这三个方法均定义在 Object 类中,是 final 修饰的实例方法。
    这三个方法必须在 synchronized 代码中调用,而且只有锁对象才能调用,即持有锁对象监视器的线程才能调用锁对象的这三个方法
  • wait()
    调用该方法的线程放弃同步锁并进入等待状态(堵塞状态),直到其他线程
    获得相同的同步锁并调用 notify( )notifyAll()方法来唤醒。
  • notify( )
    通知在相同同步锁上处于等待的一个线程结束等待,进入到就绪状态。
    即唤醒一个等待(当前线程所持有)同步锁的线程。
  • notifyAll()
    通知在相同同步锁上处于等待的所有线程结束等待,进入到就绪状态。
    即唤醒所有等待(当前线程所持有)同步锁的线程

生产者消费者模型(助于理解线程协作)(传送门:阻塞队列实现生消模型)

 描述:我有一个面包店,有一个师傅生产面包,有一个师傅销售面包,怎样达到一种产销平衡

面包类

public class Bread {
    private String bread;
    public Bread() {
    }
}

操作类

public class BreadCabinet {
    private Bread[] breads;//定义一个面包数组,用于规定容器大小
    private int num;//用于统计当前面包数量

    public BreadCabinet(int initSize) {
        breads=new Bread[initSize];//初始化容器大小
    }
    public synchronized void doBread(Bread bread){
        while (breads.length==num){//说明面包箱满了
            System.out.println("面包箱满了,快卖");
            try {
                wait();//容器装满了,就让该线程进入等待状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        breads[num]=bread;
        num++;//做出一个面包
        System.out.println(Thread.currentThread().getName()+":"+"面包"+num+"出炉");
        notifyAll();//唤醒线程
    }
    public synchronized void saleBread(){
        while (num==0){
            System.out.println("面包箱没了,快做");
            try {
                wait();//没面包了,就让该线程进入等待状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+":"+"面包"+num+"售出");
        num--;//卖出一个面包
        notifyAll();//唤醒线程
    }
}

测试类

public class BreadTest {
    public static void main(String[] args) {
        BreadCabinet breadCabinet = new BreadCabinet(10);
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    Bread bread = new Bread();
                    breadCabinet.doBread(bread);
                }
            }
        },"生产者1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    breadCabinet.saleBread();
                }
            }
        },"消费者1").start();
    }
}

结果:实现了产销平衡
Java多线程(一文看懂!)_第4张图片

七,线程的中断

首先了解一下方法interruptinterruptedisInterruptedsleep三个方法
Java多线程(一文看懂!)_第5张图片
Java多线程(一文看懂!)_第6张图片
Java多线程(一文看懂!)_第7张图片
很明显上面的interrupt方法可以中断,但是需要特殊情况
interruptedisInterrupted貌似只能测试这个线程是否被中断
在这里插入图片描述

Java多线程(一文看懂!)_第8张图片
Java多线程(一文看懂!)_第9张图片
Java多线程(一文看懂!)_第10张图片

方法一:先看上面四张图介绍:经测试后,发现只要调用了上面的几个方法,就可以通过异常实现打断线程
方法二futureTask.cancel()方法传入true(该方法底层调用了interrupted()方法)
方法三: 通过接收信号的方式在子线程内主动结束线程(根据具体业务运用returnbreak等,)

描述:现有主线程一个,子线程一个,如何让主线程中断子线程
描述:让主线程在i=20时,结束子线程

建立子线程

public class CallableTask implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum=0;
        for (int i = 0; i < 100; i++) {
            sum+=i;
            System.out.println(Thread.currentThread().getName()+":"+sum);
        }
        return sum;
    }
}

开启子线程,主线程

public class CallableTaskTest {
    public static void main(String[] args) {
        //开启子线程
        CallableTask callableTask = new CallableTask();
        FutureTask<Integer> futureTask = new FutureTask<>(callableTask);
        Thread thread = new Thread(futureTask, "线程一");
        thread.start();
        //主线程
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
            if (i==20){
                thread.interrupt();
                if (thread.isInterrupted()){
                    System.out.println("线程中断成功");
                }
            }
        }
    }
}

Java多线程(一文看懂!)_第11张图片
方法一详解:

详解:
当我们将sleep的异常throws出的时候,是能够实现中断的
但当把该异常由throws改成try-catch再看看结果
得到不仅没有打断成功,而且还报出了睡眠被吵醒的异常
可见这是一种通过异常打断线程的方法,实现线程的打断的
因此给子线程加上sleep等方法配合使用interrupt方法就可以实现线程打断
         //子线程的循环加sleep
        for (int i = 0; i < 100; i++) {
            sum+=i;
            System.out.println(Thread.currentThread().getName()+":"+sum);
            Thread.sleep(0);
        }
         
            for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
            if (i==20){
                thread.interrupt();
                if (thread.isInterrupted()){
                    System.out.println("线程中断成功");
                }
            }
        }

Java多线程(一文看懂!)_第12张图片
在这里插入图片描述
方法二详解:

 详解:内部调用了interrupt()方法,原理与法一类似
        //子线程的修改方式
        for (int i = 0; i < 100; i++) {
            sum += i;
            System.out.println(Thread.currentThread().getName() + ":" + sum);
            Thread.sleep(0);
        }
        
        //主线程的修改方式
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
            if (i==20){
                futureTask.cancel(true);//发出线程中断信号
                if (thread.isInterrupted()){
                    System.out.println("线程中断成功");
                }
            }
        }

方法三详解:

详解:既然interrupted()可以发信号,isInterrupted()可以收信号,那就利用这一点来主动结束线程
        //子线程修改代码
        for (int i = 0; i < 100; i++) {
            sum += i;
            System.out.println(Thread.currentThread().getName() + ":" + sum);
            if (Thread.interrupted()){
                System.out.println("线程中断成功");
                break;//结束循环,相当于手动结束线程
            }
        }
        //主线程修改代码
                for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
            if (i==20){
                thread.interrupt();//发出终断信号
                if (thread.isInterrupted()){
                    System.out.println("线程中断信号发出");
                }
            }
        }

你可能感兴趣的:(Java多线程,多线程,线程池,java)