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)

  • 相关阅读:
    word设置的密码忘了怎么办?
    Navicat Report Viewer 设置 HTTP 的方法
    如何处理Navicat Report Viewer 报表
    excel密码忘记了怎么办
    Beyond Compare文本比较搜索功能详解
    Popular Cows POJ
    Problem B. Harvest of Apples HDU
    网络流模型整理
    The Shortest Statement CodeForces
    Vasya and Multisets CodeForces
  • 原文地址:https://www.cnblogs.com/syousetu/p/6723454.html
Copyright © 2011-2022 走看看