01、多线程的概念

  目前为止我们大部分程序都是单线程——都只有一条顺序执行流,程序从main方法开始执行,依次向下执行每条语句,如果程序执行某行代码时遇到了阻塞,则程序将会停滞在该处。实际情况是单线程的程序往往功能非常有限。多线程使得程序中的多个任务可以同时执行。java的重要功能之一就是内部支持多线程。

程序、进程、线程的概念

  程序:一个静止的app

       进程:一个运行的app,在内存里会出现一条进程

       线程:一个进程可能包含多个可以同时运行的任务。线程是指一个任务从头到尾的执行流程。

多线程的优点

  并发编程方便进行业务拆分,提升系统的并发能力。

  线程提供了一个任务的运行机制。对于java而言,可以在一个程序中并发的启动多个线程。这些线程可以在多处理器上同时运行(并行)。在单处理器系统中,多个线程共享CPU时间称为时间分享,而操作系统负责调度并分配资源给它们(并发)。

  多线程可以使程序反应更快、交互性更强、执行效率更高。在一些情况下,即使在单处理器系统上,多线程程序的运行速度也比单线程程序更快。Java对多线程程序的创建和运行,以及锁定资源以避免冲突提供了非常好的支持。

  可以在程序中创建附加的线程以执行并发任务。在java中,每个任务都是Runnable接口的一个实例,也称为可运行对象。线程本质上来讲就是便于任务执行的对象。

java中实现多线程的方式

java中实现多线程的方式一:实现Runnable接口

  任务需要实现Runnable,任务必须从线程开始执行

package edu.uestc.avatar;

public class TaskThreadDemo {
    public static void main(String[] args) {
        //创建任务
        Runnable printA = new PrintLetter('A', 100);
        Runnable printB = new PrintLetter('B', 100);
        Runnable printNumber = new PrintNumber(100);
        
        //任务必须从线程开始运行
        Thread t1 = new Thread(printA);
        Thread t2 = new Thread(printB);
        Thread t3 = new Thread(printNumber);
        
        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

/**
 * 任务需要实现Runnable,任务必须从线程开始执行
 * 该任务完成打印特定字母times次
 *
 */
class PrintLetter implements Runnable{
    private char letter;
    private int times;
    
    public PrintLetter(char letter, int times) {
        this.letter = letter;
        this.times = times;
    }

    /**
     * 指明如何完成这个任务
     */
    @Override
    public void run() {
        for(int i = 0; i < times; i++)
            System.out.print(letter);
    }
}

/**
 * 该任务连续打印数字
 *
 */
class PrintNumber implements Runnable{
    private int lastNumber;
    
    
    public PrintNumber(int lastNumber) {
        this.lastNumber = lastNumber;
    }


    @Override
    public void run() {
        for(int i = 1; i <= lastNumber; i++)
            System.out.print(i + " ");
    }
    
}

java中实现多线程的方式二:继承Thread类

       由于Thread类实现了Runnable接口,所以可以定义一个类扩展自Thread类,并且覆盖run方法。

package edu.uestc.avatar;

public class CustomThreadClient {
    public static void main(String[] args) {
        //创建线程
        Thread custom = new CustomThread();
        //启动线程
        custom.start();
    }
}

/**
 * 由于Thread类实现了Runnable接口,
 * 所以可以定义一个类扩展自Thread类,并且覆盖run方法。
 *
 */
class CustomThread extends Thread{
    private int ticket = 100;

    @Override
    public void run() {
        while(ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "正在卖出编号为" + ticket + "的票");
            ticket--;
        }
    }
}

  但是,不推荐使用这种方法,因为它将任务和运行任务的机制混在了一起。将任务从线程中分离出来是比较好的设计。同时,Java存在单继承的限制。

java中实现多线程的方式三:线程池

  使用方法一或方法二对单一任务的执行是很方便的,但是由于必须为每一个任务创建一个线程。因此对大量任务而言是不够高效的。为每一个任务开始一个新线程可能会限制吞吐量并且造成性能降低。线程池是管理并发执行任务个数的理想方法。Java提供Executor接口来执行线程池中的任务,提供ExecutorService接口来管理和控制任务。ExecutorService是Executor子接口。

  为了创建一个Executor对象,可以使用Executors类中的静态方法

  • newFixedThreadPool(int):在线程池中创建可重用的固定数目的线程。如果线程完成了任务的执行,它可以被重新使用以执行另外一个任务。如果线程池中所有的线程都不是处于空闲状态,而且有任务在等待执行,那么在关闭之前,如果由于一个错误终止了一个线程,就会创建一个新线程来替换它
  • newCachedThreadPool():如果线程池中所有的线程都不是处于空闲状态,而且有任务在等待执行,会创建一个新线程。如果缓冲池中的线程在60秒内都没有被使用就该终止它。对许多小任务而言,一个缓冲池已经足够
  • newSingleThreadExecutor():创建一个只有一个线程的线程池。
  • newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,可以在指定延迟后执行任务。corePoolSize指池中保存的线程数,即使线程是空闲的也保存在池中
  • newSingleThreadScheduledExecutor():创建一个只有一个线程的线程池,可以在指定延迟后执行任务
  • newWorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别,该方法会使用多个队列来减少竞争。newWorkStealingPool()是为简化版本,以机器CPU数量作为参数传入。
package edu.uestc.avatar;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorDemo {
    public static void main(String[] args) {
        //ExecutorService executor = Executors.newFixedThreadPool(3);
        //顺序执行任务
        ExecutorService executor = Executors.newFixedThreadPool(1);
        //提交任务给线程池
        executor.execute(new PrintLetter('A', 100));
        executor.execute(new PrintLetter('B', 100));
        executor.execute(new PrintNumber(100));
        //关闭执行器,允许执行器中的任务完成,一旦关闭,不再接收新的任务
        executor.shutdown();
    }
}

创建线程方式四:Callable

  Callable提供了一个call()方法可以作为线程执行体;比run方法功能更强大,

  • 可以获得任务执行返回值;
  • 通过与Future的结合,可以实现利用Future来跟踪异步计算的结果

  Future接口代表Callable接口里call()方法的返回值,提供了FutureTask实现类,且实现了Runnable接口,可以使用FutureTask实例作为Thread类的target

  • Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、等待完成和得到计算的结果。
  • 当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。
  • 如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。
  • 一旦计算完成了,那么这个计算就不能被取消。

  FutureTask类

  • FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以说FutureTask是一个提供异步计算的结果的任务。
  • FutureTask可以用来包装Callable或者Runnbale对象。因为FutureTask实现了Runnable接口,所以FutureTask也可以被提交给Executor
package edu.uestc.avatar;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableAndFutureDemo {
    public static void main(String[] args) {
        //创建Callable对象,使用FutureTask来保证Callable对象
        FutureTask<Integer> task = new FutureTask<Integer>(()->{
            int i = 0;
            for(; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "循环遍历i的值:" + i);    
            }
            //call()方法有返回值
            return i;
        });
        
        for(int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "循环遍历i的值:" + i);    
            if(i == 20) {
                new Thread(task,"有返回值的线程").start();
            }
        }
        
        //获取线程的返回值
        try {
            System.out.println(task.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

 

posted @ 2022-04-18 13:14  Tiger-Adan  阅读(722)  评论(0编辑  收藏  举报