1、多线程技术实现方法
Java中至少有一个线程,如果不特殊处理,它至少是一个单线程程序。此外,Java也支持多线程。多线程技术主要有三种实现方法,第一种是继承Thread,第二种是实现Runnable接口,第三种是实现Callable接口。接下来就讲讲这三种方法的使用方法。
2、继承Thread
创建一个子类继承Thread,并且重写Thread里的run方法,把要用多线程处理的任务放在run方法里。比如现在有一对夫妻,他们要协作做一顿饭,我们用两个线程开展,一个线程表示丈夫,一个线程表示妻子,程序如下:
package com.wxf.thread;
public class ThreadDemo1 {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
myThread1.setName("丈夫");
MyThread myThread2 = new MyThread();
myThread2.setName("妻子");
myThread1.start();
myThread2.start();
}
}
class MyThread extends Thread{
private int taskNum = 4;
@Override
public void run() {
while(taskNum>0){
switch (taskNum){
case 1: System.out.println(Thread.currentThread().getName()+":炒菜");
break;
case 2: System.out.println(Thread.currentThread().getName()+":切菜");
break;
case 3: System.out.println(Thread.currentThread().getName()+":洗菜");
break;
case 4: System.out.println(Thread.currentThread().getName()+":蒸饭");
break;
}
taskNum--;
}
}
}
程序运行结果是:
通过结果我们可以看出这对夫妻其实是做了两顿饭,这也是继承Thread和实现Runnable两种方法的区别。
3、实现Runnable接口
实现Runnable的方法和继承Thread相似,都是创建类,实现Runnable接口,重写run方法。所不同的是在创建线程时,通过实现Runnable接口方式的多线程技术是先创建任务,再创建线程,然后给线程分配任务,相当于把一个大任务划分为几个子任务,所以这里创建的线程都会执行同一个任务。人话就是先创建了做饭这个任务,把任务分成洗菜、切菜、炒菜、蒸饭这四个子任务,再把这四个子任务分给丈夫和妻子这两条线,所以丈夫和妻子做了一顿饭。而继承Thread方法则创建了多个线程,每个线程领到的任务都是一整个任务,所以这个任务会被执行多次。
package com.wxf.thread;
public class RunnableDemo1 {
public static void main(String[] args) {
MyRunnable task = new MyRunnable();
Thread myThread1 = new Thread(task);
myThread1.setName("丈夫");
Thread myThread2 = new Thread(task);
myThread2.setName("妻子");
myThread1.start();
myThread2.start();
}
}
class MyRunnable implements Runnable{
private int taskNum = 4;
@Override
public void run() {
while(taskNum>0){
switch (taskNum){
case 1: System.out.println(Thread.currentThread().getName()+":炒菜");
break;
case 2: System.out.println(Thread.currentThread().getName()+":切菜");
break;
case 3: System.out.println(Thread.currentThread().getName()+":洗菜");
break;
case 4: System.out.println(Thread.currentThread().getName()+":蒸饭");
break;
}
taskNum--;
}
}
}
结果如下图:
通过上述结果,可以看到丈夫和妻子都在进行做饭这一项任务,且是在协作做饭。但是为什么出现了丈夫和妻子都做了蒸饭这个任务的情况?因为丈夫这条线程走到System.out.println()时,CPU切换到下一个时间段了,taskNum还没来得及减1,此时妻子这条线程抢到了CPU,因此妻子也执行了蒸饭这个任务。而且结果显示菜还没洗就开始切了,出现了任务错乱的问题,也就是线程是不安全的。要解决线程不安全问题,可以给任务加锁,当任务被锁上时,任何线程都不能执行任务,当任务解锁后,线程才可以抢任务执行,加锁的方式在下一篇文章讲。
4、继承Thread和实现Runnable接口的区别
通过上面两种方法的对比,我们可以看到:
(1)实现Runnable接口的方法是通过创建任务和线程,给线程分配任务来实现多线程技术,这种方式更适合多个线程执行相同任务的情况。
(2)继承Thread的有只能继承一个父类的局限,而实现接口可以实现多个接口,也可以继承多个父类,具有更好的可扩展性。
(3)任务和线程本身是分离的,提高了线程的健壮性
(4)线程池技术接受Runnable类型,不接受Thread类型。
5、实现Callable接口
除了上述两种多线程技术外,还有一种实现多线程的技术,就是实现Callable接口。实现Callable接口比实现Runnable接口稍微麻烦一点,它和Runnable的区别在于:实现Callable接口需要重写call方法,此方法可以提供返回值,而Runnable接口里的run方法是不提供返回值的。除了创建类实现Callable接口外,还需要用到FutureTask类包装Callable对象,通过FutureTask的get方法可以获得返回值。
我们之所以需要返回值,是因为有时候一个线程的执行需要根据另一个线程的返回值来决定是否执行。比如在做饭这个例子中,我们在主线程里加上吃饭这个任务,吃饭要在饭做好以后才能执行,如果使用Runnable接口或者继承Thread的方式,吃饭和做饭是并发的,很有可能在饭还没做好就开始吃饭了。实际上,在实现Callable接口的时候,如果不用futuretask.get()方法阻塞线程,也会出现吃饭和做饭并发这种情况,比如以下代码:
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask futureTask1 = new FutureTask(myCallable);
FutureTask futureTask2 = new FutureTask(myCallable);
Thread myThread1 = new Thread(futureTask1);
Thread myThread2 = new Thread(futureTask2);
myThread1.setName("丈夫");
myThread2.setName("妻子");
myThread1.start();
myThread2.start();
System.out.println("开始吃饭");
}
}
class MyCallable implements Callable<Integer>{
private int taskNum = 4;
@Override
public Integer call() throws Exception {
while(taskNum>0){
switch (taskNum){
case 1: System.out.println(Thread.currentThread().getName()+":洗菜");
break;
case 2: System.out.println(Thread.currentThread().getName()+":切菜");
break;
case 3: System.out.println(Thread.currentThread().getName()+":炒菜");
break;
case 4: System.out.println(Thread.currentThread().getName()+":蒸饭");
break;
}
taskNum--;
}
return taskNum;
}
}
代码结果如图:
从结果可以看到饭还没做好就开始吃了。所以我们在开始吃饭前加上FutureTask的get方法阻塞线程,让丈夫线程和妻子线程都执行完了再开始吃饭:
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask futureTask1 = new FutureTask(myCallable);
FutureTask futureTask2 = new FutureTask(myCallable);
Thread myThread1 = new Thread(futureTask1);
Thread myThread2 = new Thread(futureTask2);
myThread1.setName("丈夫");
myThread2.setName("妻子");
myThread1.start();
myThread2.start();
futureTask1.get();
futureTask2.get();
System.out.println("开始吃饭");
}
}
class MyCallable implements Callable<Integer>{
private int taskNum = 4;
@Override
public Integer call() throws Exception {
while(taskNum>0){
switch (taskNum){
case 1: System.out.println(Thread.currentThread().getName()+":洗菜");
break;
case 2: System.out.println(Thread.currentThread().getName()+":切菜");
break;
case 3: System.out.println(Thread.currentThread().getName()+":炒菜");
break;
case 4: System.out.println(Thread.currentThread().getName()+":蒸饭");
break;
}
taskNum--;
}
return taskNum;
}
}
结果截图如下:
从结果可以看到需要等到做完饭才开始吃饭。