“你好,阿米戈!我们有一种万能药,可以治愈所有疾病。正如我们所看到的,不受控制的线程切换是一个问题。”

“为什么线程本身不能决定何时切换到下一个线程?完成它们需要执行的所有工作然后发出信号“我完成了!”?

“允许线程自行控制切换将是一个更大的问题。假设你编写的代码不好,线程会永不放弃 CPU。在过去,这就是它的工作方式。那真是一场噩梦。”

“好的。那有什么解决方法呢?”

"阻塞其他线程。这就是它的工作方式。” 

显然,线程在尝试使用共享对象和/或资源时会互相干扰。就像在具有控制台输出的示例中所看到的那样:有一个控制台,所有线程都输出到该控制台。这太混乱了。

因此发明了一个特殊的对象:互斥锁。就像洗手间门上写着“无人/有人”的标志一样。它有两种状态:对象可用被占用。这些状态也称为“锁定”和“解锁”。

当线程需要与其他线程共享的对象时,它将检查与该对象关联的互斥锁。如果互斥锁已解锁,则线程将锁定它(将其标记为“已占用”)并开始使用共享资源。线程完成业务后,互斥锁将被解锁(标记为“可用”)。

如果线程要使用该对象且互斥锁已锁定,则线程在等待时会进入休眠状态。当互斥锁最终被占用线程解锁时,我们的线程将立即锁定它并开始运行。洗手间门标志这个比喻非常恰当。

“如何使用互斥锁呢?我需要创建特殊对象吗?”

“比那简单多了。Java 的创建者将此互斥锁内置到 Object 类中。因此,你甚至不必创建它。它是每个对象的一部分。下面是它的工作原理:”

代码 说明
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
swap 方法交换 name1 和 name2 变量的值。

如果同时从两个线程中调用它会发生什么?

实际代码执行 第一个线程的代码 第二个线程的代码
String s1 = name1; //Ally
name1 = name2; //Lena
name2 = s1; //Ally

String s2 = name1; //Lena
name1 = name2; //Ally
name2 = s2; //Lena
String s1 = name1;
name1 = name2;
//other thread is running
name2 = s1;
//the thread waits until the mutex is unlocked

String s2 = name1;
name1 = name2;
//other thread is running
//other thread is running
name2 = s2;
末行
变量的值被交换两次,各自返回其原始位置。

注意关键字 synchronized

“嗯,它是什么意思?”

“当线程进入标记为 synchronized 的代码块时,Java 机器立即锁定 synchronized 一词后面括号内指示的对象的互斥锁。在我们的线程离开之前,没有其他线程可以进入此代码块。一旦我们的线程离开标记为 synchronized 的代码块,互斥锁将立即自动解锁,并可被另一个线程获取。”

如果互斥锁被占用,我们的线程将保持静止并等待其释放。

“如此简单精妙。很漂亮的解决方法。”

“是的。但是你知道在这种情况下会发生什么吗?”

代码 说明
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public void swap2()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
swap 和 swap2 方法共享同一个互斥锁 (this) 对象。

如果一个线程调用 swap 方法,而另一个线程调用 swap2 方法会发生什么?

“由于互斥锁相同,因此第二个线程将不得不等待,直到第一个线程离开 synchronized 代码块为止。因此,同时访问不会有任何问题。”

“做得不错,阿米戈!这就是正确答案!”

现在我想指出的是,synchronized 不仅可以用于标记代码块,而且可以用于标记方法。如下所示:

代码 实际发生的情况
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public synchronized void swap()
{
String s = name1;
name1 = name2;
name2 = s;
}

public static synchronized void swap2()
{
String s = name1;
name1 = name2;
name2 = s;
}
}
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public static void swap2()
{
synchronized (MyClass.class)
{
String s = name1;
name1 = name2;
name2 = s;
}
}