引子
首先要理解并发(Concurrency)和并行(Parallelism)的区别:
- 并发是在同一时段发生。多线程就是分时利用CPU,宏观上让所有线程一起执行,也叫并发。但在微观上不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。这就好像两个人用同一把铁锨,轮流挖坑,一小时后,两个人各挖一个小一点的坑,要想挖两个大一点得坑,一定会用两个小时。
- 并行是在同一时刻发生。无论从微观还是宏观,二者都是一起执行的,就好像两个人各拿一把铁锨在挖坑,一小时后,每人一个大坑。
从以上本质不难看出,“并发”执行,在多个进程存在资源冲突时,并没有从根本提高执行效率。
异步和多线程也不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。实现异步可以采用多线程技术。
下面看看新建线程的3种方法 :
一、继承Thread,实现run()方法
只要两步即可创建并开启一个线程:
继承Thread类,并实现run()方法;
调用start()方法开启线程。
package twm.JConcurrence.demo;
public class NewThreadDemo {
public static void main(String[] args) {
/*在主线程中开启10个线程,每个线程需要耗时2秒。但因为线程并发,所以总体运行时间也在2秒左右,并非20秒。*/
for (int i = 0; i < 10; i++) {
demothread abc= new demothread("chapter1_thread"+String.valueOf(i));
abc.start();
}
}
static class demothread extends Thread {
String put;
public demothread(String name) {
super(name);
this.put = name;
}
/*覆写run()方法*/
public void run() {
try {
/*模拟耗时操作,让线程休眠2秒*/
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out
.println("Class name is : "+this.put+",thread name is :" + Thread.currentThread().getName());
}
}
}
输出:
Class name is : chapter1_thread0,thread name is chapter1_thread0
Classname is : chapter1_thread2,thread name is chapter1_thread2
Class name is : chapter1_thread1,thread name is chapter1_thread1
Class name is : chapter1_thread4,thread name is chapter1_thread4
Class name is : chapter1_thread6,thread name is chapter1_thread6
Class name is : chapter1_thread8,thread name is chapter1_thread8
Class name is : chapter1_thread9,thread name is chapter1_thread9
Class name is : chapter1_thread3,thread name is chapter1_thread3
Class name is : chapter1_thread7,thread name is chapter1_thread7
Class name is : chapter1_thread5,thread name is chapter1_thread5
由于只要实现一个run()方法即可,所以可以改写一下上面程序,让代码更简洁。
使用Java中的匿名内部类(原型:new Thread(){ void run{ 实现 } }.start()
)来实现,代码如下:
package JConcurrence.Study;
public class chapter1 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
final int m=i;
new Thread(){
/*覆写run()方法*/
public void run() {
try {
/*模拟耗时操作,让线程休眠2秒*/
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out
.println("thread name is" + Thread.currentThread().getName()+"输出:"+m);
}
}.start();
}
}
}
在for循环体内加了一句final int m=i;这是因为使用了匿名内部类,在类的内部隐式调用外部变量i,外部变量需要final修饰,不可修改值。否则会报错:Cannot refer to a non-final variable i inside an inner class defined in a different method
二 、实现Runnable接口
同样只要两步即可创建并开启一个线程:
创建一个实现了Runnable接口的类,并重写run()方法;
将该类的实例化对像作为参数传入Thread类的构造方法中,并调用Thread类的start()方法启动。
package JConcurrence.Study;
public class chapter1 {
public static void main(String[] args) {
/*在主线程中开启10个线程,每个线程需要耗时2秒,但因为线程并发,所以总体运行时间也在2秒左右,并非20秒。*/
for (int i = 0; i < 10; i++) {
Runnable demoRunnable=new DemoRunable("chapter1runable"+String.valueOf(i));
new Thread(demoRunnable).start();
}
}
static class DemoRunable implements Runnable {
String put;
public DemoRunable(String name) {
super();
this.put = name;
}
/*覆写run()方法*/
public void run() {
try {
/*模拟耗时操作,让线程休眠2秒*/
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out
.println("Class name is : "+this.put+",thread name is" + Thread.currentThread().getName());
}
}
}
由于只要实现Runnable的一个run()方法,所以可以改写一下上面程序,让代码更简洁。
使用Java中的匿名内部类(原型:new Thread( new Runnable(){ void run{ 实现 } } ).start()
)来实现,代码如下:
package JConcurrence.Study;
public class chapter1 {
public static void main(String[] args) {
/* 在主线程中开启10个线程,每个线程需要耗时2秒,但因为线程并发,所以总体运行时间也在2秒左右,并非20秒。 */
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
/* 模拟耗时操作,让线程休眠2秒 */
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread name is"
+ Thread.currentThread().getName());
}
}).start();
}
}
}
三、实现Callable接口
使用方法和Runable类似,但应用场景不同,会在后面的Callable和Future接口中详细说到Callable。
Runnable和Callable接口的区别:
- Callable重写的方法是call(),Runnable重写的方法是run();
- Callable的任务执行后可返回值,而Runnable不能返回值;
- call方法可以抛出异常,run()不可以;
- 运行Callable任务可以拿到一个future对象,表示异步计算的结果,
它供检查计算是否完成的方法,以等待计算完成,并检索计算的结果。通过Future对象可以了解任务的执行情况,可取消任务的执行,还可以获取执行的结果。
四、总结Run()方法
无论继承Thread类还是实现Runnable接口,代码基本高度一致,核心就是在run()方法的实现中。
通过Thread源码中run()方法,可以看到它自己并不做什么(只是简单的判断一下是否可以调用),而是调用了target的run()方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
这个target到底是哪位大侠呢?跟踪发现是这样定义的:
private Runnable target;
target就是Runnable。
Runable的定义是:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
我了个去,满山遍野都是run,是不是感觉有零乱。
其实梳理一下就很好理解了。
1、如果我们要让Thread听我们指挥,做我们安排的工作,那么就直接重写Thread类run函数。甭管他里面什么target.run乱七八糟的,你自己想怎么写就怎么写,空着也行。这就是“1、继承Thread,实现run()方法”说讲的内容。
2、如果我们实现Runable接口,并重写他的run方法。而后将这个对像作为参数传入Thread,那么在Thread类内部,会把该参数赋值到target。线程启动后调用Thread.run时运行的代码是target.run(),最终调用了runable实现类中的run方法。所以可以你在runable中的run方法中自己想怎么写就怎么写,空着也行。这就是“实现Runnable接口”讲的内容
3、如果有个哥们他硬要把两样都加上,即:同时把将runable传入Thread,然后又实现Thread中的run方法,那么程序最终会运行runable中的run(),还是Thread中的run()呢?….舌头差点打结了。很明显因为重写Thread中的run方法后,就不会再执行target.run了。因此无论你传什么runable进来,Thread都不会理睬了。看代码:
package JConcurrence.Study;
public class chapter1 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
/* 模拟耗时操作,让线程休眠2秒 */
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Executive agent:Runable 's run()");
}
}) {
public void run() {
try {
/* 模拟耗时操作,让线程休眠2秒 */
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out
.println("Executive agent:Thread 's Override run()");
}
}.start();
}
}
执行结果:
Executive agent:Thread ‘s Override run()