java多线程入门

来源:互联网 发布:北京淘宝美工培训学校 编辑:程序博客网 时间:2024/05/16 14:23

首先讲一下进程和线程的区别:

  进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。

  线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。

  线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

  多进程是指操作系统能同时运行多个任务(程序)。

  多线程是指在同一程序中有多个顺序流在执行。

java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。

一、扩展java.lang.Thread类

 public class ExtendThread extends Thread {private String name;public ExtendThread(String name) {super();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) {e.printStackTrace();}}}public static class Main {public static void main(String[] args) {ExtendThread mTh1 = new ExtendThread("A");ExtendThread mTh2 = new ExtendThread("B");mTh1.start();mTh2.start();}}}

运行结果是:

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

再次运行是:

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

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

二、实现java.lang.Runnable接口

 public class ImpleThread implements Runnable{private String name;public ImpleThread(String name) {    super();    this.name = name;    }@Override    public void run() {    for (int i = 0; i < 5; i++) {    System.out.println(name+"运行:"+i);    try {            Thread.sleep(100);            }            catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();            }        }    }public static class main{public static void main(String[] args) {ImpleThread im1=new ImpleThread("a");ImpleThread im2 =new ImpleThread("b"); new Thread(new ImpleThread("A")).start(); new Thread(new ImpleThread("B")).start();}}}

结果为:
A运行:0
B运行:0
B运行:1
A运行:1
B运行:2
A运行:2
B运行:3
A运行:3
B运行:4
A运行:4

三、Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

public class ShareThread extends Thread{private int count =5;private String name;public ShareThread(String name) {    super();    this.name = name;    }public void run(){for (int i = 0; i < 5; i++) {        System.out.println(name+"运行  count="+count--);        try {            Thread.sleep(100);            }            catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();            }        }}public static class main{public static void main(String[] args) {ShareThread mTh1=new ShareThread("A");ShareThread mTh2=new ShareThread("B");mTh1.start();mTh2.start();}}}

结果为:

A运行  count=5
B运行  count=5
B运行  count=4
A运行  count=4
A运行  count=3
B运行  count=3
A运行  count=2
B运行  count=2
B运行  count=1
A运行  count=1


从上面可以看出,不同的线程之间count是不同的,这对于卖票系统来说就会有很大的问题,当然,这里可以用同步来作。这里我们用Runnable来做下看看

public class ShareThread1 implements Runnable{private int count=15;  public void run(){ for (int i = 0; i < 5; i++) {        System.out.println(Thread.currentThread().getName()+ "运行  count= " + count--);        try {            Thread.sleep(300);            }            catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();            } }}public static class main{public static void main(String[] args){ShareThread1 sh=new ShareThread1();new Thread(sh,"A").start();new Thread(sh,"b").start();new Thread(sh,"c").start();}}}

结果是:

A运行  count= 15
c运行  count= 13
b运行  count= 14
A运行  count= 12
b运行  count= 11
c运行  count= 12
c运行  count= 9
A运行  count= 8
b运行  count= 10
c运行  count= 7
A运行  count= 5
b运行  count= 6
c运行  count= 4
b运行  count= 3
A运行  count= 2

总结:

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

注:有错误数据。该方法还需要考虑使用。

总结:

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

注意:该方法有错误数据,不是完全安全的。

四、线程状态转换



1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行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()和getPriority()方法分别用来设置和获取线程的优先级。
 
每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
 
2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(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中已经废除,不再介绍。因为有死锁倾向

六、常用函数说明

①sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

②join():指等待t线程终止。

使用方式。

join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

为什么要用join()方法

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

不加join。

 public class NoJoinThread implements Runnable{private String name;public NoJoinThread(String name) {    super();    this.name = name;    }@Override    public void run() {System.out.println(name+"线程开始执行");     for (int i = 0; i < 10; i++) {     System.out.println(name+"线程:"+i);     try {            Thread.sleep(100);            }            catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();            }     }     System.out.println(name+"线程结束执行");    }public static class Main{public static void main(String[] args){System.out.println("主线程开始执行");new Thread(new NoJoinThread("A")).start();new Thread(new NoJoinThread("B")).start();System.out.println("主线程执行完毕");}}}

执行结果:

主线程开始执行
主线程执行完毕
B线程开始执行
B线程:0
A线程开始执行
A线程:0
B线程:1
A线程:1
B线程:2
A线程:2
A线程:3
B线程:3
A线程:4
B线程:4
A线程:5
B线程:5
A线程:6
B线程:6
A线程:7
B线程:7
B线程:8
A线程:8
B线程:9
A线程:9
A线程结束执行
B线程结束执行

发现主线程比子线程早结束

加join方法以后

 public class UseJoinThread implements Runnable{private String name;public UseJoinThread(String name) {    super();    this.name = name;    }@Override    public void run() {System.out.println(name+"线程开始执行");     for (int i = 0; i < 5; i++) {     System.out.println(name+"线程:"+i);     try {            Thread.sleep(100);            }            catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();            }     }     System.out.println(name+"线程结束执行");    }public static class Main{public static void main(String[] args) throws InterruptedException{System.out.println("主线程开始执行");UseJoinThread join1=new UseJoinThread("A");UseJoinThread join2=new UseJoinThread("B");Thread t1=new Thread(join1); Thread t2=new Thread(join2); t1.start();t2.start();t1.join();t2.join();System.out.println("主线程执行完毕");}}}

得到的结果是:

主线程开始执行
B线程开始执行
B线程:0
A线程开始执行
A线程:0
B线程:1
A线程:1
B线程:2
A线程:2
B线程:3
A线程:3
B线程:4
A线程:4
B线程结束执行
A线程结束执行
主线程执行完毕

③yield():暂停当前正在执行的线程对象,并执行其他线程。

        Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
         yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
 
结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。可看上面的图。
public class YieldThread implements Runnable { private String name;public YieldThread(String name) {super();this.name = name;}public void run() {for (int i = 0; i < 5; i++) {        System.out.println(name+"线程正在执行,i为"+i);        if (i%2!=0) {            Thread.currentThread().yield();            System.out.println(name+"正处于 yield");            }        }}public static class Main{public static void main(String[] args){YieldThread y1=new YieldThread("A");YieldThread y2=new YieldThread("B");YieldThread y3=new YieldThread("C");new Thread(y1).start();new Thread(y2).start();new Thread(y3).start();}}}
结果为:B线程正在执行,i为0
C线程正在执行,i为0
A线程正在执行,i为0
C线程正在执行,i为1
B线程正在执行,i为1
C正处于 yield
A线程正在执行,i为1
C线程正在执行,i为2
C线程正在执行,i为3
C正处于 yield
C线程正在执行,i为4
B正处于 yield
B线程正在执行,i为2
B线程正在执行,i为3
A正处于 yield
A线程正在执行,i为2
A线程正在执行,i为3
B正处于 yield
B线程正在执行,i为4
A正处于 yield
A线程正在执行,i为4

可以 发现  yield之后 依然还是 这个线程。

sleep()和yield()的区别
        sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
        sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU  的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
       另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield()  方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

④setPriority(): 更改线程的优先级。

    MIN_PRIORITY = 1
       NORM_PRIORITY = 5
           MAX_PRIORITY = 10

用法:

Thread4 t1 = new Thread4("t1");Thread4 t2 = new Thread4("t2");t1.setPriority(Thread.MAX_PRIORITY);t2.setPriority(Thread.MIN_PRIORITY);
⑤interrupt():中断某个线程,这种结束方式比较粗暴,如果t线程打开了某个资源还没来得及关闭也就是run方法还没有执行完就强制结束线程,会导致资源无法关闭

  要想结束进程最好的办法就是用sleep()函数的例子程序里那样,在线程类里面用以个boolean型变量来控制run()方法什么时候结束,run()方法一结束,该线程也就结束了。

⑥wait()

Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.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()就可以很方便的解决。代码如下


0 0
原创粉丝点击