码迷,mamicode.com
首页 > 其他好文 > 详细

对象及变量的并发访问一

时间:2016-05-08 22:33:17      阅读:182      评论:0      收藏:0      [点我收藏+]

标签:

一、多个线程操作一个对象实例
     当两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则有可能会出现“非线程安全问题”。
 1 package concurrent;
 2 /**
 3  * 测试不同线程操作同一个实例变量线程安全问题
 4  * @author foolishbird_lmy
 5  *
 6  */
 7 class ThePrivateNumber{
 8      private int num = 0;
 9 
10      public synchronized void addI(String username){
11         try {
12             if (username.equals("a" )){
13                 num = 100;
14                System. out .println("a set over!");
15                Thread. sleep(2000);
16            } else {
17                 num = 200;
18                System. out .println("b set over!");
19            }
20            System. out .println(Thread.currentThread().getName()+ " num is " +num );
21        } catch (InterruptedException e){
22            e.printStackTrace();
23        }
24     }
25 }
26 
27 class ThePrivateNumA extends Thread{
28      private ThePrivateNumber tNumber;
29      public ThePrivateNumA(ThePrivateNumber tNumber){
30         this .tNumber = tNumber;
31     }
32      public void run(){
33         tNumber.addI( "a" );
34     }
35 }
36 class ThePrivateNumB extends Thread{
37      private ThePrivateNumber tNumber;
38      public ThePrivateNumB(ThePrivateNumber tNumber){
39         this .tNumber = tNumber;
40     }
41      public void run(){
42         tNumber.addI( "b" );
43     }
44 }
45 public class ThePrivateNum {
46      public static void main(String[] args) {
47        ThePrivateNumber tNumber = new ThePrivateNumber();
48        ThePrivateNumA tNumA = new ThePrivateNumA(tNumber);
49        tNumA.setName( "A" );
50        tNumA.start();
51        ThePrivateNumB tNumB = new ThePrivateNumB(tNumber);
52        tNumB.setName( "B" );
53        tNumB.start();
54     }
55 }

技术分享技术分享

  

  上面演示的就是两个线程操作同一个对象实例,如果没有加synchronized关键字执行同步,则会出现线程安全问题,也就是产生脏读数据,即读取到的数据是已经被修改过的数据;
     分析:当线程A进入获取到CPU执行权,匹配到“a”,则num=100,之后该线程被休眠2秒,此时B线程获得CPU执行权,开始执行到else代码,执行num=200,之后退出,A线程经过短暂休眠后自动苏醒继续执行,但是此时num已经被更改为了200,所以最后输出num都是200,这就是线程非安全的;加了同步锁之后,线程B必须等待线程A执行完之后才能进入,所以不会产生数据被修改问题。
 
二、一个对象实例中有同步方法与非同步方法
 1 package concurrent;
 2 
 3 class MySynchroized{
 4      public synchronized void methodA(){
 5         try {
 6            System. out .println("the methodA is start:"+Thread. currentThread().getName());
 7            Thread. sleep(5000);
 8            System. out .println("A end time "+System.currentTimeMillis ());
 9        } catch (InterruptedException e){
10            e.printStackTrace();
11        }
12     }
13      //synchronized,分别测试加同步锁与不加执行顺序
14      public void methodB(){
15         try {
16            System. out .println("the methodB is start:"+Thread. currentThread().getName());
17            System. out .println("B begin time "+System.currentTimeMillis ());
18            Thread. sleep(5000);
19        } catch (InterruptedException e){
20            e.printStackTrace();
21        }
22     }
23 }
24 class TestA extends Thread{
25      private MySynchroized ms;
26      public TestA(MySynchroized ms){
27         this .ms = ms;
28     }
29      public void run(){
30         ms.methodA(); //调用同步方法
31     }
32 }
33 class TestB extends Thread{
34      private MySynchroized ms;
35      public TestB(MySynchroized ms){
36         this .ms = ms;
37     }
38      public void run(){
39         ms.methodB(); //调用非同步方法
40     }
41 }
42 public class TestSynchroized {
43      public static void main(String[] args) {
44        MySynchroized ms = new MySynchroized();
45        TestA a = new TestA(ms);
46        a.setName( "A" );
47        TestB b = new TestB(ms);
48        b.setName( "B" );
49        a.start();
50        b.start();
51     }
52 }

技术分享技术分享

  

  分析:在共享的对象实例类中有两个方法,我们分别设置为同步与非同步,
(1)左边是B方法非同步测试效果,我们可以看到,当执行A线程时,A拿到该对象的实例锁,但是并没有影响线程B执行非同步的方法,说明A、B线程几乎是同时进行,B线程并没有因为A拿到锁而发生等待现象,属于异步执行;
(2)右边是将B方法也执行同步,可以看到A、B线程同步执行即顺序执行,当A先进入拿到对象锁,B此时就会是同步等待状态,只有当A执行休眠完成,释放锁之后B才有机会执行,属于同步执行操作。
     结论:对于执行相同对象的不同线程,执行对象类中的同步方法时,不管有多少个同步的方法,都是同一个对象锁,必须等一个线程执行完毕释放锁之后另一个线程才能获取执行;但是非同步方法可以任意时刻调用,不收锁限制。
 
三、关于脏读问题
 1 package concurrent;
 2 class DirtyRead {
 3      private String name = "a" ;
 4      private String id = "aa" ;
 5      public synchronized void set(String name, String id) {
 6         try {
 7             this .name = name;
 8            Thread. sleep(2000);
 9             this .id = id;
10            System. out .println("set method " + Thread.currentThread().getName()
11                   + " name:" + name + " id:" + id);
12        } catch (InterruptedException e) {
13            e.printStackTrace();
14        }
15     }
16      public void get() {
17        System. out .println("get method " + Thread.currentThread().getName()
18                + " name:" + name + " id:" + id );
19     }
20 }
21 class DirtyReadRun extends Thread {
22      private DirtyRead dr;
23      public DirtyReadRun(DirtyRead dr) {
24         this .dr = dr;
25     }
26      public void run() {
27         dr.set( "b" , "bb" );
28     }
29 }
30 public class TestDirtyRead {
31      public static void main(String[] args) {
32         try {
33            DirtyRead dr = new DirtyRead();
34            DirtyReadRun drr = new DirtyReadRun(dr);
35            drr.start();
36 //         Thread.sleep(1000);
37            Thread. sleep(3000);
38            dr.get();
39        } catch (InterruptedException e) {
40            e.printStackTrace();
41        }
42     }
43 }

技术分享技术分享

  

 分析:
(1)左边结果是在主线程中休眠1秒,可以发现读取数据发生了错误,原因是我们将set方法设置为同步,所以线程在执行时拿到锁后会安全执行,数据的设置没有问题,但是当我们调用非同步方法get获取值时,注意主线程只休眠了1秒,但是我们在set线程中休眠了2秒,所以此时线程并没有来得及给id赋值操作,就直接输出了aa而不是bb;
(2)右边是主线程休眠3秒的情况,即调用get方法的线程是在线程赋值set休眠2秒之后才调用,此时已经安全给数据赋值了,所以输出结果正确;
 当然我们也可以直接给get方法执行同步操作,这有另一个线程就必须等待第一个线程执行完set里面的全部操作释放锁之后才能执行,会发生等待。
 
四、锁的可重入
  关键字synchronized拥有可重入的功能,即当一个线程得到一个对象锁之后,再次请求此对象锁时是可以再次得到该对象的锁的,自己可以再次获取自己的内部锁。
 1 package concurrent;
 2 /**
 3  * 可重入锁测试
 4  * @author foolishbird_lmy
 5  *
 6  */
 7 class Synch{
 8      public synchronized void sA(){
 9        System. out .println("sA()" );
10        sB();
11     }
12      public synchronized void sB(){
13        System. out .println("sB()" );
14        sC();
15     }
16      public synchronized void sC(){
17        System. out .println("sC()" );
18     }
19 }
20 class SynchARun extends Thread{
21      private Synch sa;
22      public SynchARun(Synch sa){
23         this .sa = sa;
24     }
25      public void run(){
26         sa.sA();
27     }
28 }
29 public class ReSynchronized {
30      public static void main(String[] args) {
31        SynchARun sa = new SynchARun( new Synch());
32        sa.start();
33     }
34 }

技术分享

   从输出结果可以看出,线程只要获取到了该对象锁,其他的同步锁也一样能获取。

 

五、静态同步synchronized方法与synchronized(class)代码块

   用static修饰的同步方法中的同步锁是给Class类上锁,而非static方法是给对象上锁;

 

 1 package concurrent;
 2 
 3 class StaticSyn {
 4      public static synchronized void printA() {
 5         try {
 6            System. out .println(Thread.currentThread().getName() + " : "
 7                   + System.currentTimeMillis() + "进入printA()");
 8            Thread. sleep(3000);
 9            System. out .println(Thread.currentThread().getName() + " : "
10                   + System.currentTimeMillis() + "退出printA()");
11        } catch (InterruptedException e) {
12            e.printStackTrace();
13        }
14     }
15      public static synchronized void printB() {
16         try {
17            System. out .println(Thread.currentThread().getName() + " : "
18                   + System.currentTimeMillis() + "进入printB()");
19            Thread. sleep(3000);
20            System. out .println(Thread.currentThread().getName() + " : "
21                   + System.currentTimeMillis() + "退出printB()");
22        } catch (InterruptedException e) {
23            e.printStackTrace();
24        }
25     }
26      public synchronized void printC() {
27         try {
28             System. out .println(Thread.currentThread().getName() + " : "
29                   + System.currentTimeMillis() + "进入printC()");
30            Thread. sleep(3000);
31            System. out .println(Thread.currentThread().getName() + " : "
32                   + System.currentTimeMillis() + "退出printC()");
33        } catch (InterruptedException e) {
34            e.printStackTrace();
35        }
36     }
37 }
38 class StaticSynRunA extends Thread{
39      private StaticSyn ss;
40      public StaticSynRunA(StaticSyn ss){
41         this .ss = ss;
42     }
43      @SuppressWarnings( "static-access" )
44      public void run(){
45         ss. printA();
46     }
47 }
48 class StaticSynRunB extends Thread{
49      private StaticSyn ss;
50      public StaticSynRunB(StaticSyn ss){
51         this .ss = ss;
52     }
53      public void run(){
54         ss .printB ();
55     }
56 }
57 class StaticSynRunC extends Thread{
58      private StaticSyn ss;
59      public StaticSynRunC(StaticSyn ss){
60         this .ss = ss;
61     }
62      public void run(){
63         ss.printC();
64     }
65 }
66 public class TestStaticSyn {
67      public static void main(String[] args) {
68        StaticSyn ss = new StaticSyn();
69        StaticSynRunA ssa = new StaticSynRunA(ss);
70        ssa.setName( "A" );
71        ssa.start();
72        StaticSynRunB ssb = new StaticSynRunB(ss);
73        ssb.setName( "B" );
74        ssb.start();
75        StaticSynRunC ssc = new StaticSynRunC(ss);
76        ssc.setName( "C" );
77 //     ssc.start();
78     }
79 }

 

技术分享技术分享

  分析:
 (1)左边结果是注释掉C,即A、B线程都是调用静态的同步方法,所以都是同步顺序执行,他们的锁都是Class锁,是同一种锁,所以B线程必须等待A线程释放锁;
 (2)右边的结果是A、B、C线程同时运行,由于C线程调用的是非静态的同步方法,非静态的同步方法是对象锁,与其他两个线程的锁不一样,所以是异步的,但是A与B还是同步执行。

 

对象及变量的并发访问一

标签:

原文地址:http://www.cnblogs.com/lmy-foolishbird/p/5471853.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!