您的位置:首页 > 编程语言 > Java开发

Java 中Thread用法

2015-07-20 11:40 681 查看
1、使用线程主要有以下原因:1)解决生成进程导致的性能问题;2)用于同时处理;3)合理利用CPU资源。

2、Java 线程的运行:构造一个Thread类的实例(主要有两种方法),调用其start()方法,如:

Thread t = new Thread();
t.start();


这是一个空壳线程,不做任何事,创建之后就退出。

构造一个Thread类的实例的两种方法:1)派生Thread的子类,覆盖run()方法;2)实现一个Runnable接口,将Runnable对象传递给Thread构造函数。采用方法2)更能区分线程和线程执行的任务。

3、Thread类有多个方法,采用派生Thread并覆盖run()方法时,不能覆盖其他方法,如start(),stop(),interrupt(),join(),sleep()等等。简单示例:

public class MyThread extends Thread {
private String msg;
public MyThread(String s) {
msg = s;
}
public void run() {
System.out.println(msg);
}
}

public static void main(String[] args) {
String s = "Hello world.";
MyThread t = new MyThread(s);
t.start();
}


当然这个示例也没有实际应用,因为没有利用多线程完成I/O和CPU之间的平衡问题。仅作为示例

4、为避免覆盖标准Thread中的其他方法,可以将任务编写为Runnable的一个实例。将上述代码修改一下:

public class MyRunnable implements Runnable {
private String msg;
public MyThread(String s) {
msg = s;
}
public void run() {
System.out.println(msg);
}
}

public static void main(String[] args) {
String s = "Hello world.";
MyRunnable mr = new MyRunnable(s);
MyThread t = new Thread(mr);
t.start();
}


这样就能创建线程了。

那线程之间如何交互?

5、Thread类中run()和start()方法既不接收参数也不返回结果。线程接收参数容易实现,不管是派生子类还是实现Runnable接口,都可以在构造函数中将参数传递进去;那线程运行的结构如何返回?

不好的方法——1):派生子类中实现一个public type[] getResult()的方法,这样在线程运行结束的时候得到结果。问题在于不知线程结束时间,当调用getResult()的时候,也许run()还没有运行完。

不太好的方法——2):轮询,既当run()方法调用结束后,修改子类某字段或者是有个标志。时间效率太差。

比较好的方法——3):回调。要想获得线程运行之后的结果,不一定去请求线程的方法,也可以用线程去通知主程序。回调也有两种方法,一是静态方法,二是回调实例方法。静态方法虽然简单,但是不够灵活。以下假如主程序通过调用其它线程分析字符串中的计算式子,得到结果,并打印。

调用静态方法的伪代码:

public class CallBack implements Runnable {
private String source;
private String result;
public CallBack(String s) {
this.source = s;
}
public void run() {
try{
...
result = ...;
CallBackUserInterface.receiveResult(source, result);
}
catch(Exception e) {
...
}
}
}

public class CallBackUserInterface {
public static void receiveResult(String source, String result) {
Sytem.out.println(source + " = " +  result);
}
public static void main(String[] args) {
for(int i = 0; i < args.length; i++) {
CallBack cb = new CallBack(args[i]);
Thread t = new Thread(cb);
t.start();
}
}
}


而调用实例方法的伪代码:仅列出改变的地方

public class InstanceCallBack implenments Runnable {
private String source;
private String result;
private InstanceCallBackUserInterface callBack;
public CallBack(String s, InstanceCallBackUserInterface callBack) {
this.source = s;
this.callBack = callBack;
}
public void run() {
try{
...
result = ...;
//Here
callBack.receiveResult(result);
}
catch(Exception e) {
...
}
}
}

public class InstanceCallBackUserInterface {
private String source;
private String result;
public InstanceCallBackUserInterface(String source) {
this.source = source;
}
public void calculate() {
InstanceCallBack cb = new InstanceCallBack(souece, this);
Thread t = new Thread(cb);
t.start();
//Here
void receiveResult(String result) {
this.result = result;
}
public String toString() {
String s = source + " = " + result;
return s;
}
public static void main(String[] args) {
for(int i = 0; i < args.length; i++) {
InstanceCallBackUserInterface c = new InstanceCallBackUserInterface(args[i]);
c.calculate();
}
}
}


这里没有将calculate()方法放在构造方法里,主要是担心构造方法未完成calculate里的线程便执行结束。

回调实例函数虽然有点复杂,但是多个类都关心线程运行结果时,回调静态函数就无能为力了,而如果多个类都实现了一个统一的接口用于接收结果,则可以向多个对象发送运行结果。

public interface ResultListener {
public void receiveResult(String result);
}

public class ListCallBack implements Runnable {
List listenerList;
private String source;
private String result;
public synchronized void addListener(ResultListener l) {
listenerList.add(l);
}
public synchronized void removeListener(ResultListener l) {
listenerList.remove(l);
}
public synchronized sendResult() {
ListIterator iterator = listenerList.listIterator();
while(iterator.hasNext()) {
ResultListener rl = (ResultListener)iterator.next();
rl.receiveResult(result);
}
}
public void run() {
...
sendResult();
}
}


调用者类先构造一个ListCallBack,并将自己添加到listenerList中,再运行线程。

6、同步问题。由synchronized关键词修饰,可修饰对象或者方法。

而同步容易导致死锁,所以同步尽量少使用。同步使用的情景一般有全局变量,所以减少全局变量可以使同步减少。

7、线程调度。

线程优先级有1~10,数字大优先级高,与一般情况不同。Thread中有getPriority()和setPriority()两个方法。

线程调度的时机:1)阻塞;2)显示放弃(Thread的yield()方法);3)休眠(Thread 的sleep()方法,精度依赖平台);4)连接线程,Thread的join()方法,某线程a在其代码中调用线程t的join()方法,其后的代码要在线程t运行结束再运行;5)等待一个对象,调用Object的wait()方法,每个对象都有,当此对象的notify()方法调用之后,运行wait()之后的代码,另外wait()方法也可以传入时间参数,即等待一段时间就运行其后代码;6)当线程结束也进行线程调度。而调用sleep,join,wait方法都有被中断(interrupt)的可能,一旦捕获到中断异常,则执行catch(InterruptException ex)里的代码。

8、线程池。

尽管线程与进程相比开销小很多,但是频繁创建与删除依然影响性能。虽然线程停止之后不能重启,但是可以进行改造。首先保存一个全局的任务池并创建固定数量的线程。当池为空时,线程等待;当向池中添加一个任务时,所有等待线程得到通知。当一个线程结束其分配的任务时,再去从池中获取新的任务。

需要注意的是,如何告知程序已经结束。一般需要再设置两个全局变量,一个是总任务数,一个是已经完成的任务数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: