Java 多线程

来源:互联网 发布:mac 取消隐藏文件夹 编辑:程序博客网 时间:2024/04/29 22:20

  本文主要讲了Java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。
  首先讲一下进程和线程的区别:
  进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1-n个线程。
  线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器,线程切换开销小。
  线程和进程一样分为5个阶段:创建、就绪、运行、阻塞、终止。
  多进程是指操作系统能同时运行多个任务(程序)。
  多线程是指在同一程序中有多个顺序流在执行。
  在Java中要想实现多线程,有两种手段,一种是继承Thread类,另一种是实现Runnable接口。
  一、扩展java.lang.Thread类

public class Test3 extends Thread{    private String name;    public Test3(String name){        this.name=name;    }    public void run(){        for (int i = 0; i < 5; i++) {            System.out.println(name+"运行:"+i);            try {                sleep((int) (Math.random()+10));            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }    }    public static void main(String[] args) {        Test3 s1=new Test3("A");        Test3 s2=new Test3("B");        s1.start();        s2.start();    }}

输出:
A运行:0
B运行:0
B运行:1
A运行:1
A运行:2
B运行:2
A运行:3
B运行:3
A运行:4
B运行:4

再次运行:
B运行:0
A运行:0
A运行:1
B运行:1
B运行:2
A运行:2
B运行:3
A运行:3
B运行:4
A运行:4
  说明:程序启动运行main时候,Java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用两个对象的start方法,两个线程也启动了,这样,整个应用就在多线程下运行。
  注意:start()方法的调用并不是立即执行多线程代码,而是使得该线程变为可运行状态(Runnable),什么时候运行由操作系统决定的。
  从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计多线程。
  Thread.sleep()方法调用目的就是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
  实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。
  但是start方法重复调用的话,会出现java.IllegalThreadStateException异常。
  二、实现java.lang.Runnable接口
  

public class Test4 implements Runnable{    private String name;    public Test4(String name){        this.name=name;    }    @Override    public void run() {        for (int i = 0; i < 5; i++) {            System.out.println(name+"运行:"+i);            try {                Thread.sleep((long) (Math.random()+10));            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }    }    public static void main(String[] args) {        new Thread(new Test4("C")).start();        new Thread(new Test4("D")).start();    }}

输出:
C运行:0
D运行:0
D运行:1
C运行:1
D运行:2
C运行:2
D运行:3
C运行:3
C运行:4
D运行:4
说明:
  Test4类通过实现Runnable接口,使得该类有了多线程的特性。run()方法是多线程程序的一个约定。所有的多线程代码都在run()方法里实现。Thread类实际上也是实现了Runnable接口的类。
  在启动多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target)构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
  实际上所有的多线程代码都是通过运行Thread的run()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程,熟悉Thread类的API是进行多线程编程的基础。
  三、Thread和Runnable的区别
  如果一个类继承Thread,则不适合资源共享。但是如果实现了Runnable接口的话,则很容易实现资源共享。
  

public class Test4 implements Runnable{    private int count=15;    @Override    public void run() {        for (int i = 0; i < 5; i++) {            System.out.println(Thread.currentThread().getName()+"运行 count="+count--);            try {                Thread.sleep((long) (Math.random()+10));            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }    }    public static void main(String[] args) {        Test4 my=new Test4();        new Thread(my, "C").start();        new Thread(my,"D").start();        new Thread(my,"E").start();    }}

输出:
 E运行 count=14
 C运行 count=15
 D运行 count=13
 D运行 count=12
 E运行 count=10
 C运行 count=11
 C运行 count=9
 E运行 count=8
 D运行 count=7
 D运行 count=6
 E运行 count=4
 C运行 count=5
 C运行 count=3
 E运行 count=2
 D运行 count=1
  这里要注意每个线程都是用同一个实例化对象,如果不是同一个,效果就喝上面的一样了!
 总结:
 实现Runnable接口比继承Thread类所具有的优势:
 1)适合多个相同的程序代码的线程去处理同一个资源
 2)可以避免Java中单继承的限制
 3)增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

提醒下大家:main方法其实也是一个线程,在Java中所有的线程都是同事启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。

四、线程状态转换

这里写图片描述
  1.新建状态(New):新创建了一个线程对象。
  2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
  3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
  4.阻塞状态:阻塞状态是线程因为某种原因放弃了CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分3种:
  1)等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
  2)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
  3)其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或超时、或者I/O处理完毕时,线程重新转入就绪状态。
  5.死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束了生命周期。

五、线程调度
  线程的调度
  1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
   Java线程的优先级用整数表示,取值范围是1-10,Thread类有以下三个静态常量:
   static int MAX_PRIORITY 线程可以具有最高优先级,取值为10
   static int MIN_PRIORITY 线程可以具有最低优先级,取值为1
   static int NORM_PRIORITY 分配给线程的默认优先级,取值为5

  Thread类的setPriority()和getProirity()方法分别用来设置和获取线程的优先级。
  每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PROIRITY。线程的优先级有继承关系,比如A线程创建了B线程,那么B将和A具有相同的优先级。
  JVM提供了10个线程优先级,但与常用的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态变量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
  2.线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。milles参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
  3.线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()唤醒方法。这两个唤醒方法也是Object类中的方法,行为等价于调用wait(0)一样。
  4.线程让步:Thread.yield()方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
  5.线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个线程运行结束,当前线程再由阻塞转为就绪状态。
  6.线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个wait方法,在对象的监视器上等待。直到当前的线程放弃此对象的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在对象上主动同步的其他所有线程竞争。例如,被唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll,唤醒在此对象监视器上等待的所以线程。
  注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍,因为有死锁倾向。
  
  六、常用函数说明
  1.sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  2.join():等待线程终止。
  使用方式:join是Thread类的一个方法,启动线程后直接调用,即join()的作用是“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
  

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

为什么要用join()方法
  在很多情况下,主线程生成并启动了子线程,如果子线程里要进行大量的耗时运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
  不加join
  

public class Test3 extends Thread{    private String name;    public Test3(String name){        super(name);        this.name=name;    }    public void run(){        System.out.println(Thread.currentThread().getName()+"线程运行开始!");        for (int i = 0; i < 5; i++) {            System.out.println("子线程"+name+"运行:"+i);            try {                sleep((long) (Math.random()+10));            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }        System.out.println(Thread.currentThread().getName()+"线程运行结束!");    }    public static void main(String[] args) {        System.out.println(Thread.currentThread().getName()+"主线程运行开始!");        Test3 s1=new Test3("A");        Test3 s2=new Test3("B");        s1.start();        s2.start();        System.out.println(Thread.currentThread().getName()+"主线程运行结束!");    }}
输出结果:

main主线程运行开始!
main主线程运行结束!
A线程运行开始!
B线程运行开始!
子线程A运行:0
子线程B运行:0
子线程B运行:1
子线程A运行:1
子线程B运行:2
子线程A运行:2
子线程A运行:3
子线程B运行:3
子线程B运行:4
子线程A运行:4
A线程运行结束!
B线程运行结束!
发现主线程比子线程早结束。

加join

public static void main(String[] args) {        System.out.println(Thread.currentThread().getName()+"主线程运行开!");        Test3 s1=new Test3("A");        Test3 s2=new Test3("B");        s1.start();        s2.start();        try {            s1.join();        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        try {            s2.join();        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName()+"主线程运行结束!");    }}

运行结果:
main主线程运行开!
B线程运行开始!
A线程运行开始!
子线程B运行:0
子线程A运行:0
子线程B运行:1
子线程B运行:2
子线程A运行:1
子线程B运行:3
子线程A运行:2
子线程B运行:4
子线程A运行:3
子线程A运行:4
B线程运行结束!
A线程运行结束!
main主线程运行结束!

主线程一定会等到子线程都结束了才结束。

  3.yield():暂停当前正在执行的线程对象,并执行其他线程。
  Thread.yield()方法应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还可能被线程调度程序再次选中。
  结论:yield()从未导致线程转到等到/睡眠/阻塞状态。在大多数情况下,yield将导致线程从运行状态转到可运行状态,但是有可能没效果。
  sleep()和yield()的区别:
  sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时候内肯定不会被执行,yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能再进入到可执行状态后马上又被执行。
  sleep方法使当前运行中的线程睡眠一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield方法使当前线程让出CPU占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检查当前是否有相同优先级的线程处于同可运行状态,如有,则把CPU的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程。
  另外,sleep方法允许较低优先级的线程获得运行机会,但yield方法执行时,当前线程仍处于可运行状态,所以,不可能让较低优先级的线程获得CPU占用权。在一个运行系统中,如果较高优先级的线程没有调用sleep方法,又没有受到IO阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
  4.setPriority():更改线程的优先级
  用法:

Thread4 t1=new Thread4("t1");Thread4 t2=new Thread4("t2");t1.setPriority(Thread.MAX_PRIORITY);t2.setPriority(Thread.MIN_PRIORITY);

  5.interrupt():中断某个线程,这种结束方式比较粗暴,如果t线程打开了某个资源还没来得及关闭也就是run方法还没执行完就强制结束线程,会导致资源无法关闭。
  要想结束进程最好的办法就是用sleep()函数的例子程序里那样,在线程类里面用一个boolean型变量来控制run方法什么时候结束,run()方法一结束,该线程也就结束了。
  6.wait()
  Obj.wait()与Obj.notify()必须要与synchronized( Obj)一起使用,也就是wait与notify是针对已经获得了Obje锁进程操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其他线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,,唤醒线程,继续执行。这样就提供了再线程间同步、唤醒的操作。
  Threa.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。
  单单在概念上理解清楚了还不够,需要在实际的例子中进行测试才能更好的理解。对Object.wait(),Object.notify()的应用最经典的例子,应该是三线程打印ABC的问题了吧,这是一个比较经典的面试题,题目要求如下:
  建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC,这个问题用Object.wait(),notify()就可以很方便的解决。代码如下:
  

public class Test3 implements Runnable{    private String name;    private Object pre;    private Object self;    public Test3(String name,Object pre,Object self){        this.name=name;        this.pre=pre;        this.self=self;    }    public void run(){        int count=10;        while (count>0) {            synchronized (pre) {                synchronized (self) {                    System.out.print(name);                    count--;                    self.notify();                }                try {                    pre.wait();                } catch (InterruptedException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }            }        }    }    public static void main(String[] args) throws InterruptedException {        Object a=new Object();        Object b=new Object();        Object c=new Object();        Test3 pa=new Test3("A", c, a);        Test3 pb=new Test3("B", a, b);        Test3 pc=new Test3("C", b, c);        new Thread(pa).start();        Thread.sleep(100);        new Thread(pb).start();        Thread.sleep(100);        new Thread(pc).start();        Thread.sleep(100);    }}

输出结果:ABCABCABCABCABCABCABCABCABCABC
  先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程,为了控制线程执行的顺序,那么必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是pre,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有pre锁,也就是前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用pre.wait()释放pre对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行顺序。
  wait和sleep区别:
  共同点:
    1.它们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
    2.wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt()方法。如果此线程B正在wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch(){}中直接return即安全的结束线程。
    需要注意的是,interruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一丹该线程进入到wait()/join()后,就会立刻抛出InterruptedException。
  不同点:
    1.Thread类的方法:sleep(),yield()等。
    Object的方法:wait(),notify()等
    2.每个方法都有一个锁来控制同步访问。synchronized关键字可以和对象的锁交互,来实现线程的同步。sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
    3.wait,notify和notifyAll只能再同步控制方法或者同步控制块里使用,而sleep可以在任何地方使用。
    4.sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。
    所以sleep()和wait()方法最大的区别是:sleep()睡眠时,保持对象锁,仍然占有该锁,而wait()睡眠时,释放对象锁。但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedEcxeption。(但不建议使用该方法)

  sleep()方法
    sleep()使当前线程进入停滞状态(阻塞当前线程),让出CPU的使用、目的是不让当前线程独自霸占该CPU,以留一定时间给其他线程执行的机会。sleep()方法是Thread类的Static方法,因此它不能改变对象的机锁,所以当一个synchronized块中调用sleep()方法时,线程虽然休眠了,但是对象的机锁并没有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。在sleep()休眠时间到后,该线程不一定会立即执行,这是因为其他线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。
  wait()方法
    wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;wait()使用notify或notifyAll或者指定睡眠时间来唤醒当前等待池中的线程。wait()必须放在synchronized block中,否则会在program runtime时扔出Java.lang.IllegalMonitorStateException异常。
  七.常见线程名词解释
  主线程:JVM调用程序main()所产生的线程。
  当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。
  后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和后台线程的区别在于,是否等待主线程依赖于主线程结束而结束。
  前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操作者一样的关系,傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认为前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。
  线程类的一些常用方法:
  sleep():强迫一个线程睡眠N毫秒。
  isAlive():判断一个线程是否存活。
  join():等待线程终止。
  activeCount():程序中活跃的线程数。
  enumerate():枚举程序中的线程。
  currentThread():得到当前线程。
  isDaemon():一个线程是否为守护线程。
  setDaemon():设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)。
  setName():为线程设置一个名称。
  wait():强迫一个线程等待。
  notify():通知一个线程继续运行。
  setPriority():设置一个线程的优先级。
  
  八.线程同步
  1.synchronized关键字的作用域有两种:
  1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相互干的。也就是说,其它线程照样可以同时访问相同的另一个对象实例中的synchronized方法。
  2)除了方法前面用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是:synchronized(this){/区块/},它的作用域是当前对象。
  3)synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){}在继承类中并不自动是synchronized f(){},而变成了f(){}。继承类需要你显示的指定它的某个方法为synchronized方法;
  总的来说,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
  在进一步阐述之前,我们需要明确几点:
  A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当做锁–而且同步方法很可能还会被其他线程的对象访问。
  B.每个对象只有一个锁(lock)与之关联。
  C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。    

  如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized的instance函数B,那么这个类的同一个对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,
  1.线程同步的目的就是为了保护多个线程访问一个资源时对资源破坏。
  2.线程同步方法是通过锁来实现的,每个对象都有且仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
  3.对于静态同步方法,锁是针对这个类的,锁对象是该类的class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
  4.对于同步,要时刻清醒在哪个对象上同步,这是关键。
  5.编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全作出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
  6.当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
  7.死锁是线程间相互等待锁造成的,在实际中发生的概率非常的小。真让写一个死锁程序,不一定好使,但是一旦程序发生死锁,程序将死掉。

  九.线程数据传递
  在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法像函数一样通过函数参数和return语句来返回数据。
  9.1通过构造方法传递数据
  在创建线程时,必须要建立一个Thread类的或其子类的实例,因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用类变量保存起来,以便线程使用(其实就是在run方法中使用)。下面的代码演示如何通过构造方法来传递数据:

public class Test3 extends Thread{    private String name;    public Test3(String name){        this.name=name;    }    @Override    public void run() {        System.out.println("hello "+name);    }    public static void main(String[] args) throws InterruptedException {        Test3 thread=new Test3("world");        thread.start();    }}

由于这种方法是在创建线程对象的同时传递数据的,因此在线程运行之前这些数据就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。由于Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。
  9.2通过变量和方法传递出具
  向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,另一个机会是在类中定义一系列的public的方法或变量(也可称为字段)。然后再建立完对象后,通过对象实例逐个赋值。下面的代码的对Test3类的改版,使用了一个setName方法来设置name变量:

public class Test3 implements Runnable{    private String name;    public void setName(String name) {        this.name = name;    }    @Override    public void run() {        System.out.println("hello "+name);    }    public static void main(String[] args)  {        Test3 th=new Test3();        th.setName("world");        Thread thread=new Thread(th);        thread.start();    }}

  9.3通过回调函数传递数据
  上面讨论的两种向线程中传递数据的方法是最常用的。但这两种方法都是main()方法中主动将数据传入线程类的。这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据。如在下面的run方法中产生3个随机数,然后通过work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个value是无法事先就传入线程类的。

import java.util.Random;class Data{    public int value=0;}class Work{    public void process(Data data,Integer... numbers){        for(int n:numbers){            data.value+=n;        }    }}public class Test3 extends Thread{    private Work work;    public Test3(Work work){        this.work=work;    }    @Override    public void run() {        Random random=new Random();        Data data=new Data();        int n1=random.nextInt(1000);        int n2=random.nextInt(2000);        int n3=random.nextInt(3000);        work.process(data, n1,n2,n3);        System.out.println(String.valueOf(n1)+"+"+String.valueOf(n2) + "+"                   + String.valueOf(n3) + "=" + data.value);    }    public static void main(String[] args)  {        Test3 th=new Test3(new Work());        th.start();    }}

  转自:http://blog.csdn.net/evankaka/article/details/44153709

0 0
原创粉丝点击