• 多线程基础学习


    多线程的好处

    1、并行编程可以使程序执行速度极大的提高,java本身是一种多线程语言。

    2、使用多线程可以利用机器额外的处理器,资源充分利用。

    简单介绍

    java的线程机制是抢占式的,这表示调度机制会周期性的中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都会分配到数量合

    理的时间去驱动它的任务,并发编程使我们可以将程序划分为多个分离的、独立运行的任务。

    任务实现

    通过实现Runnable接口实现线程:

    public class LiftOff implements Runnable{

    //打印100以内的数
    @Override
    public void run() {
    for (int i = 0; i < 100; i++) {
    System.out.println(i);
    }
    }
    }

    当从Runnable导出一个类时,必须具有run()方法,但是这个方法并无特殊之处————它不会产生任何内在的县城能力,要实现线程行为,你必须显式地将一个任务附着到线程上。
    将Runnable类提交给Thread的构造器,通过Thrad.start()启动线程,调用之后主线程立即返回执行下面的打印逻辑,启动的新线程再执行它的run()方法。

    public class TestThread {
    public static void main(String[] args) {
    Thread t = new Thread(new LiftOff());
    t.start();
    System.out.println("waiting for thread !");
    }

    }

    通过集成Thread类来实现线程:

    public class SimpleThread extends Thread {
    private int countDown = 5;
    private static int threadCount = 0;

    public SimpleThread() {
    super(Integer.toString(++threadCount)); //通过构造器为线程赋名,改名字可以通过Thread.getName()获得
    start();
    }

    @Override
    public String toString() {
    return "#" + getName() + "(" + countDown + ") .";
    }

    @Override
    public void run() {
    while (true){
    System.out.println(this);
    if(--countDown == 0) return;
    }
    }

    public static void main(String[] args) {
    for (int i = 0; i < 5; i++) {
    new SimpleThread();
    }
    }
    }






    通过实现Callable接口实现线程:

    如果希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。

    public class TaskWithResult implements Callable<String> {

    private int id;

    public TaskWithResult(int id) {
    this.id = id;
    }

    @Override
    public String call() throws Exception {
    return "result of TaskWithResult is " + id ;
    }
    }
    测试主类:
    public class TestCallThread {

    public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    ArrayList<Future<String>> results = new ArrayList<>();

    for (int i = 0; i < 10; i++) {
    results.add(executorService.submit(new TaskWithResult(i)));
    }

    for (Future<String> fs : results) {
    try {
    System.out.println(fs.get());
    } catch (InterruptedException e) {
    System.out.println(e);
    return;
    } catch (ExecutionException e) {
    e.printStackTrace();
    return;
    }finally {
    executorService.shutdown();
    }
    }
    }
    }

    ExecutorService.add()方法添加一个任务之后,会返回一个Future,通过Future的Future.isDone()可以查询任务是否已经完成,如果完成了可以调用Future.get(),此时get()方法会阻塞,直至结果准备就绪。

    线程池的使用

    为什么使用线程池:

    1、每次new Thread() 新建对象,性能差;

    2、线程缺乏统一的管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或OOM(内存溢出);

    3、缺少更多的功能,如更多执行,定期执行,线程中断。

    线程池的好处:

    1、重用 存在的线程,减少对象的创建、消亡的开销,性能佳;

    2、可有效的控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞;

    3、提供定时执行,定期执行,单线程,并发控制等高级功能。

    在实际多线程开发中,使用线程池管理线程是优选方法。

    线程池的类:

    ThreadPoolExecutor

    三个重要参数:

    corePoolSize:核心线程数量; 线程数少于该值,创建新线程,即使存在空闲线程。

    maximumPoolSize:线程最大线程数量;线程数少于该值,且大于corePoolSize,只有当阻塞队列满的时候会创建线程。

    workQueue:阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。当线程达到最大线程数量时,新提交的任务会添加到阻塞队列里,当阻塞队列满的时候,会使用下文的拒绝策略。

    keepAliveTime:线程没有任务执行时最多保持多久时间终止。当线程数量大于核心线程数的时候,如果一个线程超过这个时间,没有任务提交,线程即销毁。

    unit:keepAliveTime的时间单位。

    ThreadFactory:线程工厂,用来创建线程,默认会使用默认的线程工厂来创建线程,使用默认的线程工厂创建线程的时候,线程具有相同的优先级,非守护的线程,具有线程名称的线程。

    rejecthandler:当拒绝处理任务时的策略。

    a、直接抛出异常(默认策略)(AbortPolicy)

    b、用调用者的线程处理任务  (CallerRunsPolicy)

    c、丢弃队列中最靠前的任务。(DiscardOldestPolicy)

    d、直接丢弃任务。(DiscardPolicy)

    如果想使cpu有一个合理的利用:建议设置较大的阻塞队列大小,较小的线程池容量。


    public class ExecutorDemo {
    public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(new LiftOff());
    executorService.shutdown();  //使线程池继续执行已经提交的任务,但是不再接受新任务
    }
    }

    几种不同的Executor:

    • FixedThreadPool:一次性预先的执行代价高昂的线程分配,因而也就限制了线程的数量了。
      • 超出的数量会在队列中等待
    • CachedThraedPool: 这种执行器在执行过程中通常会创建于所需数量相同的线程,然后在他回收旧线程时停止创建新线程,因此他是合理的Executor的首选。只有当这种方式会引发问题时,你才需要切换到FixedThreadPool;
      • Executors.newCachedThreadPool()–>可缓存的线程池
    • SingleThreadPool:线程数量为1的FixedThradPool,这种线程池,如果提交了多个任务,每个任务都会在下一个任务开始之前运行结束,所有的任务将使用相同的线程每个任务都是按照他们被提交的顺序,并且是在下一个任务开始之前完成的。SingleThreadExecutor会序列化所有提交给他的任务,并会维护它自己的(隐藏)的悬挂任务队列。
    • ScheduledThreadPool:也是定长的线程池,支持定时,周期性的任务执行

    用法:

    //延迟3秒执行
    public static void main(String[] args) {
    //定义一个实例
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
    scheduledExecutorService.schedule(new Runnable() {
    @Override
    public void run() {
    System.out.println("hello!");
    }
    },3,TimeUnit.SECONDS);
    }

    //延迟1秒,每隔3秒执行一次

    public static void main(String[] args) {
    //定义一个实例
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
    scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
    System.out.println("hello!");
    }
    },1,3,TimeUnit.SECONDS);
    }




    线程池的几种状态:

    image

    1、Running:接受新提交的任务,并且也能处理阻塞队列当中的任务;

    2、ShutDown:不能再接受新提交的任务,但能提交阻塞队列保存的任务;

    3、Stop:不再接受新提交的任务,也不处理阻塞队列中的任务;

    4、Tidying:所有的线程终止,线程池中的工作线程数量为0;

    5、Terminated:

    线程池重要方法:

    1、execute():提交任务,交给线程池执行。

    2、submit():提交任务,能够返回执行结果,execute+Future。

    3、shutDown():关闭线程,等待任务都执行完。

    4、shutDownNow():关闭线程,不等待任务执行完,立即关闭。

    以下几个方法可以对线程的实例进行监控:

    5、getTaskCount():线程池已执行和未执行的任务总数

    6、getCompleteTaskCount():已完成的任务数量

    7、getPoolSize():线程池当前的线程数量

    8、getActiveCount():当前线程池中正在执行任务的线程数量

    线程池的合理配置:

    1、CPU密集型任务:需要尽量压榨CPU,参考值可以设为N(CPU + 1);

    2、IO密集型任务,参考值可以设置为2*N(CPU);




    线程方法

    休眠    Thread.sleep()

    该方法使线程中止执行给定的时间。

    设置优先级    Thread.setPriority()

    线程的优先级将该线程的重要性传递给了调度器。尽管cpu处理现有线程集的顺序是不确定的,但是调度器将倾向于让优先级最高的线程先执行。

    public class SimplePriorities implements Runnable {

    private int countDown = 5;
    private volatile double d ;
    private int priority;


    public SimplePriorities(int priority) {
    this.priority = priority;
    }

    @Override
    public String toString() {
    return Thread.currentThread() + ": " + countDown;
    }

    @Override
    public void run() {
    Thread.currentThread().setPriority(priority);
    while (true){
    for (int i = 0; i < 100000; i++) {
    d += (Math.PI + Math.E) / (double)i;
    if(i % 1000 == 0){
    Thread.yield();
    }
    System.out.println(this);
    if(--countDown == 0) return;
    }
    }
    }
    }
    由于考虑到线程级别与操作系统兼容映射的问题,建议调整优先级的时候,只使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY三种级别。

    让步  Thread.yield()

    该方法使建议具有相同优先级的其他线程可以运行,但是建议不一定被采用,继续执行的可能还是这个线程。

    后台线程(守护线程)

    后台线程(守护线程)是指在程序运行的时候在后台提供一种通用的服务的线程,并且这种线程并不属于程序中不可或缺的部分。

    特点:非守护线程全部结束时,程序终止,同时会杀死进程中所有的后台线程。

    必须在启动线程之前调用 Thread.setDaemon()方法,才能把它设置为守护线程。

    isDaemon()可以用来判断一个线程是否是守护线程,如果是一个守护线程,那么他创建的所有线程都是守护线程。

    当非守护线程结束之后,会立即关闭正在执行的守护线程,从而不会按照既定的逻辑再次往下走,比如try…catch(){}finally{}  fianlly中的语句也不会再终止时执行。




  • 相关阅读:
    List<T>Find方法,FindAll方法,Contains方法,Equals方法
    C#SerialPort如何读取串口数据并显示在TextBox上
    49、css属性相关
    40、协程
    45、mysql 储存过程
    37、进程之间的通信
    38、线程及其方法
    39、多线程和线程池
    33、验证客户端的合法性、socketserver模块
    32、黏包的解决方式、struct模块
  • 原文地址:https://www.cnblogs.com/xujie09/p/10263125.html
Copyright © 2020-2023  润新知