Java中的多线程

来源:互联网 发布:php class 内调用方法 编辑:程序博客网 时间:2024/04/30 09:17

转自
http://blog.csdn.net/axman/article/details/420890;
http://www.cnblogs.com/DreamSea/archive/2012/01/16/sleepandwaitdifferent.html;
http://blog.csdn.net/zyplus/article/details/6672775;
http://www.blogjava.net/pengpenglin/archive/2008/09/02/226292.html;
http://www.360doc.com/content/06/0812/15/8473_179444.shtml

进程,线程,并发执行

当运行一个程序的时候就启动了一个进程(有些启动了多个进程)。启动进程时操作系统会为进程分配资源,最主要的是内存空间。

在进程中,有些程序流程块是可以乱序执行的,并且这个代码块可以同时被多次执行。实际上这样的代码块就是线程体。线程是进程中乱序执行的代码流程。当多个线程同时运行时,这种执行模式就是并发执行。

Java 编写的程序都运行在 JVM 中,在 JVM 内部,程序的多任务是通过线程来实现的。

每用java命令启动一个java应用程序,就会启动一个JVM进程。在同一个JVM进程中,有且只有一个进程,就是它自己。在这个JVM环境中,所有程序代码的运行都是以线程来运行的。JVM找到程序程序的入口点main(),然后运行main()方法,这样就产生了一个线程,这个线程称之为主线程。当main方法结束后,主线程运行完成。JVM进程也随即退出。

一 线程对象和线程

线程对象时可以产生线程的对象。比如在Java 平台中Thread 对象,Runnable 对象。
线程,是指正在执行的一个指令序列。Java 平台是指从一个线程对象的 start() 开始,运行 run 方法体中的一段相对独立的过程。进程对象是JVM 产生的一个普通的 Object 子类,而线程是 CPU 分配给这个对象的一个运行过程。我们说的这个线程在干什么,不是说一个线程对象在干什么,而是指这个运行在做什么。

public class MyThread1 extends Thread {    public static void main(String[] args) {        // TODO Auto-generated method stub        new MyThread1();        new MyThread1().start();        System.out.println("this is Main");    }    public void run(){        System.out.println("this is Thread");    }}//this is Main//this is Thread

执行改程序:
1. main 方法第一行产生一个线程对象,但是没有启动线程。
2. main 方法第二行产生一个线程对象,并启动了一个线程
3. main 方法第三行,产生并启动一个线程后,主线程自己也继续执行其他语句。

二 Java 线程对象

Java 中要开始一个线程有两种方式:
- 一种继承Thread类并重写run 方法,然后直接调用Thread 实例的start() 方法。
- 二是实现runnable接口并重写run方法,然后将Runnable 实例传递给一个 Thread 实例,然后调用它的start() 方法。

start()方法是一个native 方法(jdk1.5前),它将启动一个新的线程,并执行run 方法。

上面说到进程对象和进程是两个完全不同的概念,这里深入一下,生成一个线程的实例,并不代表启动了线程。而启动线程是说在某个线程对象上启动了该实例对应的线程,当该线程结束后,并不会立即消失。

Thread 实例

如果生成MyThread 的一个实例并调用其start() 方法,那么就产生了这个实例对应的线程:

public class MyThread1 extends Thread {    public static void main(String[] args) {        // TODO Auto-generated method stub        Thread mth = new MyThread1();        mth.start();        System.out.println("mian:11 ");    }    private int x=0;    public void run(){        for(int i=0;i<10;i++){            System.out.print((x++)+" ");        }       }}

可以知道由于单cpu 的原因,一般会先打印11,然后打印1-9 mian:11 0 1 2 3 4 5 6 7 8 9

不过我们可以控制线程让它按照我们的意思来运行:

public class MyThread1 extends Thread {    public static void main(String[] args) throws InterruptedException {        Thread mth = new MyThread1();        mth.start();        //join() 方法        mth.join();        System.out.print("11");    }    private int x=0;    public void run(){        for(int i=0;i<10;i++){            System.out.print((x++)+" ");        }       }}

这时输出就是0 1 2 3 4 5 6 7 8 9 11
在mth 实例对应的线程运行完成之后主线程才打印。这是因为mth.join()“在线程 mth上调用join() 方法,就是让当前程序等待线程对象mth 对应的线程完成后才继续运行”。

//一个线程对象只可以调用一次start()public class MyThread1 extends Thread {    public static void main(String[] args) throws InterruptedException {        Thread mth = new MyThread1();        mth.start();        //join() 方法        mth.join();        System.out.print("11  ");        //再调用mth 的start() 方法,报错:        //Exception in thread "main" java.lang.IllegalThreadStateException        mth.start();    }    private int x=0;    public void run(){        for(int i=0;i<10;i++){            System.out.print((x++)+" ");        }       }}

当线程对象mth 运行完后,再次在这个线程对象上启动线程,会提示:Exception in thread "main" java.lang.IllegalThreadStateException。 也就是线程对象运行完一次后,不能再运行第二次了。
我们可以看下它的具体实现:

public synchronized void start() {        if (started)            throw new IllegalThreadStateException();        started = true;        group.add(this);        start0();    }

一个Thread的实例一旦调用start()方法,这个实例的started标记就标记为true,事实中不管这个线程后来有没有执行到底,只要调用了一次start()就再也没有机会运行了,这意味着:通过Thread实例的start(),一个Thread的实例只能产生一个线程

Runnable 实例

如果要在一个实例上产生多个线程(多个线程共同访问同一个实例的一些共同资源),我们应该如何做呢?这就是 Runnable 接口给我们带来的伟大的功能。

class MyRunable implements Runnable {    private int x=0;    public void run(){        for(int i=0;i<10;i++){            System.out.print((x++)+" ");        }       }}

正如它的名字一样,Runnable 的实例是可运行的,但它自己并不能直接运行,它需要被Thread对象来包装才行运行:

public static void main(String[] args) throws InterruptedException {        //实现 Runnable接口来        MyRunable rn = new MyRunable();        Thread th1 = new Thread(rn);        th1.start();    }

结果和上面一样,但是如果把一个 Runnable 实例给Thread对象多次包装,我们就可以看到它们实际是在同一实例上启动线程:

//Runnable实例给Thread对象多次包装        MyRunable rns = new MyRunable();        for(int i=0;i<10;i++){            Thread ths = new Thread(rns);            ths.start();        }

输出结果则是0-99,x 被加到99。这说明这10 个线程是在同一个 rns 对象上运行的。请大家注意,因为这个例子是在单CPU上运行的,所以没有对多个线程同时操作共同的对象进行同步。这里是为了说明的方便而简化了同步,而真正的环境中你无法预知程序会在什么环境下运行,所以一定要考虑同步。

到这里我们做一个完整的例子来说明线程产生的方式不同而生成的线程的区别:

public class Runable {    public static void main(String[] args) throws InterruptedException {        for(int i=0;i<10;i++){            Thread th = new MyThread();            th.start();             }        //这里主线程main 中的换行,与上面的线程先执行那个不确定        //打印出来的有可能先换行再输出1...也有可能输出几个1,再换行        //System.out.println();        //mian 线程睡眠一段时间,让上面的线程运行完成        Thread.sleep(1000);        System.out.println();        //Runnable实例给Thread对象多次包装        MyRunable rns = new MyRunable();        for(int i=0;i<10;i++){            Thread ths = new Thread(rns);            ths.start();        }    }}//继承 Thread 生成 MyThread 的一个实例,然后调用他们的 start 方法class MyThread extends Thread{    private int x=0;    public void run(){        System.out.print((++x)+" ");    }}class MyRunable implements Runnable {    private int x=0;    public void run(){          System.out.print((++x)+" ");    }}//1 1 1 1 1 1 1 1 1 1 //1 2 3 4 5 6 7 8 9 10 

上面 10 个线程对象产生的 10 个线程运行时打印了 10 次 1,下面 10 个线程对象产生的 10 个线程运行时打印了 1 到 10. 我们把下面的 10 个线程称为同一实例(Runnable 实例)的10 个线程

三 start() 方法

一个线程对象生成后,如果要产生一个执行的线程,就一定要调用它的start() 方法。
在介绍这个方法时不得不同时说明run方法。其实线程对象的run方法完全是一个接口回调方法,它是你这个线程对象要完成的具体逻辑.简单说你要做什么就在run中完成,而如何做,什么时候做就不需要你控制了,你只要调用start()方法,JVM就会管理这个线程对象让它产生一个线程并注册到线程处理系统中(线程栈)。

从表面上看, start() 方法调用了 run() 方法,事实上, start() 方法并没有直接调用run 方法。在JDK1.5以前start()方法是本地方法,它如何最终调用run方法已经不是JAVA程序员所能了解的.而在JDK1.5中,原来的那个本地start()方法被start0()代替,另个一个纯JAVA的start()中调用本地方法start0(),而在start()方法中做了一个验证,就是对一个全局变量(对象变量)started做检验,如果为true,则start()抛出异常,不会调用本地方法start0(),否则,先将该变量设有true,然后调用start0()。

从中我们可以看到这个为了控制一个线程对象只能运行成功一次start()方法.这是因为线程的运行要获取当前环境,包括安全,父线程的权限,优先级等条件,如果一个线程对象可以运行多次,那么定义一个static的线程在一个环境中获取相应权限和优先级,运行完成后它在另一个环境中利用原来的权限和优先级等属性在当前环境中运行,这样就造成无法预知的结果.简单说来,让一个线程对象只能成功运行一次,是基于对线程管理的需要。

start() 最本质的功能是从 cpu 中申请另一个线程空间来执行 run() 方法中的代码它和当先的线程是两条线,在相对独立的线程空间运行。也就是说,如果直接调用线程对象的 run() 方法,也会执行但是那是在当前线程中执行,而调用 start() 方法后,run() 方法的代码会和当前线程并发(单cpu)或并行(多cpu)执行。所以调用线程对象的 run 方法不会产生新的线程,虽然可以得到相同的结果,但是执行过程和执行效率不同。

四 线程的interrupt() 方法,interrupted() isInterrupted()

先说interrupt()方法,它是实例方法,而它也是最奇怪的方法,在java语言中,线程最初被设计为”隐晦难懂”
的东西,直到现在它的语义不没有象它的名字那样准确.
大多数人以为,一个线程象调用了interrupt()方法,那它对应的线程就应该被中断而抛出异常,
事实中,当一个线程对象调用interrupt()方法,它对应的线程并没有被中断,只是改变了它的中断状态.使当前线程的状态变以中断状态,如果没有其它影响,线程还会自己继续执行. 只有当线程执行到sleep,wait,join等方法时,或者自己检查中断状态而抛出异常的情况下,线程才会抛出异常.

如果线程对象调用interrupt()后它对应的线程就立即中断,那么interrupted()方法就不可能执行.因为interrupted()方法是一个static方法,就是说只能在当前线程上调用,而如果一个线程interrupt()后
它已经中断了,那它又如何让自己interrupted()?

正因为一个线程调用interrupt()后只是改变了中断状态,它可以继续执行下去,在没有调用sleep,wait,join等法或自己抛出异常之前,它就可以调用interrupted()来清除中断状态(还会原状) interrupted()方法会检查当前线程的中断状态,如果为”被中断状态”则改变当前线程为”非中断状态”并返回true,如果为”非中断态”则返回false,它不仅检查当前线程是否为中断状态,而且在保证当前线程回来非中断状态,所以叫”interrupted”,是说中断的状态已经结束(到非中断状态了)

isInterrupted()方法则仅仅检查线程对象对应的线程是否是中断状态,并不改变它的状态.

目前大家只能先记住这三个方法的功能,只有真正深入到多线程编程实践中,才会体会到它们为什么
是对象方法,为什么是类方法.

线程到底什么时候才被中断抛出InterruptedException异常,我们将在提高篇中详细讨论.

sleep() ,join(),yield()方法


在现在的环节中,我只能先说明这些方法的作用和调用原则,至于为什么,在基础篇中无法深入,只能在提高篇中详细说明(原文的提高篇)。

sleep()方法
sleep() 方法是类方法,也就是对当前线程而言的,不可以指定某个线程去sleep,只能是当前线程执行到sleep() 方法时,sleep()使当前线程进入停滞阶段(阻塞当前线程,让其他线程运行).
事实上也只能是类方法,在当前线程上调用.试想如果你调用一个线程对象的sleep()方法,那么这个对象对应的线程如果不是正在运行,它如何sleep()?所以只有当前线程,因为它正在执行,你才能保证它可以调用sleep()方法.
原则:在同步方法中尽量不要调用线程的sleep()方法,或者简单说,对于一般水平的程序员你基本不应该调用sleep()方法.

join() 方法

在一个线程对象上调用join方法,是当前线程等待这个线程对象对应的线程结束
比如有两个工作,工作A要耗时10秒钟,工作B要耗时10秒或更多.我们在程序中先生成一个线程去做工作B,然后做工作A.

new B().start();//做工作B
A();//做工作A

工作A完成后,下面要等待工作B的结果来进行处理.如果工作B还没有完成我就不能进行下面的工作C,所以

B b = new B();
b.start();//做工作B
A();//做工作A
b.join();//等工作B完成.
C();//继续工作C.

原则:join是测试其他工作状态的唯一正确的方法。
我见过很多人,在处理一项工作时如果另一项工作没有完成,说让当前工作线程sleep(x),我问他,你这个x是如何指定的,你怎么知道是100毫秒而不是99毫秒或是101毫秒?其实这就是OnXXX事件的实质,我们不是要等多长时间才去做什么事,而是当等待的工作正好完成的时候去做.

  1. 调用join()方法的线程将被优先执行,直到此线程运行结束,当前线程才能继续运行
  2. 调用join(long mills)方法的线程将被优先执行, 直到此线程运行结束或者经过mills时间,当前线程才能继续运行

在这里的评论里看到

public class JoinThread extends Thread{     public JoinThread(String name){           super(name);     }     public void run(){           for(int i=0; i<10; i++){                 for(long k=0; k<100000000; k++){}                 System.out.println(this.getName() + ": " + i);           }     }     public static void main(String[] args){           Thread t1 = new JoinThread("AA AA");           t1.start();           try{                 t1.join(1000);                 //Join在这里~           } catch(InterruptedException e) {                 e.printStackTrace();           }                 System.out.println("over");     }}然后输出的结果是:AA AA: 0AA AA: 1overAA AA: 2AA AA: 3AA AA: 4AA AA: 5AA AA: 6AA AA: 7AA AA: 8AA AA: 9但是如果把 t.join(1000) 改成 t.join() 的话输出结果会变成:AA AA: 0AA AA: 1AA AA: 2AA AA: 3AA AA: 4AA AA: 5AA AA: 6AA AA: 7AA AA: 8AA AA: 9over

yield() 方法

yield 方法也是类方法,只在当前线程上调用,它主要是让当前线程放弃本次分配到的时间片。
原则:不是非常必要的情况下,没有理由调用它。

五 线程的互斥控制

线程的互斥控制

多个线程同时操作某一对象时,一个线程对该对象的操作可能会改变其状态,而该状态会影响另一线程对该对象的真正结果。就象两个操售票员同时售出同一张票一样。
线程A 线程B
1.线程A在数据库中查询存票,发现票C可以卖出
2.线程A接受用户订票请求,准备出票.
3. 这时切换到了线程B执行
4. 线程B在数据库中查询存票,发现票C可以卖出
5. 线程B将票卖了出去
6. 切换到线程A执行,线程A卖了一张已经卖出的票

所以需要一种机制来管理这类问题的发生,当某个线程正在执行一个不可分割的部分时,其它线程不能不能同时执行这一部分.象这种控制某一时刻只能有一个线程执行某个执行单元的机制就叫互斥控制或共享互斥(mutual exclusion)。 在JAVA中,用 synchronized 关键字来实现互斥控制。

synchronized 关键字

把一个单元声明为 synchronized 就可以让在同一时间只有一个线程操作该方法
每个对象只有一把监视锁(monitor lock),一次只能被一个线程获取.当一个线程获取了这一个锁后,其它线程就只能等待这个线程释放锁才能再获取.
那么synchronized关键字到底锁什么?得到了谁的锁?
对于同步块,synchronized获取的是参数中的对象锁:
synchronized(obj){
//……………
}
线程执行到这里时,首先要获取obj这个实例的锁,如果没有获取到线程只能等待.如果多个线程执行到这里,只能有一个线程获取obj的锁,然后执行{}中的语句,所以,obj对象的作用范围不同,控制程序不同.
例如:
public void test(){
Object o = new Object();

    synchronized(obj){        //...............    }}

这段程序控制不了任何,多个线程之间执行到Object o = new Object();时会各自产生一个对象然后获取这个对象有监视锁,各自皆大欢喜地执行.
而如果是类的属性:

class Test{    Object o = new Object();    public void test(){        synchronized(o){            //...............        }    }}

所有执行到Test实例的synchronized(o)的线程,只有一个线程可以获取到监视锁.?????

有时我们会这样:
public void test(){

        synchronized(this){            //...............        }    }

那么所有执行Test实例的线程只能有一个线程执行.而synchornized(o)和synchronized(this)的范围是不同的,因为执行到Test实例的synchornized(o)的线程等待时,其它线程可以执行Test实例的synchronized(o1)部分,但多个线程同时只有一个可以执行Test实例的synchornized(this).

synchronized 方法
如果一个方法声明为synchronized的,则等同于把在为个方法上调用synchronized(this).
如果一个静态方法被声明为synchronized,则等同于把在为个方法上调用synchronized(类.class).

  1. 两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块
  2. 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块
  3. 尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞.
  4. 第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
  5. 以上规则对其它对象锁同样适用.

wait方法和notify/notifyAll方法

1,wait() , notify() , notifyAll() 方法是普通对象的方法(Object超类中实现),而不是线程的方法。 如同锁一样,他们是在线程中调用某一对象上执行的。
2,wait() , notify() , notifyAll() 方法只能在同步中调用

如果没有明确在哪个对象上调用wait()方法,则为this.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同时,释放了对象锁的控制。

 class Test{        public synchronized void test(){        //获取条件,int x 要求大于100;            if(x < 100)                wait();        }    }

假如:Test t = new Test(); 现在有两个线程都执行到t.test();方法.其中线程A获取了t的对象锁,进入test()方法内. 这时x小于100,所以线程A进入等待.当一个线程调用了wait方法后,这个线程就进入了这个对象的休息室(waitset),.所以线程B有机会获得了线程A释放的锁,进入test()方法,如果这时x还是小于100,线程B也进入了t的休息室。
这两个线程只能等待其它线程调用 notity[All] 来唤醒.但是如果调用的是有参数的 wait(time) 方法,则线程A,B都会在休息室中等待这个时间后自动唤醒.

notify/notifyAll方法
这两个方法都是把某个对象上休息区内的线程唤醒,notify只能唤醒一个,但究竟是哪一个不能确定,而notifyAll则唤醒这个对象上的休息室中所有的线程.

class MyTh extends Thread{      Tests t = new Tests();        public void run(){          t.test();            System.out.println("Thread");        }    }    public class Tests {        int x = 0;        public  void test(){          if(x==0)            try{              wait();            }catch(Exception e){}        }        public static void main(String[] args) throws Exception{        new MyTh().start();        }    }

该线程不会进入 t 的wait 方法,而直接打印出 Thread。
但是test() 改为 synchronized ,线程就进入等待,注意这个线程进入等待后没有其它线程唤醒,除非强行退出 JVM 环境,否则它一直等待.

class MyTh extends Thread{      Tests t = new Tests();        public void run(){          t.test();            System.out.println("Thread say:Hello,World!");        }    }    public class Tests {        int x = 0;        //改为 synchronized 方法        public synchronized void test(){          if(x==0)            try{              wait();            }catch(Exception e){}        }        public static void main(String[] args) throws Exception{        new MyTh().start();        }    }

六 wait() stop() 区别

sleep() 方法wait() 方法sleep()使当前线程进入阻塞状态,让出CPU使用,目的是不让当前线程独自霸占该进程所获的CPU资源,所以留一定时间给其他线程执行的机会。sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁).在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。 wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;wait()使用 notify 、 notifyAlll 、指定睡眠时间 来唤醒当前等待池中的线程。wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。

所以两个方法最大的区别是:
sleep()睡眠时,保持对像锁;wait() 睡眠时,释放对像锁。
sleep()在时间到来之后会自己醒来,而wait方法需要notify 方法让它醒来

/* * Thread sleep和wait区别 * */public class ThreadTest implements Runnable {    int number = 10;    public void firstMethod() throws Exception {        synchronized (this) {            number += 100;            System.out.println(number);        }    }    public void secondMethod() throws Exception {        synchronized (this) {            /**             * (休息2S,阻塞线程)             * 以验证当前线程对象的机锁被占用时,             * 是否被可以访问其他同步代码块             */            Thread.sleep(2000);           // this.wait(2000);            number *= 200;        }    }    @Override    public void run() {        try {            firstMethod();        } catch (Exception e) {            e.printStackTrace();        }    }    public static void main(String[] args) throws Exception {        ThreadTest threadTest = new ThreadTest();        Thread thread = new Thread(threadTest);        System.out.println("number="+threadTest.number);        thread.start();        threadTest.secondMethod();         System.out.println("this is mian");        System.out.println("number="+threadTest.number);    }}

使用sleep() 方法时,输出结果:

number=10number=10//等待2秒number=21002100

使用wait() 方法时,输出结果:

number=10number=10110//等待2snumber=22000

分析下,main 方法中实例化ThreadTest 并启动该线程,此时的num 为 10,输出10.调用该线程的secondMethod() ,因为是在主线程中调用,所以调用的普通方法secondMethod() 会被先执行(与上面启动的线程不是同一线程,交替执行,只是主线程的普通方法会被先执行),所以程序会先执行secondMethod().而secondMethod 代码片段中有synchronized block,因此该方法被执行后,会占有该对象机锁导致该对象线程方法一直处于阻塞状态,不能执行,直到secondMethod 释放锁。
使用 Thread.sleep(2000)时,因为sleep在阻塞的同时仍然持有该对像锁,所有该对象的其他同步线程无法执行,直到 2 s 后sleep 休眠完毕,才继续向下执行。因此输出结果为:
num*200+100;此时ThreadTest.num 的值也为2100。
使用this.wait(2000)时,该方法会休眠2s 并释放当前持有的锁,此时线程的同步方法firstMethod() 会被执行(因为secondMethod持有的锁已经被wait() 释放),因此输出结果为:num+100。2s之后唤醒secondMethod所在线程,执行输出”this is main”,此时ThreadTest.num 的值为(num+100)*200=22000。

通过notify 唤醒线程。告诉wait 方法的线程可以去参与获得锁的竞争了,但不一定马上得到锁,因为锁还在别人手上。

public class MultiThread {    public static void main(String[] args) {        Thread1 th1 = new Thread1();        Thread  t1 = new Thread(th1);        Thread2 th2 = new Thread2();        Thread  t2 = new Thread(th2);        t1.start();        try {            Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }        t2.start();    }}class Thread1 implements Runnable{    public void run(){        //这里Thread1 Thread2 的run 方法要用同一对象作为监视器,这里不能用        //this,因为两个类里 this 不是同一个对象。用 MultiThread.class 这个字节码对象        //当虚拟机引用这个变量时,指向的是同一个对象。        synchronized (MultiThread.class){            System.out.println("enter Thr1");            System.out.println("Thr1  is  waiting ");            //释放锁有两种方式,一种离开synchronized 关键字管辖的代码范围,            //另一种是使用 wait 方法            try {                MultiThread.class.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("thr1 is going on");            System.out.println("thr1 is over");        }    }}class Thread2 implements Runnable{    public void run(){        synchronized (MultiThread.class){            System.out.println("enter thr2");            System.out.println("thr2 notify other thread can release wait status");            //notify 唤醒 Thread1 ,Thread2 sleep方法休息4s,但是Thread2 没有释放锁             //Thread1 仍然无法执行            MultiThread.class.notify();            System.out.println("thr2 sleep 4 s.....");            try {                Thread.sleep(4000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("thr2 is going on");            System.out.println("thr2 is over");        }    }}

输出:

enter Thr1Thr1  is  waiting //等待2 senter thr2thr2 notify other thread can release wait statusthr2 sleep 4 s....//等待4Sthr2 is going onthr2 is overthr1 is going onthr1 is over
0 0
原创粉丝点击