目录
一、线程和进程的区别
1. 线程是指程序在执行过程中,能够执行程序代码的一个执行单元。在java语言中,线程有四种状态:运行 、就绪、挂起和结束。线程是进程的组成部分,一个进程有多个线程,一个线程只能有一个父进程。线程可以有自己的堆栈、程序计数器、局部变量等,但不拥有系统资源,多个线程之间共享父进程的全部系统资源。
2. 进程是处于运行过程中的程序,当程序进入内存中运行时,就变成了一个进程,操作系统支持进程的概念,运行的任务通常对应一个进程,进程具有一定独立的功能。
二、线程的生命周期
线程生命周期分为:新建、可执行状态(就绪)、执行状态(运行)、阻塞状态(挂起)、结束。
线程被创建的时候是新建状态(new ),JVM会给线程分配内存空间,并初始化。当线程调用start方法后,进入可执行状态(runnable),jvm 会为其创建方法调用栈、程序计数器,等待被CPU分配时间片,随时可以被调用执行任务。当CPU分配到时间片给该线程的时候,线程处于执行状态(running),执行中如果调用了sleep或者wait或者加锁等方法,或者在线程中需要执行其他任务时,会进入阻塞状态,等待睡眠时间到了自动唤醒或者手动唤醒,或者执行线程中其他任务完成后,当前线程会再次进入可执行状态,等待再次分配时间片,分到了会继续执行。线程执行完了就生命周期结束或者执行中发生异常也会结束生命周期。
三、线程有几种实现方式?
实现线程有三种方式:实现 Runnable 接口、集成 Thread 类、实现 Callable 接口。
1. 实现 Runnable 接口
(1)自定义类实现 Runnable 接口,实现 run 方法。
package com.echo.mythread;
public class Mythread implements Runnable{
@Override
public void run() {
System.out.println("我是run方法,在这里写线程执行任务需要做的操作");
}
}
(2)创建Thread 类对象,runnable 实例作为参数实例化 Thread 对象,启动线程。
package com.echo.mythread;
public class TestThread {
public static void main(String[] args) {
Mythread mythread = new Mythread();
Thread thread = new Thread(mythread);
thread.start();//可执行状态
thread.run();//启动线程
}
}
2. 继承 Thread 类
(1)自定义类继承 Thread 类,实现 run 方法。
package com.echo.mythread;
public class Mythread extends Thread{
@Override
public void run() {
System.out.println("我是run方法,在这里写线程执行任务需要做的操作");
}
}
(2)创建自定义类的对象,启动线程
package com.echo.mythread;
public class TestThread {
public static void main(String[] args) {
Mythread mythread = new Mythread();
mythread.start();//可执行状态
mythread.run();//启动线程
}
}
3. 继承 Callable 接口
(1)自定义类实现 Callable 接口,,实现 call 方法,不同于 run 方法的是 call 方法有返回值,可以跑出异常。
package com.echo.mythread;
import java.util.concurrent.Callable;
public class Mythread implements Callable {
@Override
public Object call() throws Exception {
System.out.println("我是call方法,在这里写线程执行任务需要做的操作");
return "我有返回值";
}
}
(2)两种方法实例化线程
(2.1)使用Callable+Future获取执行结果:创建线程池、创建 Callable 对象、提交任务并获取执行结果。
package com.echo.mythread;
import java.util.concurrent.*;
public class TestThread {
public static void main(String[] args) {
//创建线程池
ExecutorService es = Executors.newSingleThreadExecutor();
//创建Callable对象任务
Mythread calTask = new Mythread();
//提交任务并获取执行结果
Future<String> future =es.submit(calTask);
//关闭线程池
es.shutdown();
try {
Thread.sleep(2000);
System.out.println("主线程在执行其他任务");
if(future.get() != null){
//输出获取到的结果
System.out.println("future.get()-->"+future.get());
}else{
//输出获取到的结果
System.out.println("future.get()未获取到结果");
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("主线程在执行完成");
}
}
(2.2)使用Callable+FutureTask获取执行结果:创建线程池、创建 Callable 对象、创建FutureTask 、执行任务、获取执行结果。
实例化自定义类对象,用对象实例化 FutureTask 对象(带泛型),通过 FutureTask 对象实例化线程,启动线程。
package com.echo.mythread;
import java.util.concurrent.*;
public class TestThread {
public static void main(String[] args) {
//创建线程池
ExecutorService es = Executors.newSingleThreadExecutor();
//创建Callable对象任务
Mythread calTask = new Mythread();
//创建FutureTask
FutureTask<String> future = new FutureTask<>(calTask);
//执行任务
es.submit(future);
//关闭线程池
es.shutdown();
try {
Thread.sleep(2000);
System.out.println("主线程在执行其他任务");
if(future.get() != null){
//输出获取到的结果
System.out.println("future.get()-->"+future.get());
}else{
//输出获取到的结果
System.out.println("future.get()未获取到结果");
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("主线程在执行完成");
}
}
(3) Future 和 FutureTask 的联系与区别
(3.1)Future<V> 是一个接口
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);//中断执行中的任务
boolean isCancelled();//如果任务完成前被取消,则返回true。
boolean isDone();//如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
//获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
V get() throws InterruptedException, ExecutionException;
//获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
如果任务还没开始,执行cancel(...)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(...)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。
Future 提供了三个功能:(1)能够中断执行中的任务(2)判断任务是否执行完成(3)获取任务执行完成后的结果。
Future<V> 是接口,无法直接创建对象,此时就有了 FutureTask<V> 是一个实现类
(3.2)FutureTask<V> 是一个实现类
public class FutureTask<V> implements RunnableFuture<V> {
private volatile int state;
private static final int NEW = 0; //任务新建和执行中
private static final int COMPLETING = 1;//任务将要执行完毕
private static final int NORMAL = 2;//任务正常执行结束
private static final int EXCEPTIONAL = 3; //任务异常
private static final int CANCELLED = 4;//任务取消
private static final int INTERRUPTING = 5;//任务线程即将被中断
private static final int INTERRUPTED = 6;//任务线程已中断
private Callable<V> callable;//被提交的任务
private Object outcome; // 任务执行结果或者任务异常
private volatile Thread runner;//执行任务的线程
private volatile WaitNode waiters;//等待节点,关联等待线程
private static final long stateOffset;//state字段的内存偏移量
private static final long runnerOffset;//runner字段的内存偏移量
private static final long waitersOffset;//waiters字段的内存偏移量
//后三个字段是配合Unsafe类做CAS操作使用的
}
FutureTask类实现了RunnableFuture接口,通过源码可以看到,FutureTask 有两种构造方法,Callable<V> 和 Runnable runnable 。
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
在看一下RunnableFuture接口的实现:
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
由此可以看出FutureTask除了实现了Future接口外还实现了Runnable接口(即可以通过Runnable接口实现线程,也可以通过Future取得线程执行完后的结果),因此FutureTask也可以直接提交给Executor执行。
四、如何实现线程同步?
1. 使用join方法,底层使用了object的wait方法进行等待,在A线程中调用B线程的join方法,A线程会等待B线程执行完在执行。
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
2. wait();
源码翻译:
导致当前线程等待,直到另一个线程为该对象调用* {@link java.lang.Object#notify()}方法或* {@link java.lang.Object#notifyAll()}方法,或*指定的时间已经过去。当前线程必须拥有这个对象的监视器。这个方法导致当前线程(调用它)把自己放在这个对象的等待集中,然后放弃对这个对象的所有同步请求。线程为线程调度的目的而被禁用,并处于休眠状态。
直到以下四种情况之一发生,将结束等待:
(1)碰巧被任意选择为要被唤醒的线程。
(2)其他线程为这个*对象调用{@code notifyAll}方法。
(3)其他线程调用{@linkplain thread #interrupt() interrupts}。
(4)指定的实时时间已经过去,或多或少。但是,如果* {@code timeout}为0,那么实时时间不会被考虑在内,线程只是等待,直到收到通知。
五、为什么要使用多线程?
使用多线程主要有以下几个方面:
(1)提高效率:多线程顾名思义,多个线程同时工作,1000个任务交给100个人去完成和交给1个人去完成的效率是完全不同的,区别于单线程,一个任务执行发生阻塞的时候程序就会进入阻塞阶段,后面的任务将无法执行,多线程可以多个线程同时去工作,即使一个阻塞了,剩下的任务还有其他线程去完成,所以说多线程可以提高效率。
(2)减少程序响应时间:
- 与进程相比,线程创建和切换开销更小,同时多线程在数据共享方面效率非常高。
- 多CPU或者多核计算机本身就具备执行多线程的能力,如果使用单个进程,将无法重复利用计算机资源,造成资源的巨大浪费。在多CPU计算机使用多线程能提高CPU的利用率。
- 使用多线程能简化程序的结构,使程序便于理解和维护
六、多线程有几种实现方式?
七、多线程如何管理?