嗨!今天,我们将继续考虑多线程编程的功能,并讨论线程同步。

Java中的同步是什么?
在编程领域之外,它意味着一种允许两个设备或程序协同工作的安排。例如,智能手机和电脑可以与谷歌帐户同步,网站帐户可以与社交网络帐户同步,因此您可以使用它们登录。线程同步具有类似的含义:它是线程相互交互的安排。在之前的课程中,我们的线程彼此分开生活和工作。一个进行了计算,第二个睡眠,第三个在控制台上显示一些东西,但他们没有互动。在实际程序中,这种情况很少见。多个线程可以主动处理和修改相同的数据集。这造成了问题。想象一下,多个线程将文本写入同一位置,例如,将文本文件或控制台。在这种情况下,文件或控制台成为共享资源。线程不知道彼此的存在,所以它们只是在线程调度器分配给它们的时间内编写所有可以编写的内容。在最近的一节课上,我们看到了一个这导致什么的例子。让我们现在回忆一下:

原因在于线程正在使用共享资源(控制台),而没有相互协调它们的操作。如果线程调度器将时间分配给线程-1,那么它会立即将所有内容写入控制台。其他线程已经写或还没有写什么并不重要。正如你所看到的,结果令人沮丧。这就是为什么他们为多线程编程引入了一个特殊概念,即互斥(相互排除)。互斥的目的是提供一种机制,这样只有一个线程可以在特定时间访问对象。如果Thread-1获取了对象A的互斥,其他线程将无法访问和修改该对象。其他线程必须等到对象A的互斥释放。这里有一个生活中的例子:想象一下,你和其他10个陌生人正在参加一个练习。你需要轮流表达你的想法并讨论一些事情。但由于你们是第一次见面,为了不经常打断对方并愤怒,你们使用“会说话的球”:只有带球的人才能说话。这样一来,你最终就进行了一次良好而富有成效的讨论。从本质上讲,球是一个互斥。如果一个对象的互斥体掌握在一个线程手中,其他线程无法与该对象一起工作。你不需要做任何事情来建立互斥:它已內建在中,這意味著Java中的每個物件都有一個。
Object

同步运算符的工作原理
让我们来了解一个新的关键词:同步。它用于标记某个代码块。如果代码块用关键字标记,那么该块一次只能由一个线程执行。同步可以通过不同的方式实现。例如,通过声明要同步的整个方法:
synchronized
public synchronized void doSomething() {
// ...Method logic
}
或者编写一个代码块,使用一些对象进行同步:
public class Main {
private Object obj = new Object();
public void doSomething() {
// ...Some logic available simultaneously to all threads
synchronized (obj) {
// Logic available to just one thread at a time
}
}
}
意思很简单。如果一个线程进入标有关键字的代码块内,它会立即捕获对象的互斥,所有其他试图进入同一块或方法的线程都被迫等待,直到上一个线程完成工作并释放监视器。
synchronized

顺便说一句!在课程中,您已经看到了的示例,但它们看起来不同:
synchronized
public void swap()
{
synchronized (this)
{
// ...Method logic
}
}
这个话题对你来说是新的。当然,语法也会有混淆。所以,马上记住它,以避免以后被不同的写法弄糊涂。这两种写作方式的含义相同:
public void swap() {
synchronized (this)
{
// ...Method logic
}
}
public synchronized void swap() {
}
}
在第一种情况下,您在输入方法后立即创建一个同步代码块。它由对象同步,即当前对象。在第二个示例中,您将
this关键字应用于整个方法。这使得没有必要明确指示用于同步的对象。由于整个方法都标有关键字,因此该方法将自动同步到类的所有实例。我们不会深入讨论哪种方式更好。现在,选择您最喜欢的方式 🙂 最重要的是要记住:只有当所有逻辑一次由一个线程执行时,您才能声明方法同步。例如,同步以下
synchronized方法将是一个错误:
doSomething()
public class Main {
private Object obj = new Object();
public void doSomething() {
// ...Some logic available simultaneously to all threads
synchronized (obj) {
// Logic available to just one thread at a time
}
}
}
如您所见,该方法的一部分包含不需要同步的逻辑。该代码可以由多个线程同时运行,所有关键位置都设置在一个单独的块中。还有一件事。让我们仔细检查课程中关于名称交换的例子:
synchronized
public void swap()
{
synchronized (this)
{
// ...Method logic
}
}
注意:同步是用完成的。也就是说,使用特定的
this对象。假设我们有2个线程(
MyClass和
Thread-1和一个
Thread-2对象。在这种情况下,如果
MyClass myClass调用
Thread-1方法,对象的互斥将繁忙,当尝试调用
myClass.swap()方法时,
myClass.swap()将在等待mutex释放时挂起。如果我們有2个线程和2個
Thread-2物件(
MyClass和
myClass1,我們的執行緒可以很容易地在不同的物件上同時執行同步方法。第一个线程执行以下:
myClass2
myClass1.swap();
第二个执行这个:
myClass2.swap();
在这种情况下,方法中的
swap()关键字不会影响程序的运行,因为同步是使用特定对象执行的。在后一种情况下,我们有2个对象。因此,线程不会给彼此带来问题。畢竟,兩個物件有2個不同的互斥,獲得一個與獲得另一個無關。
synchronized
静态方法中同步的特殊性
但是,如果您需要同步静态方法怎么办?
class MyClass {
private static String name1 = "Ally";
private static String name2 = "Lena";
public static synchronized void swap() {
String s = name1;
name1 = name2;
name2 = s;
}
}
不清楚互斥在这里会扮演什么角色。毕竟,我们已经确定每个对象都有一个互斥。但问题是,我们不需要对象来调用方法:该方法是静态的!那么接下来是什么?:/这里其实没有问题。Java的创造者照顾了一切:)如果包含关键并发逻辑的方法是静态的,那么同步会在类级别执行。为了更清晰,我们可以将上述代码重写如下:
MyClass.swap()
class MyClass {
private static String name1 = "Ally";
private static String name2 = "Lena";
public static void swap() {
synchronized (MyClass.class) {
String s = name1;
name1 = name2;
name2 = s;
}
}
}
原则上,你可以自己想到这一点:因为没有对象,所以同步机制必须以某种方式被烘烤到类本身中。事情就是这样:我们可以使用类来同步。





