提问人:user22844207 提问时间:11/10/2023 最后编辑:user22844207 更新时间:11/12/2023 访问量:132
Java 线程中的同步语句
Synchronized Statements in Java Thread
问:
我正在阅读官方教程中的线程主题,我正在尝试理解内在外观。
我现在了解了同步方法、同步语句和同步静态方法之间的不同基本原理。
在那篇文章的同步语句部分,给出一个例子,他们说
在此示例中,addName 方法需要同步对 lastName 和 nameCount 的更改,但也需要避免同步其他对象方法的调用。(从同步代码中调用其他对象的方法可能会产生有关活动部分所述的问题。如果没有同步语句,就必须有一个单独的、不同步的方法,其唯一目的是调用 nameList.add。
我真的不明白他们说了什么。请用一个简单的例子向我解释一下。
然后,他们说:
同步语句对于通过细粒度同步提高并发性也很有用。例如,假设类 MsLunch 有两个实例字段 c1 和 c2,它们从不一起使用。这些字段的所有更新都必须同步,但没有理由阻止 c1 的更新与 c2 的更新交错进行,这样做会通过创建不必要的阻塞来减少并发性。我们没有使用同步方法或以其他方式使用与此相关的锁,而是创建两个对象来提供锁。
带示例代码
public class MsLunch {
private long c1 = 0;
private long c2 = 0;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void inc1() {
synchronized(lock1) {
c1++;
}
}
public void inc2() {
synchronized(lock2) {
c2++;
}
}
}
我应该如何理解它?我认为,它只是提到创建了两个对象来管理各自的变量 c1、c2,还有什么吗?
更新
在阅读了所有评论以及@tgdavies和@michael的回答后,我非常感谢您的支持,帮助我清楚地理解了这些概念。
对于第二个引号,我尝试进行比较以理解它。 实际上比synchronized statement
synchronized method
- 带有同步语句的 MsLunch.java
public class MsLunch {
private long c1 = 0;
private long c2 = 0;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void inc1() {
synchronized(lock1) {
c1++;
}
}
public void inc2() {
synchronized(lock2) {
c2++;
}
}
public long getC1() {
return c1;
}
public long getC2() {
return c2;
}
}
- MsLunch2.java 与同步方法
public class MsLunch2 {
private long c1 = 0;
private long c2 = 0;
public synchronized void inc1() {
c1++;
}
public synchronized void inc2() {
c2++;
}
public long getC1() {
return c1;
}
public long getC2() {
return c2;
}
}
- 测试 .java
public class Main {
public static void main(String[] args) throws InterruptedException {
// synchronized statements
// MsLunch ms = new MsLunch();
// synchronized method
MsLunch2 ms = new MsLunch2();
long start = System.currentTimeMillis();
System.out.println(start);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
ms.inc1();
}
System.out.println(ms.getC1());
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
ms.inc2();
}
System.out.println(ms.getC2());
});
t1.start();
t2.start();
t1.join();
t2.join();
long end = System.currentTimeMillis();
System.out.println(end);
System.out.println("Time = " + (end - start)/1000.0);
}
}
答:
第一个问题的示例代码是:
public void addName(String name) {
synchronized(this) {
lastName = name;
nameCount++;
}
nameList.add(name);
}
重要的是,我们不要在按住锁的同时调用对象的方法,因为我们不知道它的行为——例如,这可能需要很长时间。add
nameList
this
add
如果没有同步语句(即,如果我们只有同步方法),我们需要编写:
public void addName(String name) {
doSetLastName(name);
nameList.add(name);
}
private synchronized doSetLastName(String name) {
lastName = name;
nameCount++;
}
短语的解释:
避免同步其他对象方法的调用
“避免”做某事意味着不做,所以我们应该“不同步其他对象方法的调用”
“调用”是一个调用,所以在上面的例子中,我们在对象上调用了该方法。add
nameList
实例方法中的代码在特定对象(即引用的对象)的上下文中运行,因此“其他对象的方法”是不是该对象的对象上的方法。在上面的示例中,是对象 上的方法。this
this
add
nameList
因此,我们可以将其改写为“不要在按住锁时在其他对象上调用方法”(当然,除非您决定需要这样做以确保线程安全。
对于您的第二个问题,假设它是这样实现的:MsLunch
public class MsLunch {
private long c1 = 0;
private long c2 = 0;
public synchronized void inc1() {
c1++;
}
public synchronized void inc2() {
c2++;
}
}
这仍然是完全线程安全的,但是由于两个同步部分都锁定在实例中,因此线程调用将被线程调用阻止。这对于保持线程安全是不必要的。MsLunch
inc1
inc2
评论
nameList
add
synchronized
nameList
"...我正在阅读官方教程中的线程主题,我正在尝试理解内在外观。..."
锁的概念类似于单例类的概念。
这或多或少是“内在的”这个词所描述的——即内在的、定性的。
基本上只有一个实例,一次只有 1 个线程可以访问它。
当另一个线程遇到锁时,JVM 将委托它是必须等待还是继续——这在 Java 中称为“阻塞”。
"...我现在了解了同步方法、同步语句和同步静态方法之间的不同基本原理。..."
它们是非常基本的概念。您只需锁定一个类或实例即可防止并发修改。
换句话说,您可以“微调”锁,以减少“阻塞”。
尽管如此,线程和多线程的实际实践可能相当复杂。
我推荐阅读 O'Reilly Media 的 Java Threads, 3rd Edition。
"...在那篇文章的同步语句部分,举个例子,他们说......
。我真的不明白他们说了什么。请用一个简单的例子向我解释一下。..."
从本质上讲,他们所说的是,最好不要在锁中包含方法调用。
"...然后,他们说:......
。我应该如何理解它?我认为,它只是提到创建了两个对象来管理各自的变量 c1、c2,还有什么吗?..."
这意味着,与其锁定 MsLunch 实例,不如锁定关联的 Object。
这是整个 Java 框架中的常见做法。
查看 OpenJDK 中的 BufferedReader 类源代码。
GitHub – java/io/BufferedReader.java
评论
this
this
Object
this
评论
c1
c2
this
c1
c2
c1
c10
getC1()
getC2()
synchronized