目录
1,继承Thread类,重写run方法
(1)代码的实现如下
package com.lzm.multithreading;
public class MultithreadingApplication extends Thread{
@Override
public void run(){
System.out.println("我是线程" + Thread.currentThread().getName());
}
public static void main(String[] args) {
MultithreadingApplication abc1 = new MultithreadingApplication();
MultithreadingApplication abc2 = new MultithreadingApplication();
abc1.start();
abc2.start();
}
}
(2)重写run()方法和调用start()方法分析
为什么要重写run方法?从代码中我们可以看到run方法就是我们线程运行的处理部分的入口,我们看一下源码。
首先我们查看run()方法在Thread类中的位置,如下图,注释字段说了,如果这个对象是通过Runable创建的,则运行这个线程调用run方法,并且注明Thread的子类要重写这个run方法。
我们在启动线程的时候应该是调用start(),然后执行run()方法,为什么会这样呢?直接调用run()方法难道不行么?
所以我们可以将主程序中的代码简单改一下,如下:
然后对比一下两次的结果:
左图执行start()方法, 右图执行run()方法,经过简单比对,我们可以看出,直接执行run()方法,实际上还是采用的主线程顺序执行的方式,并没有实现线程并发执行的情况。而调用start()方法则是重新创建了一个新的线程并执行其run()方法。
我们可以简单看一下start()方法的源码:如下图,实际上主要执行的源码时start0()方法,而该方法是native修饰,意味着调用的不是java代码,如果对start0()感兴趣,可以下载OpenJDK去研究一下,就是在运行时,虚拟机重新创建了个线程来运行该程序。
2,实现Runnable接口,并重写run方法
(1)代码的实现如下:
package com.lzm.multithreading;
import java.util.concurrent.Callable;
public class MultithreadingApplication implements Runnable{
@Override
public void run(){
System.out.println("我是线程" + Thread.currentThread().getName());
}
public static void main(String[] args) {
MultithreadingApplication abc1 = new MultithreadingApplication();
MultithreadingApplication abc2 = new MultithreadingApplication();
Thread t1 = new Thread(abc1);
Thread t2 = new Thread(abc2);
t1.start();
t2.start();
}
}
(2)如何实现的?
如上代码所示,可以看出,实际上实现Runnable接口和Thread类的不同就在于,实现Runnable接口需要实例化Thread类对象,然后在启动。
我们可以看一下源码,首先看一下实现的Runnable接口,如下图,注释中说明了,启动线程会回调这个对象的run()代码。也就是说,实现这个接口也是通过run()方法作为处理的入口。
其次,在第一部分说到直接调用run()方法,实际上还是在主线程中依次执行。那么实现了Runnable接口,怎么来调用呢?
还是与Thread类结合,我们可以看类中的一个有参构造函数如下图:注释中说明了,运行时调用target对象的run()方法。而target对象就是Runnable对象,所以我们通过传入一个Runnable对象,来实现线程对象的构造。然后调用start()方法在JVM中创建线程并执行run()方法。
3,实现Callable接口,重写call()方法
(1)代码实现如下:
package com.lzm.multithreading;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MultithreadingApplication implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("我是线程" + Thread.currentThread().getName());
return "SUCCESS";
}
public static void main(String[] args) {
MultithreadingApplication abc1 = new MultithreadingApplication();
FutureTask<String> f1 = new FutureTask<String>(abc1);
FutureTask<String> f2 = new FutureTask<String>(abc1);
Thread t1 = new Thread(f1);
Thread t2 = new Thread(f2);
t1.start();
t2.start();
try {
String rst = f1.get();
System.out.println(t1.getName() + " : " + rst);
String rst2 = f2.get();
System.out.println(t2.getName() + " : " + rst2);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
(2)如何实现的?
首先说一下,实现Callable接口,和上述其他两种的差别,Callable接口会返回相应的值并抛出异常。并且java.util.concurrent这个包是并发包,也就是并发的很多东西包含其中。
其次我们从代码本身出发,首先看一下Callable接口中的内容,如下图所示。我们从注释中可以看到,他说这个接口和Runnable都是为线程执行的类设计的,但是它有返回值和抛出异常。
我们在上述内容中使用的是FutureTask来提交对象并接收返回值。FutureTask是Future接口的实现类,所以先来看一下FutureTask类是如何构造的,如下图中注释的说明,说它可以用于包装Callable和Runnable对象,将其交给线程执行。
接着上述简单看一下,FutureTask实现RunnableFuture接口,而他又继承了Runable和Future接口对象,所以FutureTask可以对Runnable对象进行包装,并且其中也创建了包含Callable的构造函数。
如果对FutureTask类有兴趣,可以进一步探究。
总结
上述三种方法是常用的java的三种创建多线程的方法,但是线程对于共享资源的使用如果不加限制就会出现脏读、幻读、不可重复读的情况,以及线程之间的通信有许多种方式。我在下篇博客中继续介绍线程之间的同步和通信的方法。