Java 中Thread用法

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、线程池。
尽管线程与进程相比开销小很多,但是频繁创建与删除依然影响性能。虽然线程停止之后不能重启,但是可以进行改造。首先保存一个全局的任务池并创建固定数量的线程。当池为空时,线程等待;当向池中添加一个任务时,所有等待线程得到通知。当一个线程结束其分配的任务时,再去从池中获取新的任务。
需要注意的是,如何告知程序已经结束。一般需要再设置两个全局变量,一个是总任务数,一个是已经完成的任务数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值