Java多线程学习-III

参考了 《Java核心技术》 卷I 第14章 多线程 14.5.5-14.6

synchronized 关键字

  先来总结一下使用Lock和Condition对象的关键:

  • 锁用来保护代码片段,任何时刻只有一个线程执行被保护的代码
  • 锁可以拥有一个或多个条件对象
  • 每个条件对象管理进入被保护的代码片段,但还不能运行的线程

  Lock和Condition接口提供了高度的锁定控制,Java中的每个对象都有一个内部锁,如果方法用synchronized关键字生命,那么对象所将保护整个方法,也就是说要调用该方法,线程必须获得内部对象锁。

  也就是以下这两种是等价的:

1
2
3
4
public synchronized void method()
{
// method body
}
1
2
3
4
5
6
7
8
9
10
11
public void method()
{
this.intrinsicLock.lock();
try
{
// method body
}
finally {
this.intrinsicLock.unlock();
}
}

  因此对Bank类的transfer方法声明为synchronized,而不需要使用一个显示的锁。而wait和notifyAll方法等价于:

1
2
intrinsicCondition.await();
intrinsicCondition.signalAll();

  还可以通过synchronized关键字获得一个对象锁

1
2
3
4
5
6
7
8
9
public void transfer(int from, int to, int amount)
{
synchronized (lock) // an ad-hoc lock
{
accounts[from] -= amount;
accounts[to] += amount;
}
System.out.println(. . .);
}

  这里使用lock对象的目的就是用lock对象的内部锁(也就是所有Java对象都有的锁)

  有时候,仅仅为了读写一个或两个实例域就使用同步,开销很大,volatile关键字为实例域的同步访问提供了一种免锁机制,如果声明一个域为volatile,那么编译器和虚拟机就知道可能有多线程并发访问此变量的情况。因此编译器就会加入一些代码保证并发访问这个域的正确性,但是volatile变量不能保证原子性

1
public void flipDone() { done = !done; } // not atomic

  还可以用final变量安全地访问一个共享域,因为其他线程实在完成构造之后看到final变量的。

线程局部变量

  如果想为每个线程构造一个实例:

1
2
public static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

  然后具体调用:

1
String dateStamp = dateFormat.get().format(new Date());

  在一个给定线程首次调用get方法时,会调用其初始化代码。

锁测试和超时

1
2
3
4
5
6
7
8
if (myLock.tryLock())
{
// now the thread owns the lock
try { . . . }
finally { myLock.unlock(); }
}
else
// do something else

  tryLock先试图申请一个锁,成功后返回true,否则返回false,而且线程可以立即离开做其他事情。tryLock也可以使用超时参数

1
if (myLock.tryLock(100, TimeUnit.MILLISECONDS)) {. . .}