zoukankan      html  css  js  c++  java
  • java 多线程系列基础篇(四)之 synchronized关键字

    1. synchronized原理

    在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。
    当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj)就获取了“obj这个对象”的同步锁。
    不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。

    2. synchronized基本规则

    我们将synchronized的基本规则总结为下面3条,并通过实例对它们进行说明。
    第一条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
    第二条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块
    第三条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

    第一条

    当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
    下面是“synchronized代码块”对应的演示程序。

    复制代码
     1 class MyRunable implements Runnable {
     2     
     3     @Override
     4     public void run() {
     5         synchronized(this) {
     6             try {  
     7                 for (int i = 0; i < 5; i++) {
     8                     Thread.sleep(100); // 休眠100ms
     9                     System.out.println(Thread.currentThread().getName() + " loop " + i);  
    10                 }
    11             } catch (InterruptedException ie) {  
    12             }
    13         }  
    14     }
    15 }
    16 
    17 public class Demo1_1 {
    18 
    19     public static void main(String[] args) {  
    20         Runnable demo = new MyRunable();     // 新建“Runnable对象”
    21 
    22         Thread t1 = new Thread(demo, "t1");  // 新建“线程t1”, t1是基于demo这个Runnable对象
    23         Thread t2 = new Thread(demo, "t2");  // 新建“线程t2”, t2是基于demo这个Runnable对象
    24         t1.start();                          // 启动“线程t1”
    25         t2.start();                          // 启动“线程t2” 
    26     } 
    27 }
    复制代码

    运行结果

    复制代码
    t1 loop 0
    t1 loop 1
    t1 loop 2
    t1 loop 3
    t1 loop 4
    t2 loop 0
    t2 loop 1
    t2 loop 2
    t2 loop 3
    t2 loop 4
    复制代码

    结果说明
    run()方法中存在“synchronized(this)代码块”,而且t1和t2都是基于"demo这个Runnable对象"创建的线程。这就意味着,我们可以将synchronized(this)中的this看作是“demo这个Runnable对象”;因此,线程t1和t2共享“demo对象的同步锁”。所以,当一个线程运行的时候,另外一个线程必须等待“运行线程”释放“demo的同步锁”之后才能运行。

    如果你确认,你搞清楚这个问题了。那我们将上面的代码进行修改,然后再运行看看结果怎么样,看看你是否会迷糊。修改后的源码如下:

    复制代码
     1 class MyThread extends Thread {
     2     
     3     public MyThread(String name) {
     4         super(name);
     5     }
     6 
     7     @Override
     8     public void run() {
     9         synchronized(this) {
    10             try {  
    11                 for (int i = 0; i < 5; i++) {
    12                     Thread.sleep(100); // 休眠100ms
    13                     System.out.println(Thread.currentThread().getName() + " loop " + i);  
    14                 }
    15             } catch (InterruptedException ie) {  
    16             }
    17         }  
    18     }
    19 }
    20 
    21 public class Demo1_2 {
    22 
    23     public static void main(String[] args) {  
    24         Thread t1 = new MyThread("t1");  // 新建“线程t1”
    25         Thread t2 = new MyThread("t2");  // 新建“线程t2”
    26         t1.start();                          // 启动“线程t1”
    27         t2.start();                          // 启动“线程t2” 
    28     } 
    29 }
    复制代码

    代码说明
    比较Demo1_2 和 Demo1_1,我们发现,Demo1_2中的MyThread类是直接继承于Thread,而且t1和t2都是MyThread实例。
    幸运的是,在“Demo1_2的run()方法”也调用了synchronized(this),正如“Demo1_1的run()方法”也调用了synchronized(this)一样!
    那么,Demo1_2的执行流程是不是和Demo1_1一样呢?
    运行结果:

    复制代码
    t1 loop 0
    t2 loop 0
    t1 loop 1
    t2 loop 1
    t1 loop 2
    t2 loop 2
    t1 loop 3
    t2 loop 3
    t1 loop 4
    t2 loop 4
    复制代码

    结果说明
    如果这个结果一点也不令你感到惊讶,那么我相信你对synchronized和this的认识已经比较深刻了。否则的话,请继续阅读这里的分析。
    synchronized(this)中的this是指“当前的类对象”,即synchronized(this)所在的类对应的当前对象。它的作用是获取“当前对象的同步锁”。
    对于Demo1_2中,synchronized(this)中的this代表的是MyThread对象,而t1和t2是两个不同的MyThread对象,因此t1和t2在执行synchronized(this)时,获取的是不同对象的同步锁。对于Demo1_1对而言,synchronized(this)中的this代表的是MyRunable对象;t1和t2共同一个MyRunable对象,因此,一个线程获取了对象的同步锁,会造成另外一个线程等待。

    第二条

    当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
    下面是“synchronized代码块”对应的演示程序。

    复制代码
     1 class Count {
     2 
     3     // 含有synchronized同步块的方法
     4     public void synMethod() {
     5         synchronized(this) {
     6             try {  
     7                 for (int i = 0; i < 5; i++) {
     8                     Thread.sleep(100); // 休眠100ms
     9                     System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);  
    10                 }
    11             } catch (InterruptedException ie) {  
    12             }
    13         }  
    14     }
    15 
    16     // 非同步的方法
    17     public void nonSynMethod() {
    18         try {  
    19             for (int i = 0; i < 5; i++) {
    20                 Thread.sleep(100);
    21                 System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);  
    22             }
    23         } catch (InterruptedException ie) {  
    24         }
    25     }
    26 }
    27 
    28 public class Demo2 {
    29 
    30     public static void main(String[] args) {  
    31         final Count count = new Count();
    32         // 新建t1, t1会调用“count对象”的synMethod()方法
    33         Thread t1 = new Thread(
    34                 new Runnable() {
    35                     @Override
    36                     public void run() {
    37                         count.synMethod();
    38                     }
    39                 }, "t1");
    40 
    41         // 新建t2, t2会调用“count对象”的nonSynMethod()方法
    42         Thread t2 = new Thread(
    43                 new Runnable() {
    44                     @Override
    45                     public void run() {
    46                         count.nonSynMethod();
    47                     }
    48                 }, "t2");  
    49 
    50 
    51         t1.start();  // 启动t1
    52         t2.start();  // 启动t2
    53     } 
    54 }
    复制代码

    运行结果

    复制代码
    t1 synMethod loop 0
    t2 nonSynMethod loop 0
    t1 synMethod loop 1
    t2 nonSynMethod loop 1
    t1 synMethod loop 2
    t2 nonSynMethod loop 2
    t1 synMethod loop 3
    t2 nonSynMethod loop 3
    t1 synMethod loop 4
    t2 nonSynMethod loop 4
    复制代码

    结果说明
    主线程中新建了两个子线程t1和t2。t1会调用count对象的synMethod()方法,该方法内含有同步块;而t2则会调用count对象的nonSynMethod()方法,该方法不是同步方法。t1运行时,虽然调用synchronized(this)获取“count的同步锁”;但是并没有造成t2的阻塞,因为t2没有用到“count”同步锁。

    第三条

    当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
    我们将上面的例子中的nonSynMethod()方法体的也用synchronized(this)修饰。修改后的源码如下:

    复制代码
     1 class Count {
     2 
     3     // 含有synchronized同步块的方法
     4     public void synMethod() {
     5         synchronized(this) {
     6             try {  
     7                 for (int i = 0; i < 5; i++) {
     8                     Thread.sleep(100); // 休眠100ms
     9                     System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);  
    10                 }
    11             } catch (InterruptedException ie) {  
    12             }
    13         }  
    14     }
    15 
    16     // 也包含synchronized同步块的方法
    17     public void nonSynMethod() {
    18         synchronized(this) {
    19             try {  
    20                 for (int i = 0; i < 5; i++) {
    21                     Thread.sleep(100);
    22                     System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);  
    23                 }
    24             } catch (InterruptedException ie) {  
    25             }
    26         }
    27     }
    28 }
    29 
    30 public class Demo3 {
    31 
    32     public static void main(String[] args) {  
    33         final Count count = new Count();
    34         // 新建t1, t1会调用“count对象”的synMethod()方法
    35         Thread t1 = new Thread(
    36                 new Runnable() {
    37                     @Override
    38                     public void run() {
    39                         count.synMethod();
    40                     }
    41                 }, "t1");
    42 
    43         // 新建t2, t2会调用“count对象”的nonSynMethod()方法
    44         Thread t2 = new Thread(
    45                 new Runnable() {
    46                     @Override
    47                     public void run() {
    48                         count.nonSynMethod();
    49                     }
    50                 }, "t2");  
    51 
    52 
    53         t1.start();  // 启动t1
    54         t2.start();  // 启动t2
    55     } 
    56 }
    复制代码

    运行结果

    复制代码
    t1 synMethod loop 0
    t1 synMethod loop 1
    t1 synMethod loop 2
    t1 synMethod loop 3
    t1 synMethod loop 4
    t2 nonSynMethod loop 0
    t2 nonSynMethod loop 1
    t2 nonSynMethod loop 2
    t2 nonSynMethod loop 3
    t2 nonSynMethod loop 4
    复制代码

    结果说明
    主线程中新建了两个子线程t1和t2。t1和t2运行时都调用synchronized(this),这个this是Count对象(count),而t1和t2共用count。因此,在t1运行时,t2会被阻塞,等待t1运行释放“count对象的同步锁”,t2才能运行。

    3. synchronized方法 和 synchronized代码块

    “synchronized方法”是用synchronized修饰方法,而 “synchronized代码块”则是用synchronized修饰代码块。

    synchronized方法示例

    public synchronized void foo1() {
        System.out.println("synchronized methoed");
    }

    synchronized代码块

    public void foo2() {
        synchronized (this) {
            System.out.println("synchronized methoed");
        }
    }

    synchronized代码块中的this是指当前对象。也可以将this替换成其他对象,例如将this替换成obj,则foo2()在执行synchronized(obj)时就获取的是obj的同步锁。


    synchronized代码块可以更精确的控制冲突限制访问区域,有时候表现更高效率。下面通过一个示例来演示:

    复制代码
     1 // Demo4.java的源码
     2 public class Demo4 {
     3 
     4     public synchronized void synMethod() {
     5         for(int i=0; i<1000000; i++)
     6             ;
     7     }
     8 
     9     public void synBlock() {
    10         synchronized( this ) {
    11             for(int i=0; i<1000000; i++)
    12                 ;
    13         }
    14     }
    15 
    16     public static void main(String[] args) {
    17         Demo4 demo = new Demo4();
    18 
    19         long start, diff;
    20         start = System.currentTimeMillis();                // 获取当前时间(millis)
    21         demo.synMethod();                                // 调用“synchronized方法”
    22         diff = System.currentTimeMillis() - start;        // 获取“时间差值”
    23         System.out.println("synMethod() : "+ diff);
    24         
    25         start = System.currentTimeMillis();                // 获取当前时间(millis)
    26         demo.synBlock();                                // 调用“synchronized方法块”
    27         diff = System.currentTimeMillis() - start;        // 获取“时间差值”
    28         System.out.println("synBlock()  : "+ diff);
    29     }
    30 }
    复制代码

    (某一次)执行结果

    synMethod() : 11
    synBlock() : 3

    4. 实例锁 和 全局锁

    实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。
                   实例锁对应的就是synchronized关键字。
    全局锁 -- 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。
                   全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。

    关于“实例锁”和“全局锁”有一个很形象的例子:

    pulbic class Something {
        public synchronized void isSyncA(){}
        public synchronized void isSyncB(){}
        public static synchronized void cSyncA(){}
        public static synchronized void cSyncB(){}
    }

    假设,Something有两个实例x和y。分析下面4组表达式获取的锁的情况。
    (01) x.isSyncA()与x.isSyncB() 
    (02) x.isSyncA()与y.isSyncA()
    (03) x.cSyncA()与y.cSyncB()
    (04) x.isSyncA()与Something.cSyncA()

    (01) 不能被同时访问。因为isSyncA()和isSyncB()都是访问同一个对象(对象x)的同步锁!

    复制代码
     1 // LockTest1.java的源码
     2 class Something {
     3     public synchronized void isSyncA(){
     4         try {  
     5             for (int i = 0; i < 5; i++) {
     6                 Thread.sleep(100); // 休眠100ms
     7                 System.out.println(Thread.currentThread().getName()+" : isSyncA");
     8             }
     9         }catch (InterruptedException ie) {  
    10         }  
    11     }
    12     public synchronized void isSyncB(){
    13         try {  
    14             for (int i = 0; i < 5; i++) {
    15                 Thread.sleep(100); // 休眠100ms
    16                 System.out.println(Thread.currentThread().getName()+" : isSyncB");
    17             }
    18         }catch (InterruptedException ie) {  
    19         }  
    20     }
    21 }
    22 
    23 public class LockTest1 {
    24 
    25     Something x = new Something();
    26     Something y = new Something();
    27 
    28     // 比较(01) x.isSyncA()与x.isSyncB() 
    29     private void test1() {
    30         // 新建t11, t11会调用 x.isSyncA()
    31         Thread t11 = new Thread(
    32                 new Runnable() {
    33                     @Override
    34                     public void run() {
    35                         x.isSyncA();
    36                     }
    37                 }, "t11");
    38 
    39         // 新建t12, t12会调用 x.isSyncB()
    40         Thread t12 = new Thread(
    41                 new Runnable() {
    42                     @Override
    43                     public void run() {
    44                         x.isSyncB();
    45                     }
    46                 }, "t12");  
    47 
    48 
    49         t11.start();  // 启动t11
    50         t12.start();  // 启动t12
    51     }
    52 
    53     public static void main(String[] args) {
    54         LockTest1 demo = new LockTest1();
    55         demo.test1();
    56     }
    57 }
    复制代码

    运行结果

    复制代码
    t11 : isSyncA
    t11 : isSyncA
    t11 : isSyncA
    t11 : isSyncA
    t11 : isSyncA
    t12 : isSyncB
    t12 : isSyncB
    t12 : isSyncB
    t12 : isSyncB
    t12 : isSyncB
    复制代码

    (02) 可以同时被访问。因为访问的不是同一个对象的同步锁,x.isSyncA()访问的是x的同步锁,而y.isSyncA()访问的是y的同步锁。

    复制代码
     1 // LockTest2.java的源码
     2 class Something {
     3     public synchronized void isSyncA(){
     4         try {  
     5             for (int i = 0; i < 5; i++) {
     6                 Thread.sleep(100); // 休眠100ms
     7                 System.out.println(Thread.currentThread().getName()+" : isSyncA");
     8             }
     9         }catch (InterruptedException ie) {  
    10         }  
    11     }
    12     public synchronized void isSyncB(){
    13         try {  
    14             for (int i = 0; i < 5; i++) {
    15                 Thread.sleep(100); // 休眠100ms
    16                 System.out.println(Thread.currentThread().getName()+" : isSyncB");
    17             }
    18         }catch (InterruptedException ie) {  
    19         }  
    20     }
    21     public static synchronized void cSyncA(){
    22         try {  
    23             for (int i = 0; i < 5; i++) {
    24                 Thread.sleep(100); // 休眠100ms
    25                 System.out.println(Thread.currentThread().getName()+" : cSyncA");
    26             } 
    27         }catch (InterruptedException ie) {  
    28         }  
    29     }
    30     public static synchronized void cSyncB(){
    31         try {  
    32             for (int i = 0; i < 5; i++) {
    33                 Thread.sleep(100); // 休眠100ms
    34                 System.out.println(Thread.currentThread().getName()+" : cSyncB");
    35             } 
    36         }catch (InterruptedException ie) {  
    37         }  
    38     }
    39 }
    40 
    41 public class LockTest2 {
    42 
    43     Something x = new Something();
    44     Something y = new Something();
    45 
    46     // 比较(02) x.isSyncA()与y.isSyncA()
    47     private void test2() {
    48         // 新建t21, t21会调用 x.isSyncA()
    49         Thread t21 = new Thread(
    50                 new Runnable() {
    51                     @Override
    52                     public void run() {
    53                         x.isSyncA();
    54                     }
    55                 }, "t21");
    56 
    57         // 新建t22, t22会调用 x.isSyncB()
    58         Thread t22 = new Thread(
    59                 new Runnable() {
    60                     @Override
    61                     public void run() {
    62                         y.isSyncA();
    63                     }
    64                 }, "t22");  
    65 
    66 
    67         t21.start();  // 启动t21
    68         t22.start();  // 启动t22
    69     }
    70 
    71     public static void main(String[] args) {
    72         LockTest2 demo = new LockTest2();
    73 
    74         demo.test2();
    75     }
    76 }
    复制代码

    运行结果

    复制代码
    t21 : isSyncA
    t22 : isSyncA
    t21 : isSyncA
    t22 : isSyncA
    t21 : isSyncA
    t22 : isSyncA
    t21 : isSyncA
    t22 : isSyncA
    t21 : isSyncA
    t22 : isSyncA
    复制代码

    (03) 不能被同时访问。因为cSyncA()和cSyncB()都是static类型,x.cSyncA()相当于Something.isSyncA(),y.cSyncB()相当于Something.isSyncB(),因此它们共用一个同步锁,不能被同时访问。

    复制代码
     1 // LockTest3.java的源码
     2 class Something {
     3     public synchronized void isSyncA(){
     4         try {  
     5             for (int i = 0; i < 5; i++) {
     6                 Thread.sleep(100); // 休眠100ms
     7                 System.out.println(Thread.currentThread().getName()+" : isSyncA");
     8             }
     9         }catch (InterruptedException ie) {  
    10         }  
    11     }
    12     public synchronized void isSyncB(){
    13         try {  
    14             for (int i = 0; i < 5; i++) {
    15                 Thread.sleep(100); // 休眠100ms
    16                 System.out.println(Thread.currentThread().getName()+" : isSyncB");
    17             }
    18         }catch (InterruptedException ie) {  
    19         }  
    20     }
    21     public static synchronized void cSyncA(){
    22         try {  
    23             for (int i = 0; i < 5; i++) {
    24                 Thread.sleep(100); // 休眠100ms
    25                 System.out.println(Thread.currentThread().getName()+" : cSyncA");
    26             } 
    27         }catch (InterruptedException ie) {  
    28         }  
    29     }
    30     public static synchronized void cSyncB(){
    31         try {  
    32             for (int i = 0; i < 5; i++) {
    33                 Thread.sleep(100); // 休眠100ms
    34                 System.out.println(Thread.currentThread().getName()+" : cSyncB");
    35             } 
    36         }catch (InterruptedException ie) {  
    37         }  
    38     }
    39 }
    40 
    41 public class LockTest3 {
    42 
    43     Something x = new Something();
    44     Something y = new Something();
    45 
    46     // 比较(03) x.cSyncA()与y.cSyncB()
    47     private void test3() {
    48         // 新建t31, t31会调用 x.isSyncA()
    49         Thread t31 = new Thread(
    50                 new Runnable() {
    51                     @Override
    52                     public void run() {
    53                         x.cSyncA();
    54                     }
    55                 }, "t31");
    56 
    57         // 新建t32, t32会调用 x.isSyncB()
    58         Thread t32 = new Thread(
    59                 new Runnable() {
    60                     @Override
    61                     public void run() {
    62                         y.cSyncB();
    63                     }
    64                 }, "t32");  
    65 
    66 
    67         t31.start();  // 启动t31
    68         t32.start();  // 启动t32
    69     }
    70 
    71     public static void main(String[] args) {
    72         LockTest3 demo = new LockTest3();
    73 
    74         demo.test3();
    75     }
    76 }
    复制代码

    运行结果

    复制代码
    t31 : cSyncA
    t31 : cSyncA
    t31 : cSyncA
    t31 : cSyncA
    t31 : cSyncA
    t32 : cSyncB
    t32 : cSyncB
    t32 : cSyncB
    t32 : cSyncB
    t32 : cSyncB
    复制代码

    (04) 可以被同时访问。因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。

    复制代码
     1 // LockTest4.java的源码
     2 class Something {
     3     public synchronized void isSyncA(){
     4         try {  
     5             for (int i = 0; i < 5; i++) {
     6                 Thread.sleep(100); // 休眠100ms
     7                 System.out.println(Thread.currentThread().getName()+" : isSyncA");
     8             }
     9         }catch (InterruptedException ie) {  
    10         }  
    11     }
    12     public synchronized void isSyncB(){
    13         try {  
    14             for (int i = 0; i < 5; i++) {
    15                 Thread.sleep(100); // 休眠100ms
    16                 System.out.println(Thread.currentThread().getName()+" : isSyncB");
    17             }
    18         }catch (InterruptedException ie) {  
    19         }  
    20     }
    21     public static synchronized void cSyncA(){
    22         try {  
    23             for (int i = 0; i < 5; i++) {
    24                 Thread.sleep(100); // 休眠100ms
    25                 System.out.println(Thread.currentThread().getName()+" : cSyncA");
    26             } 
    27         }catch (InterruptedException ie) {  
    28         }  
    29     }
    30     public static synchronized void cSyncB(){
    31         try {  
    32             for (int i = 0; i < 5; i++) {
    33                 Thread.sleep(100); // 休眠100ms
    34                 System.out.println(Thread.currentThread().getName()+" : cSyncB");
    35             } 
    36         }catch (InterruptedException ie) {  
    37         }  
    38     }
    39 }
    40 
    41 public class LockTest4 {
    42 
    43     Something x = new Something();
    44     Something y = new Something();
    45 
    46     // 比较(04) x.isSyncA()与Something.cSyncA()
    47     private void test4() {
    48         // 新建t41, t41会调用 x.isSyncA()
    49         Thread t41 = new Thread(
    50                 new Runnable() {
    51                     @Override
    52                     public void run() {
    53                         x.isSyncA();
    54                     }
    55                 }, "t41");
    56 
    57         // 新建t42, t42会调用 x.isSyncB()
    58         Thread t42 = new Thread(
    59                 new Runnable() {
    60                     @Override
    61                     public void run() {
    62                         Something.cSyncA();
    63                     }
    64                 }, "t42");  
    65 
    66 
    67         t41.start();  // 启动t41
    68         t42.start();  // 启动t42
    69     }
    70 
    71     public static void main(String[] args) {
    72         LockTest4 demo = new LockTest4();
    73 
    74         demo.test4();
    75     }
    76 }
    复制代码

    运行结果

    复制代码
    t41 : isSyncA
    t42 : cSyncA
    t41 : isSyncA
    t42 : cSyncA
    t41 : isSyncA
    t42 : cSyncA
    t41 : isSyncA
    t42 : cSyncA
    t41 : isSyncA
    t42 : cSyncA
    复制代码

    转载:http://www.cnblogs.com/skywang12345/p/3479202.html

  • 相关阅读:
    hdu 3666 差分约束系统
    hdu 1198农田灌溉
    常微分方程(阿諾爾德) Page 45 相空間,相流,運動,相曲線 註記
    高等微積分(高木貞治) 1.4節 例2
    常微分方程(阿諾爾德) Page 45 相空間,相流,運動,相曲線 註記
    解析函數論 Page 29 命題(2) 函數模的有界性
    高等微積分(高木貞治) 1.4節 例2
    解析函數論 Page 29 命題(1) 有界閉集上的一致連續性
    解析函數論 Page 29 命題(3) 模的下界的可達性
    解析函數論 Page 29 命題(2) 函數模的有界性
  • 原文地址:https://www.cnblogs.com/cainiao-Shun666/p/8145800.html
Copyright © 2011-2022 走看看