zoukankan      html  css  js  c++  java
  • Java中synchronized同步的理解

    在并发访问的问题上,Java引入了同步监视器来应对,主要是通过关键字synchronized实现。关于synchronized,它有两种形式,一种是同步代码块:synchronized(obj){},另一种是同步方法:public synchronized void method1(){},前者比较灵活,可以自己控制同步的范围,而后者同步的是整个方法。

    同步代码块

    synchronized(obj){}

    在上述代码中,对象obj即是同步监视器,代码表示,如果要执行{}中的代码,必须先获得对obj的锁定。该对象可以是任何对象,但是一般情况下推荐使用那个要被并发访问的对象,因为同步监视器的作用就是防止对某资源进行并发访问。比如

     1 package com.hm.thread.test;
     2 
     3 public class Thread111 implements Runnable {
     4     public Object o = new Object();
     5     public void run() {
     6         synchronized (o) {
     7             for (int i = 0; i < 5; i++) {
     8                 System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
     9             }
    10         }
    11     }
    12 
    13     public static void main(String[] args) {
    14         Thread111 t1 = new Thread111();
    15         Thread ta = new Thread(t1, "A");
    16         Thread tb = new Thread(t1, "B");
    17         ta.start();
    18         tb.start();
    19     }
    20 }

    如果不加synchronized (o) {},则输出的A,B是交错的,因为两个线程A,B并发执行,所以打印的结果就是交替输出。而使用了synchronized (o) {}之后,两个线程都要去获取o的锁,那么就变成了同步执行了,A和B分别输出:

    A synchronized loop 0
    A synchronized loop 1
    A synchronized loop 2
    A synchronized loop 3
    A synchronized loop 4
    B synchronized loop 0
    B synchronized loop 1
    B synchronized loop 2
    B synchronized loop 3
    B synchronized loop 4

     同步方法

    public synchronized void method1(){}

    对于同步方法来说,没有显式的指定同步监视器,实际的同步监视器是this,即调用该方法的对象本身。

    这里有一点需要注意:是否是同一个对象在多线程的访问同步方法。

    这里借用一些例子说明,比如:

     1 package com.hm.thread.account;
     2 
     3 class Account {
     4     String name;
     5     float amount;
     6 
     7     public Account(String name, float amount) {
     8         this.name = name;
     9         this.amount = amount;
    10     }
    11 
    12     public synchronized void deposit(float amt) {
    13         float tmp = amount;
    14         tmp += amt;
    15         try {
    16             Thread.sleep(1);// 模拟其它处理所需要的时间,比如刷新数据库等
    17         } catch (InterruptedException e) { }
    18         amount = tmp;
    19     }
    20 
    21     public synchronized void withdraw(float amt) {
    22         float tmp = amount;
    23         tmp -= amt;
    24         try {
    25             Thread.sleep(114);// 模拟其它处理所需要的时间,比如刷新数据库等
    26         } catch (InterruptedException e) { }
    27         amount = tmp;
    28     }
    29 
    30     public float getBalance() {
    31         return amount;
    32     }
    33 }
    34 
    35 public class AccountTest {
    36     private static int NUM_OF_THREAD = 200;
    37     static Thread[] threads = new Thread[NUM_OF_THREAD];
    38 
    39     public static void main(String[] args) {
    40         final Account acc = new Account("John", 1000.0f);
    41         for (int i = 0; i < NUM_OF_THREAD; i++) {
    42             threads[i] = new Thread(new Runnable() {
    43                 public void run() {
    44                     acc.deposit(100.0f);
    45                     acc.withdraw(100.0f);
    46                 }
    47             });
    48             threads[i].start();
    49         }
    50 
    51         for (int i = 0; i < NUM_OF_THREAD; i++) {
    52             try {
    53                 threads[i].join(); // 等待所有线程运行结束
    54             } catch (InterruptedException e) {
    55             }
    56         }
    57         System.out.println("Finally, John's balance is:" + acc.getBalance());
    58     }
    59 
    60 }

    在上面的例子中,模拟账户中有1000元,存取款各100元,各200次,理论上余额应该还是1000,但是对于deposite()和 withdraw()方法,如果去掉synchronized修饰,则结果是不确定的。加上synchronized,则对于同一个account对象,在多个线程中并发访问deposite()和withdraw()方法,这两个方法都变成同步的了,即多个线程被同步了。

    那么换个例子

     1 package com.hm.thread.test;
     2 
     3 public class ClassSync_Test {
     4     public static void main(String[] args) {
     5         Foo f1 = new Foo(1);
     6         Foo f2 = new Foo(3);
     7         f1.start();
     8         f2.start();
     9     }
    10 }
    11 
    12 class Foo extends Thread {
    13     private int val;
    14     public Foo(int val){
    15         this.val = val;
    16     }
    17     
    18     public synchronized void print(int v){
    19             int n = 100;
    20             int i = 0;
    21             while(i < n){
    22                 System.out.println(v);
    23                 i++;
    24                 try {
    25                     Thread.currentThread().sleep(1);
    26                 } catch (InterruptedException e) {
    27                     e.printStackTrace();
    28                 }
    29             }
    30     }
    31     public void run(){
    32         print(val);
    33     }
    34 }

    在这个例子中,print()也是同步方法,但是在main方法中new出了两个Foo对象,分别启动线程执行,可以看到1和3交替出现,说明这个方法同时被两个线程执行了,而并不是同步的,原因在哪?

    在Java中,类本身只有一个实例,但是类产生的对象却可以多个。对于Foo,它存在两个实例对象,启动它们的线程,彼此并不干扰,所以它们各自执行自己的同步方法。换句话说,synchronized虽然锁住了这个方法,但是只是针对同一个对象来说的,不同的对象会获取了各自的锁,彼此没有影响。所以,如果要实现同步,则需要让不同的对象在访问这个方法的时仍然去获取同一把锁,而不是各自获取各自的。即让synchronized去同步一个类级别的对象。

    可以这么修改print():

     1 public void print(int v){
     2         synchronized(Foo.class){
     3             int n = 100;
     4             int i = 0;
     5             while(i < n){
     6                 System.out.println(v);
     7                 i++;
     8                 try {
     9                     Thread.currentThread().sleep(1);
    10                 } catch (InterruptedException e) {
    11                     e.printStackTrace();
    12                 }
    13             }
    14         }
    15     }

    当锁住Foo.class之后,该类对应的对象在访问print方法时都会去获取Foo.class身上的锁--同一把锁。所以实现了同步。或者可以在该类中创建一个全局的静态变量,然后替换掉Foo.class也是可以的,因为静态的变量和类一样,都只有一份(要注意锁住一个class,范围较大,性能不好)。

    另外一方面,对于wait(),notify(),notifyAll()三个方法,它们属于Object类,并且必须由同步监视器调用。所以在同步代码块中,使用obj去调用,obj.wait()。而对于同步方法来说,由于它没有显示指定同步监视器,并且监视器就是this,所以可以直接调用:this.wait()或者wait()。

    引用:

    http://www.shangxueba.com/jingyan/90315.html

    http://www.cnblogs.com/devinzhang/archive/2011/12/14/2287675.html#top

  • 相关阅读:
    【基础】jquery全选、反选、全不选代码
    【基础】jquery全选、反选、全不选代码
    收集一些程序员励志经典名言
    收集一些程序员励志经典名言
    收集一些程序员励志经典名言
    防止表单重复提交的解决方案整理
    Git使用教程
    2019牛客暑期多校训练营(第二场)J Subarray
    Hibernate-配置
    与项目欧拉速度比较:C vs Python与Erlang vs Haskell
  • 原文地址:https://www.cnblogs.com/seguzhizi/p/5145169.html
Copyright © 2011-2022 走看看