作为一个开始学习进阶知识的小学生,并发编程是一个很重要的点,无论是以后面试,还是工作中都会用到,如果学会的话,可以让我们水平上一个台阶。
首先,我们需要知道,线程实现的方法。
public class RunnableThread implements Runnable{
@Override
public void run() {
System.out.println("runnable接口实现");
}
}
public class RunnableThread extends Thread{
@Override
public void run() {
System.out.println("thread继承实现");
}
}
一般来说,我们有两种,一种是实现runnable接口,然后实现run()方法;另一种是继承thread接口重写run()方法。这两种方法应该说是最基础的回答。一般来说工作中经常用的也是这两种。
记住 线程的实现方式就两种!!!千万不要说三种以上,因为其他的实现方式也都是基于这两种。可以说自己知道其他的用法,不可以说是实现方式!
接下来讲一下,第三种,可以说做是创建线程的方法,线程池:
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
可以看到,线程池虽然很多个参数往里传,但是本质上还是通过一个new Thread来实现的 。
第四种,有返回值的callable创建线程,要和Future、FutureTask一起使用。
class CallableTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return new Random().nextInt();
}
}
public static void main(String[] args) {
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//提交任务,并用 Future提交返回结果
Future<Integer> future = service.submit(new CallableTask());
}
这种方式实际上是需要被执行的,而不是本身就是线程,只是说它们可以放到线程池中去运行,如代码所示,service.submit将方法放到线程池中,然后执行,实际上这种方式也是通过上述两种实现方式实现的(thread或runnable)。
还有一些比如定时器,匿名内部类,lamada表达式等,实际上都是一样的。
这时候,就会有小伙伴问,是不是说我之后只要说,所有线程的本质都是这两种方式咯,那我以后面试只要记住就可以了。
这我只能说,小伙子你还太天真了,真正最后的实现方式只有一种!构造Thread类。
首先我们看第一种实现Runnable接口的方式,启动线程要通过Thread.start()方法,然后start()还是会去调用run()方法,在run()方法里有一个target,这个target就是一个Runnable。
/* What will be run. */
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
这时候我们看一下第二种,继承Thread类,重写run()后,仍然是使用的Thread.start()方法,然后再调用run(),所以这个时候就明白,事实上创建线程只有一种方式,就是构造Thread类。
所以这两种方式到底有啥区别呢,都是通过构造Thread类进行创建的线程。
一个是通过实现run()的方式,实际上是一个Runnable的target去执行我们想要的方法,一个是通过重写run()的方式,执行的是run()里的我们想要的方法,从内存的角度来看,Runnable的线程池是创建好的,当我使用的时候,直接把任务放入线程池里,而继承Thread是每次都要自己创建一个线程空间来运行最后销毁,所以从这个角度来讲,Runnable比Thread效率要高一点。
最后说一下为什么事先Runnable要比继承Thread方法好。
1,java单继承,如果使用继承了,以后要使用这个类去继承别的不行,可扩展性不高。
2,上述提到的,Runnable的效率高一些。
3,Runnable只有一个run()方法,它定义了需要执行的内容,这种情况下,实现了Runnable和Thread的解耦,在Thread中是实现了Runnable接口的run()方法的。这样Thread类负责启动和属性设置,Runnable负责运行。
点击查看下一篇:如何正确停止线程,在使用volatile标记位的停止方法需要注意什么?