Java中多线程同步与通信

来源:互联网 发布:pony.ai 知乎 编辑:程序博客网 时间:2024/06/06 01:31

线程的创建
使用继承java.lang.Thread类来创建线程,例如:

1   class ThreadOne extends Thread

2 {

3    public void run(){

4

5 }

6 }

7

使用实现Runnable接口来创建线程,例如:

8   class ThreadTwo implements Runnable

9 {

10    public void run(){

11

12    }

13 }

线程同步技术
每个线程操作共享的数据时,该线程在操作数据完成之后,才能让其它线程操作。如果不这样做,则会出现共享的数据被操作成一个不理想的值。
例如:桌上有3个苹果,有两个线程来吃它们,线程完成以下操作:
while(apple > 0) appl- -;
线程A和B都启动后,首先A吃一个苹果,然后苹果减少一个,这时,apple 为2了;这时如果轮到B吃,则B发现还有2个苹果,即(apple > 0),B吃一个苹果,然后苹果减少一个,这时为1了;如果这时,轮到A吃,则A发现还有1一个苹果(apple > 0),则A准备吃一个,还没有开始吃的时候,系统分配给线程A的时间片到期了,这时该轮到B来吃,因为A只是准备吃,实际还没有吃(也就是apple- -没有执行),所以,还有1个苹果存在(apple > 0),这时B就吃了它(apple- -,这时就没有苹果了,apple = 0),这时,B又准备吃苹果,但发现没有苹果了,(apple > 0)不成立,B线程也就没事干了,结束。这时,先前正准备吃苹果(表明已经进入到while()循环内部)的A还没开始吃,系统分它的线程时间片就执行完了,所以当时它停下来了,让线程B执行,殊不知,在它停下来之后,线程B吃了一个苹果,但A并不知道,现在,又轮到A来执行,它执行吃苹果的操作(apple- -),这一吃,就让apple = -1了,下次A再想吃时,发现(apple > 0)不成立,A线程也终止。上述过程,最终结果就是苹果的剩余量为-1,但这并不是理想的结果。
要处理上面的问题,就需要使用线程同步技术。

以上述过程为例,使用线程同步技术,让线程A和B同步,可以考虑用下面的方式来实现:

14     Object obj = new Object();

15 synchronized(obj){

16     while(apple > 0){

17     apple- -;

18 }

19 }

这里,用到了synchronized关键字,将吃苹果的任务(操作applet变量的代码)放入synchronized代码块中.synchronized接收一个参数obj,这个参数是任意的一个对象,例如String型, Image型等任何对象。这个对象参数,叫锁(或者叫监视器,或者锁旗标),这里,obj就是一个锁。

当线程A进入synchronized块中后,线程A就获取了这个锁,这时,如果A在执行synchronized内部代码的过程中,系统分配给A的线程时间片已经到了,这时A就理应停下来,线程B开始执行,线程试图进入synchronized块中时,B发现obj锁被线程A获取了,这时,B就没有权限进入synchronized块中执行,这时系统转到线程A,线程A又开始执行,A把synchronized内的代码执行完成后,A才会释放锁。这时,如果系统给机会让线程B运行,线程B发现synchronized块的锁已经被线程A释放,B就进入synchronized块中执行,同时,B也获取了锁,这时,在B释放锁之前,其它任何线程都没有机会进入synchronized 块中.

除了使用synchronized代码块的方式来实现之外,也可以用“同步方法”来实现,即在方法前加synchronized 关键字,例如:

20  synchronized public void eatApple(){

21   while(apple > 0) apple- -;

22 }

使用synchronized块时,显式地声明了一个锁对象,上例中是Objec类型的obj,而使用同步方法,代码中没有显式地锁对象,但实际上,有一个隐式的锁对象,即this(调用该方法的当前对象)。
如果说该同步方法是一个静态的(static)方法,那么,这个隐式的锁对象就是Class。

需要注意的是,synchronized方法只锁定调用该方法的当前对象(或Class),如果该方法负责操作一个变量N,而且另一个名为XXX的方法也可以操作N变量的代码,那么,当一个线程锁定在该synchronized方法内时,并不影响XXX,XXX方法可以实时地取存变量N。例如:

23 class TestSync

24 {

25     public static void main(String[] args)

26     {

27         Thread1 trd1 = new Thread1();

28         Thread t = new Thread(trd1);

29         t.start();

30         try{

31             Thread.sleep(1);

32         }

33         catch(Exception ex){

34             ex.printStackTrace();

35         }

36         trd1.operateN();

37     }

38 }

39

40 class Thread1 implements Runnable

41 {

42     int n = 0;

43     public synchronized void run(){

44         n = 1;

45         try{

46             Thread.sleep(1000);

47         }

48         catch(Exception ex){

49             ex.printStackTrace();

50         }

51         finally{

52             System.out.println(Thread.currentThread().getName() + " n = " + n);

53         }

54     }

55

56     public void operateN(){

57         n = 2;

58         System.out.println(Thread.currentThread().getName() + " n = " + n);

59     }

60 }

61

当线程t启动后,将n的值改成了1,这时,外面有一个operateN()方法,被一个名为trd1的Thread1类型的对象调用,这个operateN()并不受run方法内的synchronized锁的限制,于是, operateN()方法执行了把n值改为2的操作。当线程t在睡眠1秒后醒过来时,它打印输出的n值并不是1,而是2。因为在它睡眠的过程中,n已经被synchronized块之外的方法修改了值。

原创粉丝点击