Java多线程:多线程的Synchronized详解

输出为什么要引入同步机制

在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。
解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。

怎样实现同步

对于访问某个关键共享资源的所有方法,都必须把它们设为synchronized
例如:
synchronized void f() { /* ... */ } synchronized void g() { /* ... */ }
如果想保护某些资源不被多个线程同时访问,可以强制通过synchronized方法访问那些资源。 调用synchronized方法时,对象就会被锁定。

 1 public class MyStack {
 2     int idx = 0;
 3     char [] data = new char[ 6];
 4     public synchronized void push( char c) {
 5       data[ idx] = c;
 6       idx++;
 7     }
 8     public synchronized char pop() {
 9       idx--;
10       return data[ idx];
11     }
12 }

说明:
•当synchronized方法执行完或发生异常时,会自动释放锁。
•被synchronized保护的数据应该是私有(private)的。

 

看几个例子

 1 public class ThreadTest3
 2 {
 3     public static void main(String[] args)
 4     {
 5         Runnable r = new HelloThread();
 6         
 7         Thread t1 = new Thread(r);
 8         
 9         //r = new HelloThread();
10         
11         Thread t2 = new Thread(r);
12         
13         t1.start();
14         t2.start();
15     }
16 }
17 
18 class HelloThread implements Runnable
19 {
20     int i;
21     
22     @Override
23     public void run()
24     {
25     //    int i = 0;
26         
27         while(true)
28         {
29             System.out.println("number: " + i++);
30             
31             try
32             {
33                 Thread.sleep((long)(Math.random() * 1000));
34             }
35             catch (InterruptedException e)
36             {
37                 e.printStackTrace();
38             }
39             
40             if(5 == i)
41             {
42                 break;
43             }
44         }
45     }
46 }

输出顺序执行:

程序执行时出现了两种输出,现在分别来分析一下:

为什么打印两个0:如文章开头所讲,由于两个线程同时访问一个资源,而且此资源未加锁就会导致访问冲突。

线程1打印语句还未执行++操作,线程2就进来了,所以导致打印两个0

为什么"只有一个线程输出":

只打印了0-4,给人感觉只打印了一条线程,其实这是两条线程共同作用的结果。

因为 i 这个变量是一个成员变量,而在同一个对象 r 中成员变量是共享的,当线程对 i 进行++操作,线程2中的 i 也会更改,所以打印出这样的结果

如果将 i 放在方法内即变成局部变量,则会打印两条线程的执行结果

总结:

关于成员变量与局部变量:如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,他们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。
如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。
停止线程的方式:不能使用Thread类的stop方法来终止线程的执行。一般要设定一个变量,在run方法中是一个循环,循环每次检查该变量,如果满足条件则继续执行,否则跳出循环,线程结束。

 

synchronized关键字

synchronized关键字:当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。

 

 1 public class ThreadTest4
 2 {
 3     public static void main(String[] args)
 4     {
 5         Example example = new Example();
 6         
 7         Thread t1 = new TheThread(example);
 8         
 9         example = new Example();
10         
11         Thread t2 = new TheThread2(example);
12         
13         t1.start();
14         t2.start();
15     }
16 }
17 
18 class Example
19 {
20     public synchronized void execute()
21     {
22         for(int i = 0; i < 5; i++)
23         {
24             try
25             {
26                 Thread.sleep((long)(Math.random() * 1000));
27             }
28             catch (InterruptedException e)
29             {
30                 e.printStackTrace();
31             }
32             
33             System.out.println("hello: " + i);
34         }
35     }
36     
37     public synchronized void execute2()
38     {
39         for(int i = 0; i < 5; i++)
40         {
41             try
42             {
43                 Thread.sleep((long)(Math.random() * 1000));
44             }
45             catch (InterruptedException e)
46             {
47                 e.printStackTrace();
48             }
49             
50             System.out.println("world: " + i);
51         }
52     }
53 }
54 
55 class TheThread extends Thread
56 {
57     private Example example;
58     
59     public TheThread(Example example)
60     {    
61         this.example = example;
62     }
63     
64     @Override
65     public void run()
66     {
67         this.example.execute();
68     }
69 }
70 
71 class TheThread2 extends Thread
72 {
73     private Example example;
74     
75     public TheThread2(Example example)
76     {    
77         this.example = example;
78     }
79     
80     @Override
81     public void run()
82     {
83         this.example.execute2();
84     }
85 }

结果为

结果可以看出两条线程都同时执行了且互不影响。

当我们修改main方法

 1 public class ThreadTest4
 2 {
 3     public static void main(String[] args)
 4     {
 5         Example example = new Example();
 6         
 7         Thread t1 = new TheThread(example);
 8         
 9         Thread t2 = new TheThread2(example);
10         
11         t1.start();
12         t2.start();
13     }
14 }

使用同一个 example,结果为

这个结果是必然的,不管你执行多少次。

很显然,线程在执行时是一个线程一个线程的执行的。

原因就如上面红字所讲的,当访问某个对象的synchronized方法时表示对该对象上锁,我们这里使用的是同一个example对象,所以只能等上一个线程执行完后才能执行下一个线程,而不能同时执行。

如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。

 

再来更改Example对象,在其方法上都加上 static 修饰

 1 public synchronized static void execute()
 2     {
 3         for(int i = 0; i < 5; i++)
 4         {
 5             try
 6             {
 7                 Thread.sleep((long)(Math.random() * 1000));
 8             }
 9             catch (InterruptedException e)
10             {
11                 e.printStackTrace();
12             }
13             
14             System.out.println("hello: " + i);
15         }
16     }
17     
18     public synchronized static void execute2()
19     {
20         for(int i = 0; i < 5; i++)
21         {
22             try
23             {
24                 Thread.sleep((long)(Math.random() * 1000));
25             }
26             catch (InterruptedException e)
27             {
28                 e.printStackTrace();
29             }
30             
31             System.out.println("world: " + i);
32         }
33     }

这时,不管你是否使用同一个example,执行结果都是先执行线程1在执行线程2.

原因就在于:

如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为Java中无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,他们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行。

 

再对Example方法进行修改,改成一个static一个非static

 1     public synchronized void execute()
 2     {
 3         for(int i = 0; i < 5; i++)
 4         {
 5             try
 6             {
 7                 Thread.sleep((long)(Math.random() * 1000));
 8             }
 9             catch (InterruptedException e)
10             {
11                 e.printStackTrace();
12             }
13             
14             System.out.println("hello: " + i);
15         }
16     }
17     
18     public synchronized static void execute2()
19     {
20         for(int i = 0; i < 5; i++)
21         {
22             try
23             {
24                 Thread.sleep((long)(Math.random() * 1000));
25             }
26             catch (InterruptedException e)
27             {
28                 e.printStackTrace();
29             }
30             
31             System.out.println("world: " + i);
32         }
33     }

这时不管你是否使用同一个example,结果都是乱序,即两个线程同时执行,原因还是在于锁对象问题。

static锁的是class对象,非static锁的就是对象,两个锁的对象不同,结果自然不同。

 

synchronized代码块

synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;

synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的。

所以更多情况下,我们选择把需要同步的逻辑写在synchronized块中。

1 synchronized(object)
2 {
3       。。。          
4 }

表示线程在执行的时候会对object对象上锁

所以上面的程序就可以改为

 1 public void execute()
 2     {
 3         synchronized (this)
 4         {
 5             for (int i = 0; i < 20; i++)
 6             {
 7                 try
 8                 {
 9                     Thread.sleep((long) (Math.random() * 1000));
10                 }
11                 catch (InterruptedException e)
12                 {
13                     e.printStackTrace();
14                 }
15 
16                 System.out.println("hello: " + i);
17             }
18         }
19 
20     }
21 
22     public void execute2()
23     {
24         synchronized(Example.class)
25         {
26             for (int i = 0; i < 20; i++)
27             {
28                 try
29                 {
30                     Thread.sleep((long) (Math.random() * 1000));
31                 }
32                 catch (InterruptedException e)
33                 {
34                     e.printStackTrace();
35                 }
36 
37                 System.out.println("world: " + i);
38             }
39         }    
40     }

 

posted on 2016-04-19 11:47  Traveling_Light_CC  阅读(335)  评论(0编辑  收藏  举报

导航