阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

同步方法

32次阅读
没有评论

共计 2038 个字符,预计需要花费 6 分钟才能阅读完成。

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

让线程自己选择锁对象往往会使得代码逻辑混乱,也不利于封装。更好的方法是把 synchronized 逻辑封装起来。例如,我们编写一个计数器如下:

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();

现在,对于 Counter 类,多线程可以正确调用。

如果一个类被设计为允许多线程正确访问,我们就说这个类就是“线程安全”的(thread-safe),上面的 Counter 类就是线程安全的。Java 标准库的 java.lang.StringBuffer 也是线程安全的。

还有一些不变类,例如StringIntegerLocalDate,它们的所有成员变量都是final,多线程同时访问时只能读不能写,这些不变类也是线程安全的。

最后,类似 Math 这些只提供静态方法,没有成员变量的类,也是线程安全的。

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

提示

没有特殊说明时,一个类默认是非线程安全的。

我们再观察 Counter 的代码:

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

当我们锁住的是 this 实例时,实际上可以用 synchronized 修饰这个方法。下面两种写法是等价的:

public void add(int n) {synchronized(this) {// 锁住 this
        count += n;
    } // 解锁
}

写法二:

public synchronized void add(int n) {// 锁住 this
    count += n;
} // 解锁

因此,用 synchronized 修饰的方法就是同步方法,它表示整个方法都必须用 this 实例加锁。

我们再思考一下,如果对一个静态方法添加 synchronized 修饰符,它锁住的是哪个对象?

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

对于 static 方法,是没有 this 实例的,因为 static 方法是针对类而不是实例。但是我们注意到任何一个类都有一个由 JVM 自动创建的 Class 实例,因此,对 static 方法添加 synchronized,锁住的是该类的Class 实例。上述 synchronized static 方法实际上相当于:

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

我们再考察 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;
    }
    ...
}

就必须要同步了。

小结

synchronized 修饰方法可以把整个方法变为同步代码块,synchronized方法加锁对象是this

通过合理的设计和数据封装可以让一个类变为“线程安全”;

一个类没有特殊说明,默认不是 thread-safe;

多线程能否安全访问某个非线程安全的实例,需要具体问题具体分析。

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计2038字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中