总结:
线程创建常用的方式有三种,分别是:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
继承Thread类的方式
public class threadClassDemo extends Thread{
int count = 1;
@Override
public void run() {
count++;
System.out.println("当前线程:"+Thread.currentThread().getName()+"\t count:"+count);
}
public static void main(String[] args) {
for (int i=0;i<10;i++){
threadClassDemo demo = new threadClassDemo();
demo.start();
}
}
}
运行结果:
通过继承Thread类重写run方法,可以创建线程,上述demo中,main方法中创建了10个线程,count是线程类的实例变量,每个线程都对这个实例变量进行了操作,但是注意一点,这里的变量是不能实现线程之间的共享的,每个线程都有一个独立的count。
实现Runnable接口的方式
public class runnableDemo implements Runnable {
int count = 0;
@Override
public void run() {
System.out.println("当前线程:"+Thread.currentThread().getName());
count++;
System.out.println("count:"+count);
}
public static void main(String[] args) {
for (int i= 0;i<10;i++){
if(i==5){
runnableDemo demo = new runnableDemo();
new Thread(demo,"thread-one").start();
new Thread(demo,"thread-two").start();
}
}
}
}
运行结果:
上述demo中,通过实现Runnable接口的形式实现run方法,在run方法中编写线程执行体,创建2个线程,可以看到当创建两个线程的目标线程体共用一个时,count是会被两个线程共享的,线程体被共享;
也就是说通过实现Runnable接口的线程体再被多个线程调用时,资源是共享的,来验证一下:
public class runnableDemo implements Runnable {
int count = 0;
@Override
public void run() {
count++;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程:"+Thread.currentThread().getName()+"\t count:"+count);
}
public static void main(String[] args) {
runnableDemo demo = new runnableDemo();
for (int i= 0;i<10;i++){
new Thread(demo,String.valueOf(i)).start();
}
}
}
上述代码中main方法中创建了十个线程,这10个线程共用一个demo线程体,也就是创建的线程都是执行的同一个线程体,这时运行结果呢?
如图,此时因为在线程体中对count的操作之后设置了一个等待时间,所以如果执行的结果大于2说明了count在10个线程中是共享的,结果也证实了count确实是共享的。
实现Callable接口方式
同样是为了创建线程,实现接口的方式创建还分为实现Runnable和Callable,这两者有什么不同呢?
class MyThread implements Runnable{
@Override
public void run() {
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 1024;
}
}
上面创建了两个线程体,有什么不同呢?通过比较,我们发现大致有三点不同:
1、前者无返回值,后者有返回值
2、前者不会抛异常,后者会抛异常
3、方法名不一样
通过有返回值的线程,在执行多线程任务时,当任务执行失败,可以通过返回信息,完成任务的回滚等操作,当然线程执行有返回值还有很多的业务场景,这里不做详细解读。
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"\t ****MyThread-->call()****");
return 1024;
}
}
public static void main(String[] args) {
try {
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread());
Thread thread = new Thread(futureTask,"aaa");
thread.start();
//这种情况只会复用,只有一个futureTask的时候多个线程来抢只会执行一次
new Thread(futureTask,"bbb").start();
//创建两个futureTask
FutureTask<Integer> futureTask2 = new FutureTask<Integer>(new MyThread());
new Thread(futureTask2,"ccc").start();
// 使用get获得Callable线程的计算结果,如果没有计算完成就要去强求,会导致阻塞,要放在最后取值
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
这里要注意这种方式创建线程的启动方式,由于通过实现Callable接口创建的线程体是Callable类型,而Thread的构造方法的参数是:Thread(Runnable target, String name),是Runnable类型的,这里怎么创建线程并执行呢?通过了解JDK源码,获取线程执行结果是借助了Future,Future有三个作用:
- 判断任务是否完成;
- 能够中断任务;
- 能够获取任务执行结果
而RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值,能够作为Runnable被线程执行意思就是可以通过new Thread()的方式创建线程,可以看到FutureTask的构造函数有一个为
这个方法便把Callable和Runnable联系起来,我们便可以通过下面
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread());
Thread thread = new Thread(futureTask,"aaa");
的方式创建并执行线程了。