zoukankan      html  css  js  c++  java
  • 线程(-)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)

  • 相关阅读:
    OpenCV 图像旋转
    单链表插入与删除数据
    opencv 数据训练
    C++ 小波变换
    二十七、miniscrapy,scrapy源码初解
    二十六、Scrapy自定义命令
    二十五、scrapy中的去重规则及自定义
    二十四、在scrapy中如何获取cookies
    十六、 IO多路复用,异步非阻塞
    五、IO模型简介
  • 原文地址:https://www.cnblogs.com/syousetu/p/6723454.html
Copyright © 2011-2022 走看看