线程(-)synchronized

线程安全的概念:

  当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或者方法)就是线程安全的。

synchronized:可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”

  线程不安全的时候:

 1 package com.java.day01;
 2 
 3 public class MyThread extends Thread{
 4     
 5     private int count=5;
 6     
 7     public void run(){
 8         count--;
 9         System.out.println(this.currentThread().getName()+"  "+"count="+count);
10     }
11     
12     
13     public static void main(String[] args) {
14         MyThread myThread  = new MyThread();
15         
16         Thread t1 = new Thread(myThread,"t1");
17         Thread t2 = new Thread(myThread,"t2");
18         Thread t3 = new Thread(myThread,"t3");
19         Thread t4 = new Thread(myThread,"t4");
20         Thread t5 = new Thread(myThread,"t5");
21         
22         t1.start();
23         t2.start();
24         t3.start();
25         t4.start();
26         t5.start();
27         
28     }
29     
30     
31 }

  运行结果:

1 t2  count=3
2 t1  count=3
3 t3  count=2
4 t4  count=0
5 t5  count=0

  线程安全:

 1 package com.java.day01;
 2 
 3 public class MyThread extends Thread{
 4     
 5     private int count=5;
 6     
 7     //synchronized加锁
 8     public synchronized void run(){
 9         
10         count--;
11         System.out.println(this.currentThread().getName()+"  "+"count="+count);
12     }
13     
14     
15     public static void main(String[] args) {
16         MyThread myThread  = new MyThread();
17         
18         Thread t1 = new Thread(myThread,"t1");
19         Thread t2 = new Thread(myThread,"t2");
20         Thread t3 = new Thread(myThread,"t3");
21         Thread t4 = new Thread(myThread,"t4");
22         Thread t5 = new Thread(myThread,"t5");
23         
24         t1.start();
25         t2.start();
26         t3.start();
27         t4.start();
28         t5.start();
29         
30     }
31     
32     
33 }

  运行结果:

1 t1  count=4
2 t4  count=3
3 t3  count=2
4 t2  count=1
5 t5  count=0

  总结:

    当多个线程访问myThread的run方法时,以排队的方式进行处理(排队顺序按照cpu分配的先后顺序而定),一个线程想要执行synchronized修饰的方法里的代码,首先是尝试获得锁,如果拿到锁,执行synchronized代码的具体内容:拿不到锁,这个线程会不断的尝试获得这把锁,知道拿到为止,而且是多个线程同时去竞争这把锁(也就是会有锁竞争的问题)。

多个线程多个锁:多个线程都可以拿到自己指定的锁,分别获得锁之后,执行synchronized方法体的具体内容

  代码一:

 1 package com.java.day01;
 2 
 3 public class MyThread02 {
 4     public static int num=0;
 5     
 6     public  synchronized void setNum(String tag){
 7         if(tag.equals("a")){
 8             
 9             num=100;
10             System.out.println("set num over!");
11             
12         }else if(tag.equals("b")){
13             
14             num=200;
15             System.out.println("set num over!");
16             
17         }
18         
19         System.out.println("tag="+tag+"  num="+num);
20     }
21     
22     
23     public static void main(String[] args) {
24         final MyThread02 m1 =  new MyThread02();
25         final MyThread02 m2 =  new MyThread02();
26         
27         Thread t1 = new Thread(new Runnable() {
28             public void run() {
29                 m1.setNum("a");
30             }
31         });
32         
33         Thread t2 = new Thread(new Runnable() {
34             public void run() {
35                 m2.setNum("b");
36             }
37         });
38         
39         t1.start();
40         t2.start();
41         
42         
43     }
44     
45     
46 }

  执行结果:

1 set num over!
2 set num over!
3 tag=a  num=200
4 tag=b  num=200

执行结果与预期不同,并没有实现线程安全

结果分析:num为共享变量,但是两个对象获得的是两个各自对象的锁,所以两个方法可以说是同时进行,并没有被锁住

 

  代码二 给方法加入static:

 1 package com.java.day01;
 2 
 3 public class MyThread02 {
 4     public static int num=0;
 5     
 6     public static  synchronized void setNum(String tag){
 7         if(tag.equals("a")){
 8             
 9             num=100;
10             System.out.println("tag a  set num over!");
11             
12         }else if(tag.equals("b")){
13             
14             num=200;
15             System.out.println("tag b set num over!");
16             
17         }
18         
19         System.out.println("tag="+tag+"  num="+num);
20     }
21     
22     
23     public static void main(String[] args) {
24         final MyThread02 m1 =  new MyThread02();
25         final MyThread02 m2 =  new MyThread02();
26         
27         Thread t1 = new Thread(new Runnable() {
28             public void run() {
29                 m1.setNum("a");
30             }
31         });
32         
33         Thread t2 = new Thread(new Runnable() {
34             public void run() {
35                 m2.setNum("b");
36             }
37         });
38         
39         t1.start();
40         t2.start();
41         
42         
43     }
44     
45     
46 }

  运行结果:

1 tag a  set num over!
2 tag=a  num=100
3 tag b set num over!
4 tag=b  num=200

总结:

  关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当作锁,所以实例中的那哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁(Lock),两个对象,线程获得的就是两个不同的锁,他们互不影响。

  有一种情况则是相同的锁,即在静态方法上加synchronized关键字,表锁定class类,类级别的锁(独占class类)

 

对象锁的同步和异步

同步:synchronized

  同步的概念就是共享,我们要牢牢记住“共享”这两个字,如果不是共享资源,就没有必要进行同步。

异步:asynchronized

  异步的概念就是独立,相互之间不受任何制约。就好像我们学习http的时候,在页面发起的ajax请求,我们还可以继续浏览或操作页面内容,二者之间没有任何关系。

同步的目的就是为了线程安全,其实对于线程来说,需要满足两个特性:

    原子性(同步)

    可见性

代码一:

 1 package com.java.day01;
 2 
 3 public class MyThread03 {
 4     public synchronized void method1(){
 5         System.out.println(Thread.currentThread().getName());
 6         try {
 7             Thread.sleep(4000);
 8         } catch (InterruptedException e) {
 9             e.printStackTrace();
10         }
11     }
12     
13     public void method2(){
14         System.out.println(Thread.currentThread().getName());
15     }
16     
17     public static void main(String[] args) {
18         final MyThread03 myThread = new MyThread03();
19         
20         Thread t1= new Thread(new Runnable() {
21             public void run() {
22                 myThread.method1();
23             }
24         },"t1");
25         
26         Thread t2  = new Thread(new Runnable() {
27             public void run() {
28                 myThread.method2();
29             }
30         },"t2");
31         
32         t1.start();
33         t2.start();
34         
35     }
36     
37 }

运行结果:

  

可以看出,在method1方法没有执行完的之后,method2方法已经执行完

代码2,给method2方法加上关键字synchronized:

 1 package com.java.day01;
 2 
 3 public class MyThread03 {
 4     public synchronized void method1(){
 5         System.out.println(Thread.currentThread().getName());
 6         try {
 7             Thread.sleep(4000);
 8         } catch (InterruptedException e) {
 9             e.printStackTrace();
10         }
11     }
12     
13     public synchronized void method2(){
14         System.out.println(Thread.currentThread().getName());
15     }
16     
17     public static void main(String[] args) {
18         final MyThread03 myThread = new MyThread03();
19         
20         Thread t1= new Thread(new Runnable() {
21             public void run() {
22                 myThread.method1();
23             }
24         },"t1");
25         
26         Thread t2  = new Thread(new Runnable() {
27             public void run() {
28                 myThread.method2();
29             }
30         },"t2");
31         
32         t1.start();
33         t2.start();
34         
35     }
36     
37 }

运行结果:

图1可以看出正在运行method1,method2并没有被运行,图2可以看出method1运行完之后method2也开始运行且运行完。

上述代码1的method1方法是同步的,method2方法是异步的

代码2的method1和method2方法都是同步的

 A线程先持有object对象的Lock锁,B线程如果在这个时候调用对象中的同步(synchronized)方法则需等待,也就是同步

A线程先持有object对象的Lock锁,B线程可以以异步的方式调用对象中的非synchronized修饰的方法

 脏读:

  对于对象的同步和异步的方法,我们在设计自己的程序的时候,一定要考虑问题的整体,不然就会出现数据不一致的错误,很经典的错误就是脏读(dirty read)

代码1:

 1 package com.java.day01;
 2 
 3 public class DirtyRead {
 4     private String name="source";
 5     private int age=0;
 6     
 7     public synchronized void setValue(String name,int age){
 8         this.name=name;
 9         
10         try {
11             Thread.sleep(4000);
12         } catch (InterruptedException e) {
13             // TODO Auto-generated catch block
14             e.printStackTrace();
15         }
16         
17         this.age=age;
18         
19         System.out.println("setValue最终的结果:"+"name:"+name+"  "+"age:"+age);
20     }
21     
22     public void getValue(){
23 
24         System.out.println("getValue方法最终得到的结果:   "+"name:"+name+"  "+"age:"+age);
25     }
26     
27     
28     public static void main(String[] args) {
29         final DirtyRead dr = new DirtyRead();
30         Thread t1 = new Thread(new Runnable() {
31             public void run() {
32                 dr.setValue("test1", 25);
33             }
34         });
35         
36         Thread t2 = new Thread(new Runnable() {
37             public void run() {
38                 dr.getValue();
39             }
40         });
41         
42         
43         t1.start();
44         t2.start();
45         
46     }
47 }

运行结果:

1 getValue方法最终得到的结果:   name:test1  age:0
2 setValue最终的结果:name:test1  age:25

得到错误的数据

 

代码2,给getValue方法加锁:

 1 package com.java.day01;
 2 
 3 public class DirtyRead {
 4     private String name="source";
 5     private int age=0;
 6     
 7     public synchronized void setValue(String name,int age){
 8         this.name=name;
 9         
10         try {
11             Thread.sleep(4000);
12         } catch (InterruptedException e) {
13             // TODO Auto-generated catch block
14             e.printStackTrace();
15         }
16         
17         this.age=age;
18         
19         System.out.println("setValue最终的结果:"+"name:"+name+"  "+"age:"+age);
20     }
21     
22     public synchronized void getValue(){
23 
24         System.out.println("getValue方法最终得到的结果:   "+"name:"+name+"  "+"age:"+age);
25     }
26     
27     
28     public static void main(String[] args) {
29         final DirtyRead dr = new DirtyRead();
30         Thread t1 = new Thread(new Runnable() {
31             public void run() {
32                 dr.setValue("test1", 25);
33             }
34         });
35         
36         Thread t2 = new Thread(new Runnable() {
37             public void run() {
38                 dr.getValue();
39             }
40         });
41         
42         
43         t1.start();
44         t2.start();
45         
46     }
47 }

运行结果:

1 setValue最终的结果:name:test1  age:25
2 getValue方法最终得到的结果:   name:test1  age:25

上述结果是setValue方法执行完之后,在执行getValue方法。

总结:

  在我们对一个对象的方法加锁的时候,需要考虑业务的整体性,即为setValue/getValue方法同时加锁synchronized同步关键字,保证业务(service)的原子性,不然会出现业务错误(也从侧面保证业务的一致性)(如果在赋值的时候进行取值,则可能取到错误的数据)

(ACID  A:原子性  C:一致性  I:隔离性  D:永久性)

synchronized其他概念

synchronized锁重入:

  关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个此线程得到了一个对象的锁之后,再次请求此对象时是可以再次得到该对象的锁。

 代码1:

  

 1 package com.java.day01;
 2 
 3 /**
 4  * 
 5  * @author syousetu
 6  *锁的重入
 7  *
 8  */
 9 public class SyncDubb01 {
10     public synchronized void method01() {
11         System.out.println("method01...");
12         this.method02();
13     }
14 
15     public synchronized void method02() {
16         System.out.println("method02...");
17         this.method03();
18     }
19 
20     public synchronized void method03() {
21         System.out.println("method03...");
22     }
23     
24     public static void main(String[] args) {
25         final SyncDubb01 s  = new SyncDubb01();
26         
27         Thread t1 = new Thread(new Runnable() {
28             public void run() {
29                 s.method01();
30             }
31         });
32         
33         t1.start();
34         
35     }
36     
37 }

运行结果:

1 method01...
2 method02...
3 method03...

代码2:有继承关系的synchronized的应用

 1 package com.java.day01;
 2 
 3 /**
 4  * 
 5  * @author syousetu
 6  *
 7  *有继承关系的时候的synchronized的应用
 8  */
 9 public class SyncDubb02 {
10 
11     static class Main {
12         public int i = 10;
13 
14         public synchronized void operationSup() {
15             try {
16 
17                 i--;
18                 System.out.println("Main print i=" + i);
19 
20                 Thread.sleep(1000);
21             } catch (InterruptedException e) {
22                 e.printStackTrace();
23             }
24         }
25 
26     }
27 
28     static class Sub extends Main {
29         public synchronized void operationSub() {
30             while (i > 0) {
31 
32                 try {
33                     i--;
34                     System.out.println("Sub print i=" + i);
35                     Thread.sleep(1000);
36                     //调用父类的方法
37                     this.operationSup();
38                 } catch (InterruptedException e) {
39                     e.printStackTrace();
40                 }
41 
42                 
43             }
44 
45         }
46 
47     }
48     
49     
50     public static void main(String[] args) {
51         Thread t1 = new Thread(new Runnable() {
52             public void run() {
53                 Sub sub = new Sub();
54                 sub.operationSub();
55             }
56         });
57         
58         t1.start();
59         
60     }
61     
62     
63     
64 
65 }

运行结果:

 1 Sub print i=9
 2 Main print i=8
 3 Sub print i=7
 4 Main print i=6
 5 Sub print i=5
 6 Main print i=4
 7 Sub print i=3
 8 Main print i=2
 9 Sub print i=1
10 Main print i=0

synchronized 出现异常,锁自动释放:

说明:对于web应用程序,异常释放锁的情况,如果不及时处理,很可能对你的应用程序逻辑业务产生严重的错误,比如,你现在在执行一个队列任务,很多对象都去等待第一个对象正确执行完毕再去释放锁,但是第一个对象由于异常的出现,导致业务逻辑没有正常执行完毕,就释放了锁,那么可想而知后续的对象执行的都是错误的逻辑。所以这一点一定要引起注意,在编写代码的时候一定要考虑周全。

 1 package com.java.day01;
 2 
 3 public class SyncException {
 4     private int i=0;
 5     
 6     public synchronized void opteration(){
 7         while(true){
 8             
 9             try {
10                 i++;
11                 Thread.sleep(1000);
12                 System.out.println(Thread.currentThread().getName()+"  i:"+i);
13             
14                 if(i==10){
15                     Integer.parseInt("a");
16                 }
17             } catch ( Exception e) {//interruptedException
18                 //捕获到异常输出,然后继续往下执行
19                 //出现异常后处理的方法:
20                 //1.将出现异常的数据记录在日志(出现异常的数据和后续的操作没有关联关系)或者continue
21                 //2.抛出运行异常或者打断异常
22                 System.out.println("出现异常 :i="+i);
23                 e.printStackTrace();
24 //                throw new RuntimeException();
25                 continue;//跳过这次继续
26             }
27             
28         }
29     }
30     
31     public static void main(String[] args) {
32         final SyncException s = new SyncException();
33         Thread t1=  new Thread(new Runnable() {
34             public void run() {
35                 s.opteration();
36                 
37             }
38         },"t1");
39         
40         t1.start();
41         
42         
43     }
44     
45     
46     
47     
48 }

运行结果:

 1 t1  i:1
 2 t1  i:2
 3 t1  i:3
 4 t1  i:4
 5 t1  i:5
 6 t1  i:6
 7 t1  i:7
 8 t1  i:8
 9 t1  i:9
10 t1  i:10
11 出现异常 :i=10
12 java.lang.NumberFormatException: For input string: "a"
13     at java.lang.NumberFormatException.forInputString(Unknown Source)
14     at java.lang.Integer.parseInt(Unknown Source)
15     at java.lang.Integer.parseInt(Unknown Source)
16     at com.java.day01.SyncException.opteration(SyncException.java:15)
17     at com.java.day01.SyncException$1.run(SyncException.java:35)
18     at java.lang.Thread.run(Unknown Source)
19 t1  i:11
20 t1  i:12
21 t1  i:13

storm:做分布式计算

使用synchronized声明的方法在某些情况下是有弊端的,比如A线程调用同步的方法执行一个很长世间的任务,那么B线程就必须等待比较长的时间去执行,这样的情况下可以使用synchronized代码块去优化代码执行时间,也就是通常所说的减小锁的粒度。(demo1)

 

synchronized可以使用任意的object进行加锁(demo2)

 

另外特别注意一个问题,就是不要使用String的常量加锁,会出现死循环问题。(demo3)

代码1 采用字符串常量加锁:

 1 package com.java.day01;
 2 
 3 public class StringLock {
 4 
 5     public void method() {
 6         synchronized ("字符串常量") {
 7 
 8             while (true) {
 9 
10                 try {
11                     System.out.println(Thread.currentThread().getName() + "线程开始");
12                     Thread.sleep(200);
13                     System.out.println(Thread.currentThread().getName() + "线程结束");
14                 } catch (InterruptedException e) {
15                     e.printStackTrace();
16                 }
17             }
18         }
19 
20     }
21 
22     public static void main(String[] args) {
23         final StringLock sl = new StringLock();
24         Thread t1 = new Thread(new Runnable() {
25             public void run() {
26                 sl.method();
27             }
28         }, "t1");
29 
30         Thread t2 = new Thread(new Runnable() {
31             public void run() {
32                 sl.method();
33             }
34         }, "t2");
35 
36         t1.start();
37         t2.start();
38 
39     }
40 
41 }

运行结果:

1 t1线程开始
2 t1线程结束
3 t1线程开始
4 t1线程结束
5 t1线程开始

线程2获取不到锁

代码2 采用new String("字符串常量的方式加锁"):

 1 package com.java.day01;
 2 
 3 public class StringLock {
 4 
 5     public void method() {
 6         //new String("字符串常量");
 7         //"字符串常量"
 8         synchronized (new String("字符串常量")) {
 9 
10             while (true) {
11 
12                 try {
13                     System.out.println(Thread.currentThread().getName() + "线程开始");
14                     Thread.sleep(200);
15                     System.out.println(Thread.currentThread().getName() + "线程结束");
16                 } catch (InterruptedException e) {
17                     e.printStackTrace();
18                 }
19             }
20         }
21 
22     }
23 
24     public static void main(String[] args) {
25         final StringLock sl = new StringLock();
26         Thread t1 = new Thread(new Runnable() {
27             public void run() {
28                 sl.method();
29             }
30         }, "t1");
31 
32         Thread t2 = new Thread(new Runnable() {
33             public void run() {
34                 sl.method();
35             }
36         }, "t2");
37 
38         t1.start();
39         t2.start();
40 
41     }
42 
43 }

运行结果:

1 t2线程开始
2 t1线程开始
3 t2线程结束
4 t1线程结束
5 t1线程开始
6 t2线程开始

 

锁对象的改变问题,当使用一个对象进行加锁的时候,要注意对象本身发生改变的时候,那么持有的锁就不同。如果对象本身不发生改变,那么依然是同步的,即使是对象的属性发生了改变。(demo4)(用字符串常量进行加锁,在所里面修改字符串常量的值,预期锁本身应该发生了改变,但是,实验结果并没有得到验证;对于第二个实验,将对象的属性进行改变,锁仍然不改变)

 

死锁问题(demo5)

 

posted @ 2017-04-17 16:15  優syousetu  阅读(165)  评论(0编辑  收藏  举报