一、线程无返回值
在Java SE5之前,Java的对线程实现有两种方式,一种是继承Thread类,一种是实现Runnable接口。两者其实都相差不多,都是在在自己的类中根据需要实现run方法。这两类方法启动线程都是调用start方法。这两类方法的一个特点就是线程运行不能得到返回值。
例如我要实现一个支持多线程的类MultiThreads,假设使用竭诚Thread类的方式,代码应该如下:
public class MultiThreads extends Thread
{
private int index;
public MultiThreads(int i)
{
this.index = i;
}
public void run()
{
System.out.println("in thread " + index);
try
{
sleep(2000);
System.out.println("thread " + index + " done");
} catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
在另外一个类中,使用这个类来实现多线程计算的代码示例如下:
import java.io.IOException;
public class Main
{
/**
* @param args
* @throws IOException
* @throws InterruptedException
*/
public static void main(String[] args) throws IOException, InterruptedException
{
Thread[] threadVec = new Thread[10];
//crate five threads and start them
for (int i= 0;i < 5;i++)
{
threadVec[i] = new Thread(new MultiThreads(i));
threadVec[i].start();
}
System.out.println("this is main thread!");
}
}
程序运行输出结果为:
in thread 0
in thread 2
in thread 1
in thread 3
in thread 4
this is main thread!
thread 0 done
thread 4 done
thread 1 done
thread 3 done
thread 2 done
从输出结果可以看出,在main函数中,顺序启动了5个线程,5个线程并发的运行,每个线程结束的时间是不确定的。但是我们会发现,当启动完5个线程以后,main函数继续执行自己剩余的部分,不会等待5个线程结束在继续运行。
有时候我们需要在main函数中等待5个子线程都运行结束,然后在继续运行main函数剩余的那部分代码,这时候就需要用到join函数。join函数的作用是一个线程如果在其他线程上调用了join方法,其效果是该线程等待一段时间直到第二个线程运行结束才继续运行第一个线程,也可以再join方法中提供一个时间参数来控制等到的时间。
对于上面的main方法,如果需要等到5个子线程均运行结束在打印“this is main thread”,可修改代码如下:
import java.io.IOException;
public class Main
{
/**
* @param args
* @throws IOException
* @throws InterruptedException
*/
public static void main(String[] args) throws IOException, InterruptedException
{
Thread[] threadVec = new Thread[10];
//crate five threads and start them
for (int i= 0;i < 5;i++)
{
threadVec[i] = new Thread(new MultiThreads(i));
threadVec[i].start();
}
//we call join function
for (int i = 0;i < 5;i++)
{
threadVec[i].join();
}
System.out.println("this is main thread!");
}
}
此时运行结果为:
in thread 0
in thread 1
in thread 2
in thread 3
in thread 4
thread 0 done
thread 1 done
thread 4 done
thread 3 done
thread 2 done
this is main thread!
实现Runnable接口比继承Thread类具有以下优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
二、具有返回值的子线程
有时候我们需要在子线程的运行结束时返回一些有用的信息。这时候上面两个方法就无能为力了,此时只能实现Callable接口,Callable接口早Java SE5中引入。实现该接口需要在类中实现call方法。他是一个具有类型参数的泛型,类型参数就是call方法的返回值类型。在这种实现方法中,不在调用start开启动线程,而是使用ExecutorService.submit()来启动线程。
submit方法会产生一个Future对象,该对象使用Callable返回结果的特定类型进行参数化,然后可使用get方法获取返回结果,可调用isDone()方法来检查Future是否完成,如果在Future未完成以前调用get方法,则get会阻塞,直到结果准备就绪。
如果使用实现Callable接口的方式来多线程,实例代码如下:
import java.util.concurrent.Callable;
public class NewMultiThread implements Callable
{
private int index;
public NewMultiThread(int i )
{
this.index = i;
}
@Override
public String call() throws Exception
{
// TODO Auto-generated method stub
System.out.println("thread " + index + " start");
Thread.sleep(2000);
System.out.println("thread " + index + " done");
return "thread " + index;
}
}
在main函数中调用,代码如下所示:
import java.util.ArrayList;
import java.util.concurrent.*;
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList> results = new ArrayList>();
//create five threads and submit them
for (int i = 0;i < 5;i++)
{
results.add(exec.submit(new NewMultiThread(i)));
}
//we want to check when main thread is waiting for sub-thread
System.out.println("this is main threads");
//get result
for (Future fs : results)
{
System.out.println(fs.get());
}
}
}
程序运行结果为:
thread 1 start
thread 2 start
thread 0 start
thread 4 start
this is main threads
thread 3 start
thread 0 done
thread 1 done
thread 0
thread 2 done
thread 4 done
thread 1
thread 2
thread 3 done
thread 3
thread 4
可以看出,各个子线程不确定的同时运行,main函数所在的线程不会等待子线程的结束才继续运行。
如果我们现在想要让main函数所在线程等待所有子线程运行完成在继续运行该如何办呢,这时候不能再使用join函数来实现了,应为join方法不在Callable接口中定义。
如果是一个线程等待一个线程,则可以通过await()和notify()来实现;
如果是一个线程等待多个线程,则就可以使用CountDownLatch和CyclicBarrier来实现比较好的控制
(1)CountDownLatch用法。
CountDownLatch应用的场景是多个线程单独的运行自己的任务,但是必须等到所有的线程都运行结束才能继续完成其他的任务。比如说在main线程中启动多个子线程,需要等到所有子线程都结束才继续main中的其他部分,此时就可以使用CountDownLatch来实现。
CountDownLatch类有一个常用的构造方法:CountDownLatch(int count);两个常用的方法:await()和countdown();其中count是一个计数器中的初始化数字,比如初始化的数字是2,当一个线程里调用了countdown(),则这个计数器就减一,当线程调用了await(),则这个线程就等待这个计数器变为0,当这个计数器变为0时,这个线程继续自己下面的工作。
使用CountDownLatch实现所需功能,修改后的代码如下所示:
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
public class NewMultiThread implements Callable
{
private int index;
private CountDownLatch endSigle;
public NewMultiThread(int i,CountDownLatch endSigle)
{
this.index = i;
this.endSigle = endSigle;
}
@Override
public String call() throws Exception
{
// TODO Auto-generated method stub
System.out.println("thread " + index + " start");
Thread.sleep(2000);
System.out.println("thread " + index + " done");
//here we decrease the endSigle when this thread is finished
endSigle.countDown();
return "thread " + index;
}
}
调用该类的代码:
import java.util.ArrayList;
import java.util.concurrent.*;
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList> results = new ArrayList>();
final int threadNum = 5;
CountDownLatch endSigle = new CountDownLatch(threadNum);
//create five threads and submit them
for (int i = 0;i < threadNum;i++)
{
results.add(exec.submit(new NewMultiThread(i,endSigle)));
}
//wait until all sub-thread are finished
endSigle.await();
//we want to check whether main thread is waiting for sub-thread
System.out.println("this is main threads");
//get result
for (Future fs : results)
{
System.out.println(fs.get());
}
}
}
运行结果为:
thread 0 start
thread 2 start
thread 1 start
thread 4 start
thread 3 start
thread 4 done
thread 2 done
thread 3 done
thread 1 done
thread 0 done
this is main threads
result:
thread 0
thread 1
thread 2
thread 3
thread 4
可以看到我们已经实现了需要的功能。
当然还可以使用其他方式来实现这些功能。
(2)CyclicBarrier用法
CyclicBarrier应用在多个线程在各种的执行过程中可能在中间位置需要等到其他线程也运行到确定的步骤才能继续运行剩下的部分。比如所三个游戏者通过,任何人在完成第一关后需要等待其他游戏者都完成第一关才能进入第二关。
CyclicBarrier类有两个常用的构造方法:
1. CyclicBarrier(int parties)
这里的parties也是一个计数器,例如,初始化时parties里的计数是3,于是拥有该CyclicBarrier对象的线程当parties的计数为3时就唤醒,注:这里parties里的计数在运行时当调用CyclicBarrier:await()时,计数就加1,一直加到初始的值
2. CyclicBarrier(int parties, Runnable barrierAction)
这里的parties与上一个构造方法的解释是一样的,这里需要解释的是第二个入参(Runnable barrierAction),这个参数是一个实现Runnable接口的类的对象,也就是说当parties加到初始值时就出发barrierAction的内容。
使用CyclieBarrier示例代码如下:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Player implements Runnable
{
private int id;
private CyclicBarrier cycli;
public Player(int id,CyclicBarrier cycli)
{
this.id = id;
this.cycli = cycli;
}
@Override
public void run()
{
// TODO Auto-generated method stub
try
{
System.out.println("player " + id + " is in lever one...");
TimeUnit.MILLISECONDS.sleep(id * 100);
//tell the cycli that the player has finished level 1
cycli.await();
System.out.println("paler " + id + " is goint lever two...");
} catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
import java.util.concurrent.CyclicBarrier;
public class TestPlayer implements Runnable
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO Auto-generated method stub
//create a CyclicBarrier,we set parts to be 4
//when all players finish level1,print a string
CyclicBarrier cycli = new CyclicBarrier(4,new TestPlayer());
for (int i = 0;i < 4;i++)
new Thread(new Player(i, cycli)).start();
}
@Override
public void run()
{
// TODO Auto-generated method stub
System.out.println("palyers go into level two");
}
}
player 0 is in lever one...
player 2 is in lever one...
player 3 is in lever one...
player 1 is in lever one...
palyers go into level two
paler 1 is goint lever two...
paler 3 is goint lever two...
paler 2 is goint lever two...
paler 0 is goint lever two...