先看看Thread是怎么创建一个线程并运行的,代码如下。
public class MyThread {
public static int number ;
public static int count = 100;
public static void main(String[] args){
Thread1 thread1 = new Thread1();
thread1.setName("线程1");
thread1.start();
}
}
class Thread1 extends Thread{
@Override
public void run(){
while (true){
if(MyThread.count < 0 ){
break;
}else {
MyThread.count--;
}
MyThread.number = 100-MyThread.count;
System.out.println(Thread.currentThread().getName()+"得到了"+"第"+MyThread.number+"张车票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
以上可知通过继承Thread创建一个线程实际上是调用的Thread类中的start()方法。而start()方法实际上又是调用的由native修饰的本地方法start0()去创建一个线程(java方法与本地方法以及java如何调用本地方法略)。此时该线程便处于运行状态,等待cpu的调度执行重写的run方法中的代码。如果主线程想要等到创建的线程执行完成之后在运行,可以使用Thread.join()方法。
使用Runnable创建线程使用Runnable创建代码如下
public class MyRunnable {
public static int number ;
public static int count = 100;
public static void main(String[] args){
Runnable1 runnable1 = new Runnable1();
Thread thread1 = new Thread(runnable1);
thread1.start();
}
}
class Runnable1 implements Runnable{
@Override
public void run() {
while(true){
if(MyThread.count < 0){
break;
}else {
MyThread.count--;
}
MyThread.number = 100-MyThread.count;
System.out.println("线程"+Thread.currentThread().getName()+"得到了"+"第"+MyThread.number+"张车票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
通过以上代码可知通过实现Runnable接口创建线程,实际上还是将Runnable接口实现类的实例对象传入Thread类对象中,由Thread类中的start方法创建线程,再等待cpu调度执行该对象的run方法。其实Thread实现的run方法也是Runnable接口中定义的,因为Thread也实现了Runnable接口。那么就是说Runnable接口中run方法是线程执行内容的载体,而Thread类中start方法则是用来创建线程的。那么既然原理都是一样的为什么还要提供两种实现方式呢?主要还是因为Thread是一个类,如果某个类想要创建线程继承了Thread类了之后就不能再继承其他父类了,所以Thread
类初始化的时候提供了传入Runnable接口实现类的实例对象的方法。
前面提到的通过Thread和Runnable创建线程都是异步执行的,并不能拿到线程的执行结果。而使用Callable创建线程则可以拿到线程的执行结果。通过Callable创建线程代码如下。
public class MyCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable1 callable1 = new Callable1();
FutureTask futureTask = new FutureTask<>(callable1);
Thread aa = new Thread(futureTask);
aa.start();
System.out.println(futureTask.get().toString());
}
}
class Callable1 implements Callable{
@Override
public Object call() throws Exception {
Date date = new Date();
return date;
}
}
其中FutureTask类实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable接口以及Future接口。所以FutureTask实例也可以用来初始化Thread类对象,并通过Thread类中start方法启动线程。线程启动之后会调用FutureTask中实现Runnable接口中的run()方法,run()方法再去调用Callable类对象中的call()方法并得到返回结果result,再将result封装进成员变量中,之后便可以通过get()方法得到返回值。FutureTask实现了Future接口中的方法如下。
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。
其实FutureTask类中除了直接传Callable类对象来初始化之外,还可以通过传Runnable接口实现类类对象 + 返回类型 来初始化。在之后的内容中会讲到。
前面已经提到线程执行的两个主要接口:Runnable和Callable,以及创建线程的类Thread和得到线程执行结果的类FutureTask。其中Thread类以及Runnable接口是JDK中一开始就提供了的。而FutureTask,Future,Callable则是在JDK1.5开始提供的Executor框架的包concurrent下的。Executor框架是一个用于统一创建线程与运行线程的接口。Executor框架实现的就是线程池的功能。先看下如何用线程池创建线程。
public class MyThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService1= new ThreadPoolExecutor(1,1,60L,TimeUnit.SECONDS, new ArrayBlockingQueue(10));
executorService1.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
executorService1.shutdown();
ExecutorService executorService2 = new ThreadPoolExecutor(1,1,60L,TimeUnit.SECONDS, new ArrayBlockingQueue(10));
Future future = executorService2.submit(new Callable
关于使用ThreadPoolExecutor创建线程池源码没太看懂,之后再补上。大概处理逻辑是如果核心线程没满则创建一个核心线程。如果已经满了则存入队列中等待核心线程空闲再去运行。如果队列也满了则创建一个非核心线程运行,如果非核心线程也满了则执行饱和策略(共四种,也可以选择自己实现饱和策略)。而submit与execute的区别则在于submit可以返回一个future对象用于获取线程执行的结果。而对于Executors工具类提供的创建常用线程池的方法一般不推荐使用,可以了解。
线程的状态转换以及销毁策略略,等到锁写完了再进行补充。



