同步方法的具体 - sychronized方法

本文深入探讨Java中synchronized关键字的使用,包括修饰普通方法、静态方法及代码块实现同步的实例。解析不同锁机制如何影响线程访问,以及如何设计线程安全的类。

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

目录

目录

1、sychronized关键字修饰普通方法+ 修饰普通代码块实现同步的实例

1、1 修饰普通方法

1、2 修饰部分代码块 - 同步块

2、sychronized关键字修饰static方法(静态方法)实现同步的实例

3、总结

4、参考



我们知道Java程序依靠synchronized对线程进行同步,使用synchronized的时候,锁住的是哪个对象非常重要。

常见的可以使用synchronized

  • 修饰this;
  • 修饰static方法;
  • 或者修饰部分代码块。

效果的不一样主要看synchronized锁住的是什么锁。每个类都有自己的锁,类的不同对象有各自的对象锁。

  • 锁住的是类的锁:类的锁的管辖范围比对象的锁的管辖范围大,不同的对象之间的锁互不影响,但是他们都受类的锁的控制,如果一个类的锁被一个线程获得,那么这个类的所有的对象都不能访问需要获得类的锁的方法,但是可以访问不需要锁的方法和需要某个对象锁的方法;
  • 两个方法是不是互斥:关键是看两个方法取得的锁是不是互斥的,如果锁是互斥的,那么方法也是互斥访问的,如果锁不是互斥的,那么不同的锁之间是不会有什么影响的,所以这时方法是可以同时访问的。

 


1、sychronized关键字修饰普通方法+ 修饰普通代码块实现同步的实例

 

1、1 修饰普通方法

synchronized修饰普通方法表示整个方法都必须用this实例加锁。synchronized锁住的是this实例的锁,就是拥有这个普通方法的实例的锁,而不是该类的锁。

封装synchronized逻辑代码块:让线程自己选择锁对象往往会使得代码逻辑混乱,也不利于封装。更好的方法是把synchronized逻辑封装起来。例如,我们编写一个线程安全(如果一个类允许多线程正确访问,则被称为线程安全的,thread-safe的计数器Counter类如下:

public class Counter {
    private int count = 0;

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

    public void dec(int n) {
        synchronized(this) {
            count -= n;
        }
    }

    public int get() {
        return count;
    }
}

这样一来,线程调用add()dec()方法时,它不必关心同步逻辑,因为synchronized代码块在add()dec()方法内部。并且,我们注意到,synchronized锁住的对象是this,即当前实例,这又使得创建多个Counter实例的时候,它们之间互不影响,可以并发执行:

var c1 = Counter();
var c2 = Counter();

// 对c1进行操作的线程:
new Thread(() -> {
    c1.add();
}).start();
new Thread(() -> {
    c1.dec();
}).start();

// 对c2进行操作的线程:
new Thread(() -> {
    c2.add();
}).start();
new Thread(() -> {
    c2.dec();
}).start();

注:

  • 当我们锁住的是this实例时,实际上可以用synchronized修饰这个方法。下面两种写法是等价的:
public void add(int n) {
    synchronized(this) { // 锁住this
        count += n;
    } // 解锁
}
public synchronized void add(int n) { // 锁住this
    count += n;
} // 解锁
  • 针对返回多个基本类型数据的方法,是需要同步的!单个不需要:

考察Counterget()方法:

public class Counter {
    private int count;

    public int get() {
        return count;
    }
    ...
}

它没有同步,因为读一个int变量不需要同步。

然而,如果我们把代码稍微改一下,返回一个包含两个int的对象,就必须要同步:

public class Counter {
    private int first;
    private int last;

    public Pair get() {
        Pair p = new Pair();
        p.first = first;
        p.last = last;
        return p;
    }
    ...
}

 

1、2 修饰部分代码块 - 同步块

synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是:

synchronized(this){/区块/}

它的作用域是当前对象,这时锁就是对象的锁,谁拿到这个锁谁就可以运行它所控制的那段代码

当有一个明确的对象作为锁时,就可以这样写程序;

但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象):

class Foo implements Runnable {

       private byte[] lock = new byte[0]; // 特殊的instance变量   

       Public void methodA() {     

         synchronized(lock) { //… }

       }

       //…..

}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

 


2、sychronized关键字修饰static方法(静态方法)实现同步的实例

 

对于static方法,是没有this实例的,因为static方法是针对类而不是实例。但是任何一个类都有一个由JVM自动创建的Class实例,因此,static方法添加synchronized,锁住的是该类的class实例。即:

public synchronized static void test(int n) {
    ...
}

相当于:

public class Counter {
    public static void test(int n) {
        synchronized(Counter.class) {
            ...
        }
    }
}

 


3、总结

关于线程安全:

  • 通过合理的设计和数据封装可以让一个类变为“线程安全”;
  • 一个类没有特殊说明,默认不是thread-safe;

线程安全定义:如果一个类被设计为允许多线程正确访问,我们就说这个类就是“线程安全”的(thread-safe)。

常见线程安全类

  • java标准库的java.lang.StringBuffer
  • 还有一些不变类,例如StringIntegerLocalDate,它们的所有成员变量都是final,多线程同时访问时只能读不能写,这些不变类也是线程安全的。
  • 最后,类似Math这些只提供静态方法,没有成员变量的类,也是线程安全的。

除了上述几种少数情况,大部分类,例如ArrayList,都是非线程安全的类,我们不能在多线程中修改它们。但是,如果所有线程都只读取,不写入,那么ArrayList是可以安全地在线程间共享的。

hashMap也是线程不安全的,这里面涉及到源码,线程安全多关乎一些变量、方法在底层实现中是否满足同步(满足原子性)、满足及时的全局更新等,目前还没深入去看,以后专门写文章来总结。这是之前总结的关于hashMap线程不安全的原因:

https://blog.youkuaiyun.com/Longtermevolution/article/details/105136241#2%E3%80%81hashmap%E4%B8%BA%E5%95%A5%E4%BC%9A%E6%9C%89%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98%EF%BC%9F


4、参考

参考一篇文章,讲述的比较清楚,synchronized修饰方法,包括普通方法和静态方法

https://blog.youkuaiyun.com/xingjiarong/article/details/47907237

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值