• Java 多线程原理


       Java 中多线程部分是Java 开发中的重要组成部分。创建java 多线程常用有三个方法:

       1、继承Thread 类创建线程。

       2、实现Runnable 接口创建线程。

       3、实现Callable和Future 创建线程。

    ------------------------继承Thread类创建线程---------------------

    通过继承Thread类来创建并启动多线程的一般步骤如下:

    1)创建Thread 的子类,重写run()方法,该方法的方法体是线程要完成的任务。run()也称为线程执行体。

    2)创建Thread的子类的实例,也就是创建了线程对象。

    3)  启动线程,调用线程的start()方法。 run()方法是运行当前线程,start()是启动一个新的线程。

    实例:

      public class  subThread extends Thread{

      public void run(){

        //重写run方法

      }

    }

     public class ThreadTest{

           public static void main(String[] args){

          new SubThread().start(); //创建并启动线程

            }

      }

    ------------------------实现Runnable接口创建线程---------------------

     通过实现Runnable 接口来创建并启动多线程的一般步骤如下:

      1) 定义Runnable 接口的实现类,一定要重写Run()方法,这个run()方法和Thread 中的run() 一样是线程的执行体。

      2) 创建Runnable 实现类的实例,并用这个实例作为Thread的target 来创建Thread对象,这个Thread对象才是真正的线程对象。

      3) 通过调用线程对象的start()方法来启动线程。

      public class SubThread2{

      public void  run(){

           ///重写run() 方法

         }

     }

      public class TestMain(){

        public static void main(String[] args){

            SubThread2 subThrad=new SubThread2();

                             Thread thread=new Thread(subThrad);

                              thread.start(); 

                 } 

       }

    ------------------------使用Callable和Future创建线程---------------------

    和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。

    》call()方法可以有返回值

    》call()方法可以声明抛出异常

    Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

    >boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务

    >V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值

    >V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException

    >boolean isDone():若Callable任务完成,返回True

    >boolean isCancelled():如果在Callable任务正常完成前被取消,返回True

    介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:

    1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

    2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值

    3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

    4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

    代码实例:

    public class Main {

      public static void main(String[] args){

       MyThread3 th=new MyThread3();

       //使用Lambda表达式创建Callable对象

       //使用FutureTask类来包装Callable对象

       FutureTask<Integer> future=new FutureTask<Integer>(

        (Callable<Integer>)()->{

          return 5;

        }

       );

       new Thread(task,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程

       try{

        System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回

       }catch(Exception e){

        ex.printStackTrace();

       }

      }

    }

    --------------------------------------三种创建线程方法对比--------------------------------------

    实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,后者线程执行体run()方法无返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:

    1、线程只是实现Runnable或实现Callable接口,还可以继承其他类。

    2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

    3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。

    4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。

    注:一般推荐采用实现接口的方式来创建多线程

    -----------------------------------------线程池原理-----------------------------------------------------------------------------------------

    JAVA  ExecutorService 中线程池主要有四种,

    Java通过Executors提供四种线程池,分别为:

    newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

    newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

    newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

    newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

    (1). newCachedThreadPool
    创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
    final int index = i;
    try {
    Thread.sleep(index * 1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
      
    cachedThreadPool.execute(new Runnable() {
      
    @Override
    public void run() {
    System.out.println(index);
    }
    });
    }

    线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

    缺点:当线程无法控制使用时,容易造成内存溢出。

    (2). newFixedThreadPool
    创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:

    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

    for (int i = 0; i < 10; i++) {
    final int index = i;
    fixedThreadPool.execute(new Runnable() {
      
    @Override
    public void run() {
    try {
    System.out.println(index);
    Thread.sleep(2000);
    catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    });
     

    因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。

    定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()

    (3) newScheduledThreadPool
    创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:

    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    scheduledThreadPool.schedule(new Runnable() {
      
    @Override
    public void run() {
    System.out.println("delay 3 seconds");
    }
    }, 3, TimeUnit.SECONDS);

    表示延迟3秒执行。

    定期执行示例代码如下:

    scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
      
    @Override
    public void run() {
    System.out.println("delay 1 seconds, and excute every 3 seconds");
    }
    }, 1, 3, TimeUnit.SECONDS);

    表示延迟1秒后每3秒执行一次。

    ScheduledExecutorService比Timer更安全,功能更强大,后面会有一篇单独进行对比。

    (4)、newSingleThreadExecutor
    创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:

    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
    final int index = i;
    singleThreadExecutor.execute(new Runnable() {
      
    @Override
    public void run() {
    try {
    System.out.println(index);
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    });
    }

    果依次输出,相当于顺序执行各个任务。

    线程池的作用:

    线程池作用就是限制系统中执行线程的数量。
         根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排 队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池 中有等待的工作线程,就可以开始运行了;否则进入等待队列。

    为什么要用线程池:

    1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

    2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

    Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

    比较重要的几个类:

    ExecutorService

    真正的线程池接口。

    ScheduledExecutorService

    能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。

    ThreadPoolExecutor

    ExecutorService的默认实现。

    ScheduledThreadPoolExecutor

    继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

  • 相关阅读:
    I/O性能优化
    磁盘性能指标
    磁盘I/O工作原理
    文件系统工作原理
    Linux命令行工具之free命令
    内存性能优化
    内存工作原理
    内存中的Buffer和Cache的区别
    proc文件系统
    oracle 查询 LOB 字段大小
  • 原文地址:https://www.cnblogs.com/shunxiyuan/p/8589985.html
Copyright © 2020-2023  润新知