Loading

怎么去创建多线程

线程是什么?

线程被称为轻量级进程,是程序执行的最小单位,它是指在程序执行过程中,能够执行代码的一个执行单位。每个程序程序都至少有一个线程,也即是程序本身。

线程的状态

Java语言定义了5种线程状态,在任意一个时间点,一个线程只能有且只有其中一个状态。这5种状态如下:

image-20220307190549448
  • 新建(New):创建后尚未启动的线程处于这种状态
  • 运行(Runable):Runable包括了操作系统线程状态的RunningReady也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间。
  • 等待(Wating):处于这种状态的线程不会被分配CPU执行时间。等待状态又分为无限期等待和有限期等待,处于无限期等待的线程需要被其他线程显示地唤醒,没有设置Timeout参数的Object.wait()、没有设置Timeout参数的Thread.join()方法都会使线程进入无限期等待状态;有限期等待状态无须等待被其他线程显示地唤醒,在一定时间之后它们会由系统自动唤醒,Thread.sleep()、设置了Timeout参数的Object.wait()、设置了Timeout参数的Thread.join()方法都会使线程进入有限期等待状态。
  • 阻塞(Blocked):线程被阻塞了,“阻塞状态”与”等待状态“的区别是:”阻塞状态“在等待着获取到一个排他锁,这个时间将在另外一个线程放弃这个锁的时候发生;而”等待状态“则是在等待一段时间或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。
  • 结束(Terminated):已终止线程的线程状态,线程已经结束执行。

创建单线程的方式

  • 继承Thread
public class ThreadTest {
 
    public static void main(String[] args) {
        //设置线程名字
        Thread.currentThread().setName("main thread");
        MyThread myThread = new MyThread();
        myThread.setName("子线程:");
        //开启线程
        myThread.start();
        for(int i = 0;i<5;i++){
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}
 
class MyThread extends Thread{
    //重写run()方法
    public void run(){
        for(int i = 0;i < 10; i++){
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}
  • 实现 Runnable 接口
public class RunnableTest {
  
    public static void main(String[] args) {
        //设置线程名字
        Thread.currentThread().setName("main thread:");
        Thread thread = new Thread(new MyRunnable());
        thread.setName("子线程:");
        //开启线程
        thread.start();
        for(int i = 0; i <5;i++){
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}
  
class MyRunnable implements Runnable {
  
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}
  • 实现Callable接口。相较于实现Runnable 接口的实现,方法可以有返回值,并且抛出异常
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//实现Callable接口
public class CallableTest {
 
    public static void main(String[] args) {
        //执行Callable 方式,需要FutureTask 实现实现,用于接收运算结果
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
        new Thread(futureTask).start();
        //接收线程运算后的结果
        try {
            Integer sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
 
class MyCallable implements Callable<Integer> {
 
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        return sum;
    }
}

什么是FutureTask?

  • FutureTask可⽤于异步获取执⾏结果或取消执⾏任务的场景。通过传⼊Runnable或者Callable的任务给FutureTask,直接调⽤其run⽅法或者放⼊线程池执⾏,之后可以在外部通过FutureTaskget⽅法异步获取执⾏结果,因此,FutureTask⾮常适合⽤于耗时的计算,主线程可以在完成⾃⼰的任务后,再去获取结果。另外,FutureTask还可以确保即使调⽤了多次run⽅法,它都只会执⾏⼀次Runnable或者Callable任务,或者通过cancel取消FutureTask的执⾏等。
  • futuretask可⽤于执⾏多任务、以及避免⾼并发情况下多次创建数据机锁的出现。

Runnable接口 和 Callable接口有什么区别?

  • Runnable接⼝中的run()⽅法的返回值是void,它做的事情只是纯粹地去执⾏run()⽅法中的代码⽽已;

  • Callable接⼝中的call()⽅法是有返回值的,是⼀个泛型,和Future、FutureTask配合可以⽤来获取异步执⾏的结果。

start()⽅法和run()⽅法的区别?

  • start()⽅法来启动⼀个线程,真正实现了多线程运⾏。
  • 如果直接调⽤run(),其实就相当于是调⽤了⼀个普通函数⽽已,直接调⽤run()⽅法必须等待run()⽅法执⾏完毕才能执⾏下⾯的代码,所以执⾏路径还是只有⼀条,根本就没有线程的特征,所以在多线程执⾏时要使⽤start()⽅法⽽不是run()⽅法。

什么是线程池?

很简单,简单看名字就知道是装有线程的池子,我们可以把要执行的多线程交给线程池来处理,和连接池的概念一样,通过维护一定数量的线程池来达到多个线程的复用。 池化技术的思想主要是为了减少每次获取资源的消耗,提⾼对资源的利⽤率。

我们知道不用线程池的话,每个线程都要通过new Thread(xxRunnable).start()的方式来创建并运行一个线程,线程少的话这不会是问题,而真实环境可能会开启多个线程让系统和程序达到最佳效率,当线程数达到一定数量就会耗尽系统的 CPU 和内存资源,也会造成 GC频繁收集和停顿,因为每次创建和销毁一个线程都是要消耗系统资源的,如果为每个任务都创建线程这无疑是一个很大的性能瓶颈。所以, 线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也会自动销毁,而不会长驻内存。

  • 线程池核心类
image-20220307192625996
  • 线程池的执行原理
image-20220307194146967

提交一个任务到线程池中,线程池的执行顺序如下:

  1. 当线程数⼩于corePoolSize时,创建线程执⾏任务。

  2. 当线程数⼤于等于corePoolSize并且workQueue没有满时,放⼊workQueue

  3. 线程数⼤于等于corePoolSize并且当workQueue满时,新任务新建线程运⾏,线程总数要⼩于maximumPoolSize

  4. 当线程总数等于maximumPoolSize并且workQueue满了的时候执⾏handlerrejectedExecution。也就是拒绝策略。

  • 线程池的创建

一类是通过 ThreadPoolExecutor 创建的线程池;另一个类是通过 Executors 创建的线程池。

image-20220307200407253
//三大方法、七大参数、4中拒绝策略
ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单个线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5); //创建一个固定大小的线程池
ExecutorService threadPool =Executors.newCachedThreadPool(); //可以伸缩的线程池

在阿里巴巴的《Java开发手册》中强制规定线程池的创建不允许使用Executors去创建线程,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学,更加明确线程的运行规则,规避资源耗尽的风险

image-20220307200701855
//7大参数
int corePoolSize 核心线程大小
int maximumPoolSize 最大核心线程池大小
long keepAliveTime 超时了 没有人连接就会释放
TimeUnit unit 超时单位
BlockingQueue<Runnable> workQueue 阻塞队列
ThreadFactory 线程工厂
RejectedExecutionHandler handler 拒绝策略
//4 中拒绝策略
AbortPolicy         超出最大承载数就会抛出异常 RejectedExecutionException
CallerRunsPolicy    那来的去哪里 直接运行这个任务的run方法,但并非是由线程池的线程处理,而是交由任务的调用线程处理
DiscardOldestPolicy 将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行 队列满了,尝试去和											最早地去竞争 也不会抛出异常
DiscardPolicy       直接丢弃任务,不抛出任何异常 队列满了,不会抛出异常

为什么要使用线程池创建多线程?

我们知道不用线程池的话,每个线程都要通过 new Thread(xxRunnable).start()的方 式来创建并运行一个线程,线程少的话这不会是问题,而真实环境可能会开启多个线程让系统和程序达到最佳效率,当线程数达到一定数量就会耗尽系统的 CPU 和 内存资源,也会造成 GC频繁收集和停顿,因为每次创建和销毁一个线程都是要消 耗系统资源的,如果为每个任务都创建线程这无疑是一个很大的性能瓶颈。所以, 线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也会自动销毁,而不会长驻内存。

ThreadPool 优点

  • 减少了创建和销毁线程的次数,每个⼯作线程都可以被重复利⽤,可执⾏多个任务

  • 可以根据系统的承受能⼒,调整线程池中⼯作线线程的数⽬,防⽌因为因为消耗过多的内存,⽽把服务器累趴下(每个线程需要⼤约1MB内存,线程开的越多,消耗的内存也就越⼤,最后死机)

    1. 减少在创建和销毁线程上所花的时间以及系统资源的开销

    2. 如不使⽤线程池,有可能造成系统创建⼤量线程⽽导致消耗完系统内存

使用线程池创建多线程的方式

package org.yun.ThreadPool;

import java.security.AccessController;
import java.util.concurrent.*;

/**
 * 线程池的创建
 * 线程的三大方法
 * 7大参数
 * 4中拒绝策略
 */
public class Demo1 {
    public static void main(String[] args) {
        //线程池的三大方法
//        ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单个线程
//        ExecutorService threadPool = Executors.newFixedThreadPool(5); //创建一个固定大小的线程池
//        ExecutorService threadPool =Executors.newCachedThreadPool(); //可以伸缩的线程池
        //自定义线程池!工作 ThreadPoolExecutor

        //获取CPU的最大核数
//        System.out.println(Runtime.getRuntime().availableProcessors());
        ExecutorService threadPool = new ThreadPoolExecutor(2,
                                                            5,
                                                            3,
                                                            TimeUnit.SECONDS,
                                                            new LinkedBlockingQueue<>(3),
                                                            Executors.defaultThreadFactory(),
                                                            //拒绝策略 银行满了,还有人进来,不处理这个人,抛出异常
                                                            new ThreadPoolExecutor.DiscardPolicy());
        try {
            //使用线程池创建线程
            //最大承载 : queue+maX
            //超出最大承载数就会抛出异常  RejectedExecutionException
            for (int i = 1; i <= 9; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完要关闭
            threadPool.shutdown();
        }
    }
}
posted @ 2022-03-07 20:16  BearBrick0  阅读(97)  评论(0编辑  收藏  举报