在java中想要创建一个线程可运行实例,通常有以下几种方式
- 实现Runnable
- 继承Thread
- 实现Callable
接下来主要针对这几种方式的使用,实现原理和细节,以及区别进行探讨。
Thread
使用
继承Thread类,需要覆盖方法 run()方法,在创建Thread类的子类时需要重写 run(),加入线程所要执行的代即可。直接继承Thread类有一个很大的缺点,因为“java类的继承是单一的,extends后面只能指定一个父类”,所有如果当前类继承Thread类之后就不可以继承其他类
线程的几种状态
- New: 至今尚未启动的线程的状态。
- Runnable :可运行线程的线程状态。
- Blocked :受阻塞并且正在等待监视器锁的某一线程的线程状态。
- Waiting :某一等待线程的线程状态。
- Timed_waiting:具有指定等待时间的某一等待线程的线程状态。
- Terminated:已终止线程的线程状态。线程已经结束执行。
方法
//sleep也是使用了native关键字,调用了底层方法。
//sleep是指线程被调用时,占着CPU不工作,形象地说明为“占着CPU睡觉”,此时,系统的CPU部分资源被占用,其他线程无法进入。
//多线程下使用时需要注意的是sleep方法不会释放锁。
sleep(long millis)
//调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。
//它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,
//另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
//注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
yield()
//
join()
//interrupt的作用是中断正被阻塞的线程
interrupt()
//用来设置线程是否成为守护线程和判断线程是否是守护线程。
//守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。
//在JVM中,像垃圾收集器线程就是守护线程。Thread类有一个比较常用的静态方法currentThread()用来获取当前线程。
setDaemon()操作
Runnable
声明
public interface Runnable {
//无返回值,不会抛出异常
public abstract void run();
}
使用
如果要实现多继承就得要用implements,Java 提供了接口 java.lang.Runnable 来解决上边的问题。
Runnable是可以共享数据的,多个Thread可以加载同一个Runnable实例,当各自Thread获得CPU时间片的时候开始运行Runnable,同一个Runnable实例里面的资源是被共享的,所以使用Runnable更加的灵活
public class RunnableTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
System.out.println("开始执行任务时间: "+getNowTime());
thread.start();
System.out.println("启动任务之后时间: "+getNowTime());
}
public static String getNowTime()
{
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.format(new Date());
}
}
class MyRunnable implements Runnable
{
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Callable
声明
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return 返回计算结果
* @throws 可抛出异常
*/
V call() throws Exception;
}
使用
Runnable是执行工作的独立任务,但是它不返回任何值。如果你希望任务在完成的能返回一个值,那么可以实现Callable接口而不是Runnable接口。在Java SE5中引入的Callable是一种具有类型参数的泛型,它的参数类型表示的是从方法call()(不是run())中返回的值。
public class CallableTest {
public static void main(String[] args) {
//创建实现了Callable接口的对象
MyCallable callable = new MyCallable();
//将实现Callable接口的对象作为参数创建一个FutureTask对象
FutureTask<String> task = new FutureTask<>(callable);
//创建线程处理当前callable任务
Thread thread = new Thread(task);
//开启线程
System.out.println("开始执行任务的时间: "+getNowTime());
thread.start();
//获取到call方法的返回值
try {
String result = task.get();
System.out.println("得到返回值: "+result);
System.out.println("结束执行get的时间: "+getNowTime());
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getNowTime()
{
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.format(new Date());
}
}
class MyCallable implements Callable<String>
{
@Override
public String call() throws Exception {
Thread.sleep(3000);
return "call method result";
}
}
Future
Executor就是Runnable和Callable的调度容器,Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作。get方法会阻塞,直到任务返回结果。
/**
* @see FutureTask
* @see Executor
* @since 1.5
* @author Doug Lea
* @param <V> The result type returned by this Future's <tt>get</tt> method
*/
public interface Future<V> {
//用来取消异步任务的执行。如果异步任务已经完成或者已经被取消,或者由于某些原因不能取消,则会返回false。
//如果任务还没有被执行,则会返回true并且异步任务不会被执行。
boolean cancel(boolean mayInterruptIfRunning);
//判断任务是否被取消,如果任务在结束(正常执行结束或者执行异常结束)前被取消则返回true,否则返回false。
boolean isCancelled();
//判断任务是否已经完成,如果完成则返回true,否则返回false。需要注意的是:任务执行过程中发生异常、任务被取消也属于任务已完成,也会返回true。
isDone():
//获取任务执行结果,如果任务还没完成则会阻塞等待直到任务执行完成。如果任务被取消则会抛出CancellationException异常,
//如果任务执行过程发生异常则会抛出ExecutionException异常,如果阻塞等待过程中被中断则会抛出InterruptedException异常。
V get():
//带超时时间的get()版本,如果阻塞等待过程中超时则会抛出TimeoutException异常。
V get(long timeout,Timeunit unit):
}
FutureTask
FutureTask则是一个RunnableFuture,而RunnableFuture实现了Runnbale又实现了Futrue这两个接口,
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
}
Runnable注入会被Executors.callable()函数转换为Callable类型,即FutureTask最终都是执行Callable类型的任务。该适配函数的实现如下 :
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
下面是RunnableAdapter
/**
* A callable that runs given task and returns given result
*/
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
实现Runnable接口相比继承Thread类有如下优势:
- 可以避免由于Java的单继承特性而带来的局限;
- 增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;
- 适合多个相同程序代码的线程区处理同一资源的情况。
实现Runnable接口和实现Callable接口的区别:
- Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的
- Callable规定的方法是call(),Runnable规定的方法是run()
- Callcble是可以有返回值的,具体的返回值就是在Callable的接口方法call返回的,并且这个返回值具体是通过实现Future接口的对象的get方法获取的,这个方法是会造成线程阻塞的;而Runnable是没有返回值的,因为Runnable接口中的run方法是没有返回值的;
当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。
同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。 - Callable里面的call方法是可以抛出异常的,我们可以捕获异常进行处理;但是Runnable里面的run方法是不可以抛出异常的,异常要在run方法内部必须得到处理,不能向外界抛出;
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
- 加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法。