“你好,三妹子!我们有一种万能药,可以治愈所有疾病。正如我们所看到的,不受控制的线程切换是一个问题。”
“为什么线程本身不能决定何时切换到下一个线程?完成它们需要执行的所有工作然后发出信号“我完成了!”?
“允许线程自行控制切换将是一个更大的问题。假设你编写的代码不好,线程会永不放弃 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; //AllyString 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 unlockedString 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;
}
} |