synchronized 看一遍就能懂

本文深入探讨Java中的synchronized关键字,详细解析其实例方法、静态方法及同步代码块的应用方式,并通过示例代码展示如何确保线程安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

synchronized的三种应用方式

synchronized关键字最主要有以下3种应用方式,下面分别介绍

  • 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁

  • 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁

  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

synchronized作用于实例方法

所谓的实例对象锁就是用synchronized修饰实例对象中的实例方法,注意是实例方法不包括静态方法,如下

public class AccountingSync implements Runnable{
    //共享资源(临界资源)
    static int i=0;

    /**
     * synchronized 修饰实例方法
     */
    public synchronized void increase(){
        i++;
    }
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        AccountingSync instance=new AccountingSync();
        Thread t1=new Thread(instance);
        Thread t2=new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
    /**
     * 输出结果:
     * 2000000
     */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

上述代码中,我们开启两个线程操作同一个共享资源即变量i,由于i++;操作并不具备原子性,该操作是先读取值,然后写回一个新值,相当于原来的值加上1,分两步完成,如果第二个线程在第一个线程读取旧值和写回新值期间读取i的域值,那么第二个线程就会与第一个线程一起看到同一个值,并执行相同值的加1操作,这也就造成了线程安全失败,因此对于increase方法必须使用synchronized修饰,以便保证线程安全。此时我们应该注意到synchronized修饰的是实例方法increase,在这样的情况下,当前线程的锁便是实例对象instance,注意Java中的线程同步锁可以是任意对象。从代码执行结果来看确实是正确的,倘若我们没有使用synchronized关键字,其最终输出结果就很可能小于2000000,这便是synchronized关键字的作用。这里我们还需要意识到,当一个线程正在访问一个对象的 synchronized 实例方法,那么其他线程不能访问该对象的其他 synchronized 方法,毕竟一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized实例方法,但是其他线程还是可以访问该实例对象的其他非synchronized方法,当然如果是一个线程 A 需要访问实例对象 obj1 的 synchronized 方法 f1(当前对象锁是obj1),另一个线程 B 需要访问实例对象 obj2 的 synchronized 方法 f2(当前对象锁是obj2),这样是允许的,因为两个实例对象锁并不同相同,此时如果两个线程操作数据并非共享的,线程安全是有保障的,遗憾的是如果两个线程操作的是共享数据,那么线程安全就有可能无法保证了,如下代码将演示出该现象

public class AccountingSyncBad implements Runnable{
    static int i=0;
    public synchronized void increase(){
        i++;
    }
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        //new新实例
        Thread t1=new Thread(new AccountingSyncBad());
        //new新实例
        Thread t2=new Thread(new AccountingSyncBad());
        t1.start();
        t2.start();
        //join含义:当前线程A等待thread线程终止之后才能从thread.join()返回
        t1.join();
        t2.join();
        System.out.println(i);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

上述代码与前面不同的是我们同时创建了两个新实例AccountingSyncBad,然后启动两个不同的线程对共享变量i进行操作,但很遗憾操作结果是1452317而不是期望结果2000000,因为上述代码犯了严重的错误,虽然我们使用synchronized修饰了increase方法,但却new了两个不同的实例对象,这也就意味着存在着两个不同的实例对象锁,因此t1和t2都会进入各自的对象锁,也就是说t1和t2线程使用的是不同的锁,因此线程安全是无法保证的。解决这种困境的的方式是将synchronized作用于静态的increase方法,这样的话,对象锁就当前类对象,由于无论创建多少个实例对象,但对于的类对象拥有只有一个,所有在这样的情况下对象锁就是唯一的。下面我们看看如何使用将synchronized作用于静态的increase方法。

synchronized作用于静态方法

当synchronized作用于静态方法时,其锁就是当前类的class对象锁。由于静态成员不专属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态 成员的并发操作。需要注意的是如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁,看如下代码

public class AccountingSyncClass implements Runnable{
    static int i=0;

    /**
     * 作用于静态方法,锁是当前class对象,也就是
     * AccountingSyncClass类对应的class对象
     */
    public static synchronized void increase(){
        i++;
    }

    /**
     * 非静态,访问时锁不一样不会发生互斥
     */
    public synchronized void increase4Obj(){
        i++;
    }

    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        //new新实例
        Thread t1=new Thread(new AccountingSyncClass());
        //new心事了
        Thread t2=new Thread(new AccountingSyncClass());
        //启动线程
        t1.start();t2.start();

        t1.join();t2.join();
        System.out.println(i);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

由于synchronized关键字修饰的是静态increase方法,与修饰实例方法不同的是,其锁对象是当前类的class对象。注意代码中的increase4Obj方法是实例方法,其对象锁是当前实例对象,如果别的线程调用该方法,将不会产生互斥现象,毕竟锁对象不同,但我们应该意识到这种情况下可能会发现线程安全问题(操作了共享静态变量i)。

synchronized同步代码块

除了使用关键字修饰实例方法和静态方法外,还可以使用同步代码块,在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了,同步代码块的使用示例如下:

public class AccountingSync implements Runnable{
    static AccountingSync instance=new AccountingSync();
    static int i=0;
    @Override
    public void run() {
        //省略其他耗时操作....
        //使用同步代码块对变量i进行同步操作,锁对象为instance
        synchronized(instance){
            for(int j=0;j<1000000;j++){
                    i++;
              }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(instance);
        Thread t2=new Thread(instance);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println(i);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

从代码看出,将synchronized作用于一个给定的实例对象instance,即当前实例对象就是锁对象,每次当线程进入synchronized包裹的代码块时就会要求当前线程持有instance实例对象锁,如果当前有其他线程正持有该对象锁,那么新到的线程就必须等待,这样也就保证了每次只有一个线程执行i++;操作。当然除了instance作为对象外,我们还可以使用this对象(代表当前实例)或者当前类的class对象作为锁,如下代码:

//this,当前实例对象锁
synchronized(this){
    for(int j=0;j<1000000;j++){
        i++;
    }
}

//class对象锁
synchronized(AccountingSync.class){
    for(int j=0;j<1000000;j++){
        i++;
    }
}
<think>嗯,用户问的是synchronized能不能用在变量上。我得先回想一下Java中synchronized的用法。记得synchronized主要是用来控制多线程的同步,避免资源竞争导致的数据不一致问题。通常它可以用在方法或者代码块上,对吧?比如synchronized方法或者synchronized(object) {}这样的结构。 那用户的问题是关于变量的,也就是说是否可以直接在变量声明时加上synchronized关键字?比如像这样:private synchronized int count; 这样的写法对吗?可能用户遇到了多线程环境下变量访问的问题,想用synchronized来保证线程安全。 不过根据我的记忆,Java中synchronized不能直接修饰变量。因为synchronized是一个关键字,用于方法或者代码块,而变量声明需要的是其他修饰符,比如volatile。可能用户混淆了synchronized和volatile的用法。volatile可以修饰变量,保证可见性,但不保证原子性,而synchronized则提供了更全面的互斥和可见性。 那这个时候,用户可能需要的是如何正确地在多线程环境下安全地访问变量。所以应该建议他们使用synchronized来同步访问该变量的代码块,或者考虑使用Lock接口的其他实现。此外,原子类比如AtomicInteger可能也是一个好的替代方案,因为它们内部使用了CAS机制,不需要显式同步。 另外,用户可能不太清楚synchronized的具体作用范围,需要解释为什么不能直接用在变量上。这时候需要指出synchronized的作用是控制线程进入代码块或方法的访问,而不是直接修饰变量本身。变量本身无法通过synchronized来保证线程安全,必须通过正确的同步机制来保护。 可能还需要提醒用户,即使使用synchronized代码块,也要注意锁的对象是否正确,避免使用可变对象或可能被其他代码共享的对象作为锁,导致意外的行为。 总结一下,回答应该分几个部分:首先明确synchronized不能直接修饰变量,然后解释正确的用法,比如用在方法或代码块上,接着提供替代方案如volatile或原子类,最后强调同步的正确实践,比如锁对象的选择和原子操作的使用。这样用户就能全面理解问题所在,并知道如何正确实现了。</think>好的,让我们逐步分析 `synchronized` 能否直接修饰变量的问题: --- ### **1. 直接回答:不能** `Synchronized` **不能直接修饰变量**。 Java语法规定,`synchronized` 关键字只能用于修饰 **方法** 或 **代码块**,无法直接修饰变量声明。例如以下写法是**非法的**: ```java private synchronized int count; // 编译错误! ``` --- ### **2. 正确用法:保护变量访问** 若需保证多线程环境下对变量的安全访问,应通过 `synchronized` 对操作变量的代码段加锁: ```java private int count; // 方式1:同步方法 public synchronized void increment() { count++; } // 方式2:同步代码块(更灵活) public void decrement() { synchronized(this) { // 锁对象需谨慎选择(通常用 final Object) count--; } } ``` --- ### **3. 替代方案:更轻量的线程安全工具** 如果仅需简单操作变量,可考虑以下替代方案: - **`volatile` 关键字** 保证变量的可见性(但**不保证原子性**),适用于单一写多读场景。 ```java private volatile int flag; ``` - **原子类(Atomic Classes)** 如 `AtomicInteger`,内部通过 CAS(Compare-And-Swap)实现原子操作。 ```java private AtomicInteger atomicCount = new AtomicInteger(0); atomicCount.incrementAndGet(); // 线程安全的递增 ``` --- ### **4. 原理与注意事项** - **锁的作用** `synchronized` 通过锁机制实现互斥,确保同一时刻仅一个线程能执行临界区代码,从而保护共享变量。 - **锁对象的选择** 避免使用可变对象(如 `this` 或非 `final` 对象)作为锁,推荐使用专用锁: ```java private final Object lock = new Object(); public void safeMethod() { synchronized(lock) { // 更可控的锁对象 // 操作共享变量 } } ``` - **原子性与可见性** `synchronized` 同时保证原子操作和内存可见性,而 `volatile` 仅保证可见性,需根据场景选择。 --- ### **总结** - **直接修饰变量 ×**:语法不支持。 - **保护变量访问 √**:通过同步方法或代码块。 - **更优选择**:优先考虑原子类或 `volatile`(根据场景)。 - **设计原则**:尽量缩小同步范围,避免过度加锁影响性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值