首页 > 试题广场 >

同步方法和同步代码块的区别是什么?

[问答题]
同步方法和同步代码块的区别是什么?
为何要使用同步? 
    java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 
    将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 
    从而保证了该变量的唯一性和准确性。
 
 
 
1.同步方法 
    即有synchronized关键字修饰的方法。 
    由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 
    内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
 
 
    代码如: 
    public synchronized void save(){}
 
 
   注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
 
 
 
2.同步代码块 
    即有synchronized关键字修饰的语句块。 
    被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
 
 
    代码如: 
    synchronized(object){ 
    }
 
 
    注:同步是一种高开销的操作,因此应该尽量减少同步的内容。 
    通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。 
     
    代码实例: 
    
 
复制代码
package com.x敏感词hread;
 
    /**
     * 线程同步的运用
     * 
     * @author XIEHEJUN
     * 
     */
    public class SynchronizedThread {
 
        class Bank {
 
            private int account = 100;
 
            public int getAccount() {
                return account;
            }
 
            /**
             * 用同步方法实现
             * 
             * @param money
             */
            public synchronized void save(int money) {
                account += money;
            }
 
            /**
             * 用同步代码块实现
             * 
             * @param money
             */
            public void save1(int money) {
                synchronized (this) {
                    account += money;
                }
            }
        }
 
        class NewThread implements Runnable {
            private Bank bank;
 
            public NewThread(Bank bank) {
                this.bank = bank;
            }
 
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    // bank.save1(10);
                    bank.save(10);
                    System.out.println(i + "账户余额为:" + bank.getAccount());
                }
            }
 
        }
 
        /**
         * 建立线程,调用内部类
         */
        public void useThread() {
            Bank bank = new Bank();
            NewThread new_thread = new NewThread(bank);
            System.out.println("线程1");
            Thread thread1 = new Thread(new_thread);
            thread1.start();
            System.out.println("线程2");
            Thread thread2 = new Thread(new_thread);
            thread2.start();
        }
 
        public static void main(String[] args) {
            SynchronizedThread st = new SynchronizedThread();
            st.useThread();
        }
 
    }
复制代码
     
3.使用特殊域变量(volatile)实现线程同步
 
    a.volatile关键字为域变量的访问提供了一种免锁机制, 
    b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新, 
    c.因此每次使用该域就要重新计算,而不是使用寄存器中的值 
    d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量 
    
    例如: 
        在上面的例子当中,只需在account前面加上volatile修饰,即可实现线程同步。 
    
    代码实例: 
    
 
复制代码
      //只给出要修改的代码,其余代码与上同
        class Bank {
            //需要同步的变量加上volatile
            private volatile int account = 100;
 
            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized 
            public void save(int money) {
                account += money;
            }
        }
复制代码
 
    注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。 
    用final域,有锁保护的域和volatile域可以避免非同步的问题。 
    
4.使用重入锁实现线程同步
 
    在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 
    ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 
    它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力
 
 
    ReenreantLock类的常用方法有:
 
        ReentrantLock() : 创建一个ReentrantLock实例 
        lock() : 获得锁 
        unlock() : 释放锁 
    注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 
        
    例如: 
        在上面例子的基础上,改写后的代码为: 
        
    代码实例: 
    
 
复制代码
//只给出要修改的代码,其余代码与上同
        class Bank {
            
            private int account = 100;
            //需要声明这个锁
            private Lock lock = new ReentrantLock();
            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized 
            public void save(int money) {
                lock.lock();
                try{
                    account += money;
                }finally{
                    lock.unlock();
                }
                
            }
        }
复制代码
          
    注:关于Lock对象和synchronized关键字的选择: 
        a.最好两个都不用,使用一种java.util.concurrent包提供的机制, 
            能够帮助用户处理所有与锁相关的代码。 
        b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 
        c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 
        
5.使用局部变量实现线程同步 
    如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 
    副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
 
 
 
    ThreadLocal 类的常用方法
 
 
 
    ThreadLocal() : 创建一个线程本地变量 
    get() : 返回此线程局部变量的当前线程副本中的值 
    initialValue() : 返回此线程局部变量的当前线程的"初始值" 
    set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
 
 
 
    例如: 
        在上面例子基础上,修改后的代码为: 
        
    代码实例: 
        
 
复制代码
//只改Bank类,其余代码与上同
        public class Bank{
            //使用ThreadLocal类管理共享变量account
            private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                protected Integer initialValue(){
                    return 100;
                }
            };
            public void save(int money){
                account.set(account.get()+money);
            }
            public int getAccount(){
                return account.get();
            }
        }
复制代码
    注:ThreadLocal与同步机制 
        a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 
        b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式 
发表于 2015-12-18 13:45:31 回复(19)
为何使用同步? 
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(增删改查),将会导致数据的不准确,相互之间产生冲突。类似于在atm取钱,银行数据确没有变,这是不行的,要存在于一个事务中。因此加入了同步锁,以避免在该线程没有结束前,调用其他线程。从而保证了变量的唯一性,准确性。
1.同步方法:
即有synchronized    (同步, ['sɪŋkrənaɪzd] ) 修饰符修饰的方法。
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用给方法前,要获取内置锁,否则处于阻塞状态。
例:public synchronized getMoney(){}
注:synchronized修饰静态方法,如果调用该静态方法,将锁住整个类。

2.同步代码块
即有synchronized修饰符修饰的语句块,被该关键词修饰的语句块,将加上内置锁。实现同步。
例:synchronized(Object o ){}

同步是高开销的操作,因此尽量减少同步的内容。通常没有必要同步整个方法,同步部分代码块即可。
同步方法默认用this或者当前类class对象作为锁。
同步代码块可以选择以什么来加锁,比同步方法要更颗粒化,我们可以选择只同步会发生问题的部分代码而不是整个方法。



发表于 2016-03-09 22:53:09 回复(0)
java 中每个对象都有一把锁, 线程可以通过 synchronized 关键字来获取对象上的锁
同步方法(粗粒度锁):
        1. 修饰一般方法: public synchronized void method (){...}, 获取的是当前调用               对象 this 上的锁
        2. 修饰静态方法: public static synchronized void method (){...}, 获取当前类的             字节码对象上的锁
同步代码块(细粒度锁):
         synchronized ( obj ) {...}, 同步代码块可以指定获取哪个对象上的锁, obj 任意
编辑于 2015-12-18 10:45:00 回复(1)
同步方法锁的是当前对象,当一个线程使用该对象的同步方法时,会获得该对象的锁,其他线程不能访问该对象的同步方法(只有获得该对象的锁才可以访问同步方法,但可以访问该对象的非同步方法),如果并发量大的话,效率很低,因为如果要访问没有冲突的方法时本来不会和之前的操作产生冲突,但因为没有该对象的锁,所以要等待获得该对象的锁,白白地浪费时间
而同步代码块可以选择要同步的代码块,粒度更小,可以避免上面出现的问题
发表于 2016-04-11 21:07:04 回复(0)
PYQ头像 PYQ

Synchronized关键字可以用来修饰4种不同类型的代码块:

  1. 实例方法
  2. 静态方法
  3. 实例方法中的代码块
  4. 静态方法中的代码块

同步方法

 public synchronized void add(int value){
      this.count += value;
  }

注意到此处的Synchronized关键字是加在方法声明处,这会告诉JVM,它是一个同步方法。

实例同步方法的锁,是加在拥有该方法的对象之上。因此每个实例的Synchronized关键字实际上都加在了不同的方法之上。同时只有一个线程能执行该实例的同步方法。

同步静态方法

  public static synchronized void add(int value){
      count += value;
  }

上面是一个同步静态方法。同步静态方法的锁是加在类之上的,而相同的类在JVM中只有一个,一个类中只能有一个线程正在运行,不论它调用的是哪个静态方法。

同步实例代码块

  public void add(int value){

    synchronized(this){
       this.count += value;   
    }
  }

上面是一个非同步方法中的同步实例代码块。注意到同步代码块中Synchronized(this)中的this意味着当前实例对象。括号中的对象被称为监控对象。

监控对象中只有一个线程能运行,下面的两个方法都把锁加在了当前的对象上,因此两者是等效的。线程每次只能执行其中的一个方法。如果第二个同步代码块把Synchronized加在不同的对象上,则这两个方法有可能被两个线程同时执行。

  public class MyClass {
  // 同步方法
    public synchronized void log1(String msg1, String msg2){
       log.writeln(msg1);
       log.writeln(msg2);
    }

  // 同步代码块
    public void log2(String msg1, String msg2){
       synchronized(this){
          log.writeln(msg1);
          log.writeln(msg2);
       }
    }
  }

静态方法中的同步代码

下面的两个方法,都把锁加在类上。

  public class MyClass {
    //静态同步方法
    public static synchronized void log1(String msg1, String msg2){
       log.writeln(msg1);
       log.writeln(msg2);
    }

    //静态同步代码块 
    public static void log2(String msg1, String msg2){
       synchronized(MyClass.class){
          log.writeln(msg1);
          log.writeln(msg2);  
       }
    }
  }

一个JAVA同步的例子

下面的这个例子,开启了两个线程,并且对同一个counter对象调用了add方法。每次只有一个线程能调用同一个实例的add方法,因为此处的Synchronized修饰的是对象。

  // 柜员类
  public class Counter{

     long count = 0;
     // 此处有synchronized关键词修饰此实例方法
     public synchronized void add(long value){
       this.count += value;
     }
  }

  // 顾客类
  public class CounterThread extends Thread{

     protected Counter counter = null;

     public CounterThread(Counter counter){
        this.counter = counter;
     }

     public void run() {
    for(int i=0; i<10; i++){
           counter.add(i);
        }
     }
  }

  // 运行类
  public class Example {

    public static void main(String[] args){
      // 同一个counter实例
      Counter counter = new Counter();
      Thread  threadA = new CounterThread(counter);
      Thread  threadB = new CounterThread(counter);

      threadA.start();
      threadB.start(); 
    }
  }
发表于 2019-10-14 10:01:17 回复(3)

java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

java中每个对象都有一把锁,线程可以通过synchronized关键字来获取对象上的锁。

内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

注意:不能用synchronized修饰方法外面的语句块(类语句块),synchronized锁住的是对象,当初始化对象的时候,JVM在对象初始化完成之前会调用方法外面的语句块,这个时候对象还不存在,所以就不存在锁了。

 

②同步方法:(粗粒度锁):

        1.修饰一般方法: public synchronized void method (){...},获取的是当前调用               对象this上的锁

        2.修饰静态方法: public static synchronized void method (){...},获取当前类的             字节码对象上的锁

③同步代码块(细粒度锁):

         synchronized ( obj ) {...},同步代码块可以指定获取哪个对象上的锁。

发表于 2017-03-26 21:46:42 回复(0)
同步代码块使用的锁是任意对象Object。 同步函数使用的锁是this。 使用静态修饰的同步函数使用的是该类所在的字节码文件对象,格式为类名.class。
发表于 2016-10-06 23:18:11 回复(1)
Java中的每一个对象都有一个内部锁,如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。同步代码块保护所内的内容
发表于 2015-12-03 16:19:38 回复(0)
在java代码中,每一个对象都拥有一把锁,使用synchronzied关键字可以获取到对象上的锁。该关键字可以用到方法上或者代码块上
发表于 2015-11-23 16:35:50 回复(0)

①首先是为什么要使用同步这个概念:

      java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

 

java中每个对象都有一把锁,线程可以通过synchronized关键字来获取对象上的锁。

内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

 

注意:不能用synchronized修饰方法外面的语句块(类语句块),synchronized锁住的是对象,当初始化对象的时候,JVM在对象初始化完成之前会调用方法外面的语句块,这个时候对象还不存在,所以就不存在锁了。

 

②同步方法:(粗粒度锁):

        1.修饰一般方法: public synchronized void method (){...},获取的是当前调用               对象this上的锁

        2.修饰静态方法: public static synchronized void method (){...},获取当前类的             字节码对象上的锁

 

③同步代码块(细粒度锁):

         synchronized ( obj ) {...},同步代码块可以指定获取哪个对象上的锁。

 

④至于代码层面的解释理解之类的总结,以后遇到再详细学习。

发表于 2016-08-21 18:26:32 回复(0)
在Java语言中,每一个对象有一把锁。线程可以使用synchronized关键字来获取对象上的锁。synchronized关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁)。
发表于 2015-10-29 12:10:41 回复(0)
复制粘贴一下 Synchronized关键字可以用来修饰4种不同类型的代码块: 实例方法 静态方法 实例方法中的代码块 静态方法中的代码块 同步方法 public synchronized void add(int value){ this.count += value; } 注意到此处的Synchronized关键字是加在方法声明处,这会告诉JVM,它是一个同步方法。 实例同步方法的锁,是加在拥有该方法的对象之上。因此每个实例的Synchronized关键字实际上都加在了不同的方法之上。同时只有一个线程能执行该实例的同步方法。 同步静态方法 public static synchronized void add(int value){ count += value; } 上面是一个同步静态方法。同步静态方法的锁是加在类之上的,而相同的类在JVM中只有一个,一个类中只能有一个线程正在运行,不论它调用的是哪个静态方法。 同步实例代码块 public void add(int value){ synchronized(this){ this.count += value; } } 上面是一个非同步方法中的同步实例代码块。注意到同步代码块中Synchronized(this)中的this意味着当前实例对象。括号中的对象被称为监控对象。 监控对象中只有一个线程能运行,下面的两个方法都把锁加在了当前的对象上,因此两者是等效的。线程每次只能执行其中的一个方法。如果第二个同步代码块把Synchronized加在不同的对象上,则这两个方法有可能被两个线程同时执行。 public class MyClass { // 同步方法 public synchronized void log1(String msg1, String msg2){ log.writeln(msg1); log.writeln(msg2); } // 同步代码块 public void log2(String msg1, String msg2){ synchronized(this){ log.writeln(msg1); log.writeln(msg2); } } } 静态方法中的同步代码 下面的两个方法,都把锁加在类上。 public class MyClass { //静态同步方法 public static synchronized void log1(String msg1, String msg2){ log.writeln(msg1); log.writeln(msg2); } //静态同步代码块 public static void log2(String msg1, String msg2){ synchronized(MyClass.class){ log.writeln(msg1); log.writeln(msg2); } } } 一个JAVA同步的例子 下面的这个例子,开启了两个线程,并且对同一个counter对象调用了add方法。每次只有一个线程能调用同一个实例的add方法,因为此处的Synchronized修饰的是对象。 // 柜员类 public class Counter{ long count = 0; // 此处有synchronized关键词修饰此实例方法 public synchronized void add(long value){ this.count += value; } } // 顾客类 public class CounterThread extends Thread{ protected Counter counter = null; public CounterThread(Counter counter){ this.counter = counter; } public void run() { for(int i=0; i<10; i++){ counter.add(i); } } } // 运行类 public class Example { public static void main(String[] args){ // 同一个counter实例 Counter counter = new Counter(); Thread threadA = new CounterThread(counter); Thread threadB = new CounterThread(counter); threadA.start(); threadB.start(); } }
发表于 2021-08-13 20:06:37 回复(0)
同步方法: public synchronized void xxx(...){....} 同步代码块: synchronized(this){...},比同步方法更细颗粒度
发表于 2021-03-13 00:06:23 回复(0)
同步静态方法:只要有一个静态方法被锁住,其他的静态方法都会被锁住,无论是同一个实例,还是不同的实例对象,其锁住的是类本身
发表于 2020-09-27 17:18:58 回复(0)
同步代码快比同步方法更细颗粒度,我们可以选择只会发生问题的部分代码而不是整个方法。
编辑于 2020-08-30 11:14:08 回复(0)
同步方法是在方法中加synchrorized关键字,当这个方法是静态的,锁对象是当前类的字节码对象,当这个方法是非静态的,锁对象是this 同步代码块就是使用synchrorized(锁对象)将需要同步的代码括起来,锁对象是任意的对象
发表于 2020-08-14 00:45:15 回复(0)
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(增删改查),将会导致数据的不准确,相互之间产生冲突。类似于在atm取钱,银行数据确没有变,这是不行的,要存在于一个事务中。因此加入了同步锁,以避免在该线程没有结束前,调用其他线程。从而保证了变量的唯一性,准确性。
1.同步方法:
即有synchronized    (同步,美 ['sɪŋkrənaɪzd] ) 修饰符修饰的方法。
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用给方法前,要获取内置锁,否则处于阻塞状态。
例:public synchronized getMoney(){}
注:synchronized修饰静态方法,如果调用该静态方法,将锁住整个类。

2.同步代码块
即有synchronized修饰符修饰的语句块,被该关键词修饰的语句块,将加上内置锁。实现同步。
例:synchronized(Object o ){}

同步是高开销的操作,因此尽量减少同步的内容。通常没有必要同步整个方法,同步部分代码块即可。
同步方法默认用this或者当前类class对象作为锁。
同步代码块可以选择以什么来加锁,比同步方法要更颗粒化,我们可以选择只同步会发生问题的部分代码而不是整个方法。 
发表于 2020-07-10 23:07:34 回复(0)
<p>同步方法以this'和当前方法作为锁</p><p>同步代码块可以指定锁</p>
发表于 2020-07-07 15:40:11 回复(0)
同步代码块由我们去指定是什么锁,只保证代码块中的这段代码是同步的。
同步方法默认的锁是当前类的对象,对于静态同步方法它的锁是类.class,它是对整个方法进行加锁。
发表于 2020-06-18 11:12:38 回复(0)
<p>同步方法获取的是this对象的锁,或者是当前类的锁。</p><p>同步代码块可以选择任意对象加锁。</p>
发表于 2020-05-28 18:31:41 回复(0)