Java多线程详解(面试回顾)

1,什么是多线程

一个进程中可以并发多个线程,每条线程并行执行不同的任务,多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

2,线程的生命周期

线程的五种基本状态

  1. 新建状态(New):线程对象创建后,就是这种状态。

  2. 就绪状态(Runnable):也称可运行状态,在线程对象调用start()方法后,就进入就绪状态,等待获取CPU资源。

  3. 运行状态(Running):运行状态,线程真正的执行状态,当获取CPU资源后,对象调用run()方法后进入,就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中。

  4. 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,进入阻塞状态,直到其再次进入就绪状态,获取到CPU资源,才可被再次执行,根据产生阻塞的原因,阻塞状态可细划分为三种。

    • 等待阻塞:运行状态中的线程执行wait()方法,进入等待状态。

    • 同步阻塞:线程在获取synchronized同步锁失败(锁被其他线程占用),就会进入同步阻塞状态。

    • 其他阻塞:通过调用线程的sleep()或者join()发出I/O请求时,当sleep()状态超时或join()等待线程终止或者超时时、或者I/O处理完毕时,线程就会重新进入就绪状态。

  5. 死亡状态(Dead):线程执行完了或者因为异常退出了run()方法,该线程生命周期结束。

    扫描二维码关注公众号,回复: 4465480 查看本文章

3,线程的创建

1,继承Thread类

继承Thread类,重写该类的run()方法,不推荐,由于类的单继承性,不利于类的扩展。

package com.pactera.test;
​
public class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}
package com.pactera.test;
​
public class ThreadTest1 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "+++" + i);
            if (i == 3) {
                //创建线程1
                Thread thread1 = new MyThread1();
                //创建线程2
                Thread thread2 = new MyThread1();
                //线程1进入就绪状态
                thread1.start();
                //线程2进入就绪状态
                thread2.start();
            }
        }
    }
}

2,实现Runnable接口

实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。

package com.pactera.test;
​
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        //线程的方法执行体
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}
package com.pactera.test;
​
public class ThreadTest1 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "+++" + i);
            if (i == 3) {
                //创建实例
                MyRunnable myRunnable = new MyRunnable();
                //Thread对象才是真正的实例
                //创建线程1
                Thread thread1 = new Thread(myRunnable);
                //创建线程2
                Thread thread2 = new Thread(myRunnable);
                //线程1进入就绪状态
                thread1.start();
                //线程2进入就绪状态
                thread2.start();
            }
        }
    }
}

本质上Thread也实现了Runnable接口,并在run()方法里进行了一个判断

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

这里的target就是一开始传入的myRunnable,所以这里run()方法执行的依然是MyRunnable类里面的run()方法。

3,实现Callable接口

创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。

package com.pactera.test;
​
import java.util.concurrent.Callable;
​
public class MyCallable implements Callable<Integer> {
​
​
    @Override
    public Integer call() throws Exception {
        int j = 0;
        for (int i = 0; i < 10; i++) {
            j++;
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
        return j;
    }
}
package com.pactera.test;
​
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
​
public class ThreadTest1 {
    public static void main(String[] args) {
        //创建实例
        MyCallable myCallable = new MyCallable();
        //使用FutureTask类包装
        FutureTask<Integer> future1 = new FutureTask<Integer>(myCallable);
        FutureTask<Integer> future2 = new FutureTask<Integer>(myCallable);
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "+++" + i);
            if (i == 3) {
                //创建线程1
                Thread thread1 = new Thread(future1);
                //创建线程2
                Thread thread2 = new Thread(future2);
                //线程1进入就绪状态
                thread1.start();
                //线程2进入就绪状态
                thread2.start();
            }
        }
​
        //获取新创建的线程中call()方法返回的结果
        try {
            //在所有的线程执行完成之后这里才会执行,否则就是阻塞状态,所以结果会一致
            Integer i1 = future1.get();
            Integer i2 = future2.get();
            System.out.println("线程1返回的值为:" + i1);
            System.out.println("线程2返回的值为:" + i2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

callable与runnable的区别

  1. callable的执行方法体是call(),而runnable是run()

  2. callable的任务执行后可返回值,runnable不能

  3. call()方法可以抛出异常,run()方法不能

FutureTask类实际上是同时实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。

4,使用线程池

在实际工作中,一般都使用线程池来创建线程,这样的好处是:

  • 线程的频繁创建和销毁对系统的资源是一种很大的占用,使用线程池可重复利用已创建的线程。

  • 可以控制线程的并发数,规定线程池的大小,可以防止大量线程因争夺CPU资源而造成堵塞。

  • 线程池提供更加灵活的线程管理,定时、定期、单线程等。

4,线程池的创建

线程池的结构:

  1. Executor:负责线程的使用与调度的根接口

  2. ExecutorService:Executor的子接口,线程池的主要接口

  3. AbstractExecutorService:实现了ExecutorService接口,基本实现了ExecutorService其中声明的所有方法,另有添加其他方法

  4. ThreadPoolExecutor:继承了AbstractExecutorService,主要的常用实现类

  5. ScheduledExecutorService:继承了ExecutorService,负责线程调度的接口

  6. ScheduledThreadPoolExecutor:继承了ThreadPoolExecutor同时实现了ScheduledExecutorService

Java为我们提供了一个类来创建线程池Executors,主要创建的四种类型线程池如下:

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

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

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

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

但是不推荐使用这种方式来创建线程池,因为在创建时无法传入一些核心的配置参数,全都使用默认的,容易造成系统资源浪费,比较好的做法是使用ThreadPoolExecutor来创建,它提供的有参构造如下。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • corePoolSize - 线程池核心池的大小,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true

  • maximumPoolSize - 线程池的最大线程数。

  • keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

  • unit - keepAliveTime 的时间单位。

  • workQueue - 用来储存等待执行任务的队列。

  • threadFactory - 线程工厂。

  • handler-拒绝策略。

猜你喜欢

转载自blog.csdn.net/weixin_41461281/article/details/84954026