zoukankan      html  css  js  c++  java
  • Java并发编程基础——同步

    一、synchronized 关键字

      1)synchronized 锁什么?锁对象。可能锁对象包括: this, 临界资源对象,Class 类对象。如同下面例子所示;

     1 package cn.test.juc;
     2 
     3 public class TestSynchronized {
     4 
     5     private int count = 0;
     6     private Object object = new Object();
     7 
     8 
     9     public void testSyn1() {
    10         //锁对象(这里面是锁临界资源)
    11         synchronized (object) {
    12             System.out.println(Thread.currentThread().getName()
    13                 +" count =" + count++);
    14         }
    15     }
    16 
    17     public void testSyn2() {
    18         //锁当前对象
    19         synchronized (this) {
    20             System.out.println(Thread.currentThread().getName()
    21                     +" count =" + count++);
    22         }
    23     }
    24 
    25     //锁当前对象
    26     public synchronized void testSyn3() {
    27         System.out.println(Thread.currentThread().getName()
    28                 +" count =" + count++);
    29     }
    30 }

      2)如果在加锁的时候对当前对象的访问限定要求比较低的时候,建议锁某一段代码或者某一个对象;如果访问限定要求比较高的话,建议锁当前对象。简单而言就可以说是减小锁的范围。对于锁当前对象或者都是重量级锁,什么意思呢,“就是任意多个线程,多个资源不会被多个线程访问所影响的”

      3)再看下面的例子,锁当前类的类对象的两种方式:

     1 public class TestSynchronized02 {
     2     private static int staticCount = 0;
     3 
     4     //静态同步方法,锁的是当前类型的类对象(即TestSynchronized02.class)
     5     public static synchronized void testSyn1() {
     6         System.out.println(Thread.currentThread().getName()
     7                 +" staticCount =" + staticCount++);
     8     }
     9 
    10     //下面的这种方式也是锁当前类型的类对象
    11     public static void testSyn2() {
    12         synchronized (TestSynchronized02.class) {
    13             System.out.println(Thread.currentThread().getName()
    14                     +" staticCount =" + staticCount++);
    15         }
    16     }
    17 }

      4)看一下下面一段小程序的运行结果

     1 public class TestSynchronized03 implements Runnable{
     2     private int count  = 0;
     3 
     4     @Override
     5     public /*synchronized */ void run() {
     6         System.out.println(Thread.currentThread().getName()
     7                 +" count =" + count++);
     8     }
     9 
    10     public static void main(String[] args) {
    11         TestSynchronized03 testSynchronized03 = new TestSynchronized03();
    12         for (int i = 0; i < 10 ; i++) {
    13             new Thread(testSynchronized03, "Thread --- " + i).start();
    14         }
    15     }
    16 }

      我们发下下面的结果少加了一个1,这就是原子性的问题。在synchronized关键字没有使用的时候,对于变量count而言(由多个线程访问),是不能保证原子性(某一段代码从开始运行到结束不能分步执行)的,上面的代码没有使用同步,那么很显然多线程对变量进行加操作就可能会在同一时刻只进行1次加操作

      

      5)关于同步方法和非同步方法:同步方法只影响  锁定同一个锁对象的同步方法,不影响非同步方法被其他线程调用,也不影响其他所资源的同步方法(简单理解就是锁的不是同一个资源,就不会影响);

     1 package cn.test.juc;
     2 
     3 public class TestSynchronized04 {
     4 
     5     private Object o = new Object();
     6 
     7     //同步方法
     8     public synchronized void m1() {
     9         System.out.println("public synchronized void m1() start.");
    10 
    11         try {
    12             Thread.sleep(3000);
    13         } catch (InterruptedException e) {
    14             e.printStackTrace();
    15         }
    16 
    17         System.out.println("public synchronized void m1() end.");
    18     }
    19 
    20     public void m3() {
    21         synchronized (o) {
    22             System.out.println("public void m3() start.");
    23             try {
    24                 Thread.sleep(1500);
    25             } catch (InterruptedException e) {
    26                 e.printStackTrace();
    27             }
    28             System.out.println("public void m3() end.");
    29         }
    30     }
    31 
    32     //非同步方法
    33     public void m2() {
    34         System.out.println("public void m2() start.");
    35         try {
    36             Thread.sleep(1500);
    37         } catch (InterruptedException e) {
    38             e.printStackTrace();
    39         }
    40         System.out.println("public void m2() end.");
    41     }
    42 
    43     public static class MyThread implements Runnable{
    44         int i;
    45         TestSynchronized04 testSynchronized04;
    46         public MyThread(int i, TestSynchronized04 testSynchronized04) {
    47             this.i = i;
    48             this.testSynchronized04 = testSynchronized04;
    49         }
    50 
    51         @Override
    52         public void run() {
    53             if(i == 0) {
    54                 testSynchronized04.m1();
    55             } else if(i == 1) {
    56                 testSynchronized04.m3();
    57             } else {
    58                 testSynchronized04.m2();
    59             }
    60         }
    61     }
    62 
    63     public static void main(String[] args) {
    64         TestSynchronized04 testSynchronized04 = new TestSynchronized04();
    65         new Thread(new TestSynchronized04.MyThread(0, testSynchronized04)).start();
    66         new Thread(new TestSynchronized04.MyThread(1, testSynchronized04)).start();
    67         new Thread(new TestSynchronized04.MyThread(2, testSynchronized04)).start();
    68     }
    69 }  

      下面是运行的结果

      

      6)脏读问题

     1 package cn.test.juc;
     2 
     3 import java.util.concurrent.TimeUnit;
     4 
     5 public class TestSynchronized05 {
     6     private double d = 0.0;
     7 
     8     //相当与是set方法
     9     public synchronized void m1(double d) {
    10         try {
    11             TimeUnit.SECONDS.sleep(2);
    12         } catch (InterruptedException e) {
    13             e.printStackTrace();
    14         }
    15         this.d = d;
    16     }
    17 
    18     //相当于是get方法
    19     public double m2() {
    20         return this.d;
    21     }
    22 
    23     public static void main(String[] args) {
    24         final TestSynchronized05 testSynchronized05 = new TestSynchronized05();
    25 
    26         new Thread(new Runnable() {
    27             @Override
    28             public void run() {
    29                 testSynchronized05.m1(100);
    30             }
    31         }).start();
    32 
    33         System.out.println(testSynchronized05.m2());
    34         try {
    35             TimeUnit.SECONDS.sleep(3);
    36         } catch (InterruptedException e) {
    37             e.printStackTrace();
    38         }
    39         System.out.println(testSynchronized05.m2());
    40     }
    41 }

      上面代码的输出是0.0  100.00,而不是期望的100.00  100.00,出现这种情况(脏读)的原因是什么的?就是m1方法的这段代码引起的

      

      这段代码表示睡眠2秒之后再进行set操作,使用这一段代码的原因就是模拟实际当中的复杂处理操作,可能会比较耗时,但是这时候还没执行完毕没有将正确的结果写会,别的线程就去访问临界资源的话,就会出现脏读的情况。

      7)锁的可重入问题:同一个线程,多次调用同步代码,锁定同一个对象,可重入

      看看下面的代码实例:main调用m1方法,m1方法中调用m2方法,两个方法锁定的都是this对象,就会是上面说到的这种情况

     1 package cn.test.juc;
     2 
     3 import java.util.concurrent.TimeUnit;
     4 
     5 public class TestSynchronized06 {
     6 
     7     synchronized void m1() { //锁this
     8         System.out.println("m1 start()");
     9         try {
    10             TimeUnit.SECONDS.sleep(2);
    11         } catch (InterruptedException e) {
    12             e.printStackTrace();
    13         }
    14         m2();
    15         System.out.println("m1 end()");
    16     }
    17 
    18     synchronized void m2() { //锁this
    19         System.out.println("m2 start()");
    20         try {
    21             TimeUnit.SECONDS.sleep(1);
    22         } catch (InterruptedException e) {
    23             e.printStackTrace();
    24         }
    25         System.out.println("m2 end()");
    26     }
    27 
    28     public static void main(String[] args) {
    29         new TestSynchronized06().m1();
    30     }
    31 }

      8)关于同步的继承问题:同一个线程中,子类同步方法覆盖父类的同步方法,可以指定调用父类的同步方法(相当于锁的重入);

     1 package cn.test.juc;
     2 
     3 import java.util.concurrent.TimeUnit;
     4 
     5 public class TestSynchronized07 {
     6     synchronized void m() {
     7         System.out.println("Super Class m start");
     8         try {
     9             TimeUnit.SECONDS.sleep(1);
    10         } catch (InterruptedException e) {
    11             e.printStackTrace();
    12         }
    13         System.out.println("Super Class m end");
    14     }
    15 
    16     public static void main(String[] args) {
    17         new ExtendTest07().m();
    18     }
    19 }
    20 
    21 class ExtendTest07 extends TestSynchronized07 {
    22     synchronized void m() {
    23         System.out.println("Sub Class m start");
    24         super.m();
    25         System.out.println("Sub Class m end");
    26     }
    27 }

      9)锁与异常:当同步方法出现异常的时候会自动释放锁,不会影响其他线程的执行

     1 package cn.test.juc;
     2 
     3 import java.util.concurrent.TimeUnit;
     4 
     5 public class TestSynchronized08 {
     6     int i = 0;
     7     synchronized void m(){
     8         System.out.println(Thread.currentThread().getName() + " - start");
     9         while(true){
    10             i++;
    11             System.out.println(Thread.currentThread().getName() + " - " + i);
    12             try {
    13                 TimeUnit.SECONDS.sleep(1);
    14             } catch (InterruptedException e) {
    15                 // TODO Auto-generated catch block
    16                 e.printStackTrace();
    17             }
    18             if(i == 5){
    19                 i = 1/0;
    20             }
    21         }
    22     }
    23 
    24     public static void main(String[] args) {
    25         final TestSynchronized08 t = new TestSynchronized08();
    26         new Thread(new Runnable() {
    27             @Override
    28             public void run() {
    29                 t.m();
    30             }
    31         }, "t1").start();
    32 
    33         new Thread(new Runnable() {
    34             @Override
    35             public void run() {
    36                 t.m();
    37             }
    38         }, "t2").start();
    39     }
    40 }

      下面是输出的结果:

      

      10)synchronized锁的是对象,而不是引用:同步代码一旦加锁之后会有一个临时锁引用执行锁对象,和真实的引用无直接关联,在锁释放之前,修改锁对象引用不会影响同步代码块的执行

     1 package cn.test.syn;
     2 
     3 import java.util.concurrent.TimeUnit;
     4 
     5 public class TestSynchronized09 {
     6     Object o = new Object();
     7 
     8     int i = 0;
     9     int a(){
    10         try{
    11             /*
    12              * return i ->
    13              * int _returnValue = i; // 0;
    14              * return _returnValue;
    15              */
    16             return i;
    17         }finally{
    18             i = 10;
    19         }
    20     }
    21 
    22     void m(){
    23         System.out.println(Thread.currentThread().getName() + " start");
    24         synchronized (o) {
    25             while(true){
    26                 try {
    27                     TimeUnit.SECONDS.sleep(1);
    28                 } catch (InterruptedException e) {
    29                     e.printStackTrace();
    30                 }
    31                 System.out.println(Thread.currentThread().getName() + " - " + o);
    32             }
    33         }
    34     }
    35 
    36     public static void main(String[] args) {
    37         final TestSynchronized09 t = new TestSynchronized09();
    38         new Thread(new Runnable() {
    39             @Override
    40             public void run() {
    41                 t.m();
    42             }
    43         }, "thread1").start();
    44         try {
    45             TimeUnit.SECONDS.sleep(3);
    46         } catch (InterruptedException e) {
    47             e.printStackTrace();
    48         }
    49         Thread thread2 = new Thread(new Runnable() {
    50             @Override
    51             public void run() {
    52                 t.m();
    53             }
    54         }, "thread2");
    55         t.o = new Object();
    56         thread2.start();
    57 
    58         System.out.println(t.i);
    59         System.out.println(t.a());
    60         System.out.println(t.i);
    61     }
    62 }

      11)synchronized中的常量问题:在定义同步代码块的时候,不要使用常量对象作为锁对象

     1 package cn.test.syn;
     2 
     3 import java.util.concurrent.TimeUnit;
     4 
     5 public class TestSynchronized09 {
     6     Object o = new Object();
     7 
     8     int i = 0;
     9     int a(){
    10         try{
    11             /*
    12              * return i ->
    13              * int _returnValue = i; // 0;
    14              * return _returnValue;
    15              */
    16             return i;
    17         }finally{
    18             i = 10;
    19         }
    20     }
    21 
    22     void m(){
    23         System.out.println(Thread.currentThread().getName() + " start");
    24         synchronized (o) {
    25             while(true){
    26                 try {
    27                     TimeUnit.SECONDS.sleep(1);
    28                 } catch (InterruptedException e) {
    29                     e.printStackTrace();
    30                 }
    31                 System.out.println(Thread.currentThread().getName() + " - " + o);
    32             }
    33         }
    34     }
    35 
    36     public static void main(String[] args) {
    37         final TestSynchronized09 t = new TestSynchronized09();
    38         new Thread(new Runnable() {
    39             @Override
    40             public void run() {
    41                 t.m();
    42             }
    43         }, "thread1").start();
    44         try {
    45             TimeUnit.SECONDS.sleep(3);
    46         } catch (InterruptedException e) {
    47             e.printStackTrace();
    48         }
    49         Thread thread2 = new Thread(new Runnable() {
    50             @Override
    51             public void run() {
    52                 t.m();
    53             }
    54         }, "thread2");
    55         t.o = new Object();
    56         thread2.start();
    57 
    58         System.out.println(t.i);
    59         System.out.println(t.a());
    60         System.out.println(t.i);
    61     }
    62 }

    二、Volatile关键字

       1、下面的代码在没有使用volatile之前,是不会从循环中跳出的(main线程和新创建的线程互相之间是不可见的,所以新创建的线程在使用m方法的时候并不知道main线程已经改变了b的值,所以不会跳出循环),那么使用volatile会怎样呢(简单说是可见性)但是啥是可见性:当某个线程正在使用对象状态,而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。

     1 package cn.test.Volatile;
     2 
     3 import java.util.concurrent.TimeUnit;
     4 
     5 public class TestVolatile01 {
     6     /*volatile*/ boolean b = true;
     7 
     8     void m(){
     9         System.out.println("start");
    10         while(b){}
    11         System.out.println("end");
    12     }
    13 
    14     public static void main(String[] args) {
    15         final TestVolatile01 t = new TestVolatile01();
    16         new Thread(new Runnable() {
    17             @Override
    18             public void run() {
    19                 t.m();
    20             }
    21         }).start();
    22 
    23         try {
    24             TimeUnit.SECONDS.sleep(1);
    25         } catch (InterruptedException e) {
    26             e.printStackTrace();
    27         }
    28         t.b = false;
    29     }
    30 }

      2、volatile只能保证可见性,不能保证原子性,volatile不是加锁问题,只是保证内存数据可见;参照下面的例子,运行的结果不是期望的100000,而是

    当然,也不一定每次都是这个值。

     1 package cn.test.Volatile;
     2 
     3 import java.util.ArrayList;
     4 import java.util.List;
     5 
     6 public class TestVolatile02 {
     7     volatile int count = 0;
     8     /*synchronized*/ void m(){
     9         for(int i = 0; i < 10000; i++){
    10             count++;
    11         }
    12     }
    13 
    14     public static void main(String[] args) {
    15         final TestVolatile02 t = new TestVolatile02();
    16         List<Thread> threads = new ArrayList<>();
    17         for(int i = 0; i < 10; i++){
    18             threads.add(new Thread(new Runnable() {
    19                 @Override
    20                 public void run() {
    21                     t.m();
    22                 }
    23             }));
    24         }
    25         for(Thread thread : threads){
    26             thread.start();
    27         }
    28         for(Thread thread : threads){
    29             try {
    30                 thread.join();
    31             } catch (InterruptedException e) {
    32                 // TODO Auto-generated catch block
    33                 e.printStackTrace();
    34             }
    35         }
    36         System.out.println(t.count);
    37     }
    38 }

     三、AtomicXXX

      Atomic主要做的就是原子操作,其中的每个方法都是原子操作,可以保证线程安全。参照下面的例子:创建十个线程,每个线程累加100次,得到的结果就是1000

     1 package cn.test.atomic;
     2 
     3 import java.util.ArrayList;
     4 import java.util.List;
     5 import java.util.concurrent.atomic.AtomicInteger;
     6 
     7 public class TestAtomic01 {
     8     AtomicInteger count = new AtomicInteger(0);
     9     void m1(){
    10         for(int i = 0; i < 100; i++){
    11             /*if(count.get() < 1000)*/
    12             count.incrementAndGet();
    13         }
    14     }
    15 
    16     public static void main(String[] args) {
    17         final TestAtomic01 t = new TestAtomic01();
    18         List<Thread> threads = new ArrayList<>();
    19         for(int i = 0; i < 10; i++){
    20             threads.add(new Thread(new Runnable() {
    21                 @Override
    22                 public void run() {
    23                     t.m1();
    24                 }
    25             }));
    26         }
    27         for(Thread thread : threads){
    28             thread.start();
    29         }
    30         for(Thread thread : threads){
    31             try {
    32                 thread.join();
    33             } catch (InterruptedException e) {
    34                 // TODO Auto-generated catch block
    35                 e.printStackTrace();
    36             }
    37         }
    38         System.out.println(t.count.intValue());
    39     }
    40 }

     四、CountDownLatch

     1 package cn.test.syn;
     2 /**
     3  * 门闩 - CountDownLatch
     4  * 可以和锁混合使用,或替代锁的功能。
     5  * 在门闩未完全开放之前等待。当门闩完全开放后执行。
     6  * 避免锁的效率低下问题。
     7  */
     8 import java.util.concurrent.CountDownLatch;
     9 import java.util.concurrent.TimeUnit;
    10 
    11 public class Test {
    12     CountDownLatch latch = new CountDownLatch(5);
    13 
    14     void m1(){
    15         try {
    16             latch.await();// 等待门闩开放。
    17         } catch (InterruptedException e) {
    18             e.printStackTrace();
    19         }
    20         System.out.println("m1() method");
    21     }
    22 
    23     void m2(){
    24         for(int i = 0; i < 10; i++){
    25             if(latch.getCount() != 0){
    26                 System.out.println("latch count : " + latch.getCount());
    27                 latch.countDown(); // 减门闩上的锁。
    28             }
    29             try {
    30                 TimeUnit.MILLISECONDS.sleep(500);
    31             } catch (InterruptedException e) {
    32                 // TODO Auto-generated catch block
    33                 e.printStackTrace();
    34             }
    35             System.out.println("m2() method : " + i);
    36         }
    37     }
    38 
    39     public static void main(String[] args) {
    40         final Test t = new Test();
    41         new Thread(new Runnable() {
    42             @Override
    43             public void run() {
    44                 t.m1();
    45             }
    46         }).start();
    47 
    48         new Thread(new Runnable() {
    49             @Override
    50             public void run() {
    51                 t.m2();
    52             }
    53         }).start();
    54     }
    55 }
  • 相关阅读:
    IP应用加速技术详解:如何提升动静混合站点的访问速率?
    阿里云PolarDB发布重大更新 支持Oracle等数据库一键迁移上云
    BigData NoSQL —— ApsaraDB HBase数据存储与分析平台概览
    洛谷P1457 城堡 The Castle
    洛谷P1461 海明码 Hamming Codes
    洛谷P1460 健康的荷斯坦奶牛 Healthy Holsteins
    洛谷P1459 三值的排序 Sorting a Three-Valued Sequence
    洛谷P1458 顺序的分数 Ordered Fractions
    洛谷P1218 [USACO1.5]特殊的质数肋骨 Superprime Rib
    洛谷P1215 [USACO1.4]母亲的牛奶 Mother's Milk
  • 原文地址:https://www.cnblogs.com/fsmly/p/10448035.html
Copyright © 2011-2022 走看看