并发 (2) 琐对象 条件对象 synchronized 关键字

       线程的第二节课,开始同步方面的知识,首先要了解的就是锁的概念,以前只知道synchronized关键字可以同步线程,现在深入了解一下。

(1)琐对象

       有两种机制防止代码块受并发访问的干扰。Java语言提供一个 synchronized 关键字到这一目的,并且 Java SE 5.0 引入了 ReentrantLock 类。synchronized 关键字自动提供一个锁以及相关的“ 条件”, 对于大多数需要显式锁的情况, 这是很便利的。但是, 我们相信在我们相信在读者分別阅读了条件的内容之后, 理解 synchronized 关键字是很轻松的事情。

用 ReentrantLock 保护代码块的基本结构如

myLock.lockO; // a ReentrantLock object
try{
  critical section
}finally{
myLock.unlockO;  }

       这一结构确保任何时刻只有一个线程进人临界区。一旦一个线程封锁了锁对象, 其他任何线程都无法通过 lock 语句。当其他线程调用 lock 时,它们被阻塞,直到第一个线程释放锁对象。把解锁操作括在 finally 子句之内是至关重要的。如果在临界区的代码抛出异常,锁必须被释放。否则, 其他线程将永远阻塞。

(2) 条件对象

      通常, 线程进人临界区,却发现在某一条件满足之后它才能执行。要使用一个条件对象来管理那些已经获得了一个锁但是却不能做有用工作的线程。

      一个锁对象可以有一个或多个相关的条件对象。你可以用 newCondition 方法获得一个条件对象。习惯上给每一个条件对象命名为可以反映它所表达的条件的名字。例如,在此设置一个条件对象来表达“ 余额充足” 条件

class Bank{
private Condition sufficientFunds;
public BankO{
sufficientFunds = bankLock.newCondition();
}

如果 transfer 方法发现余额不足,它调用sufficientFunds.await();

       等待获得锁的线程和调用 await 方法的线程存在本质上的不同。一旦一个线程调用 await方法, 它进人该条件的等待集。当锁可用时,该线程不能马上解除阻塞。相反,它处于阻塞状态,直到另一个线程调用同一条件上的 signalAll 方法时为止。

       至关重要的是最终需要某个其他线程调用 signalAll 方法。当一个线程调用 await 时,它没有办法重新激活自身。它寄希望于其他线程。如果没有其他线程来重新激活等待的线程,它就永远不再运行了。这将导致令人不快的死锁( deadlock) 现象。如果所有其他线程被阻塞, 最后一个活动线程在解除其他线程的阻塞状态之前就调用 await 方法, 那么它也被阻塞。没有任何线程可以解除其他线程的阻塞,那么该程序就挂起了。

       应该何时调用 signalAll 呢? 经验上讲, 在对象的状态有利于等待线程的方向改变时调用signalAll。例如, 当一个账户余额发生改变时,等待的线程会应该有机会检查余额。在例子中, 当完成了转账时, 调用 signalAll 方法。

       另一个方法 signal, 则是随机解除等待集中某个线程的阻塞状态。这比解除所有线程阻塞更加有效,但也存在危险。如果随机选择的线程发现自己仍然不能运行, 那么它再次被阻塞。如果没有其他线程再次调用 signal, 那么系统就死锁了。

介绍了如何使用 Lock 和 Condition 对象。在进一步深人之前,总结一下有关锁和条件的关键之处:
      •锁用来保护代码片段, 任何时刻只能有一个线程执行被保护的代码。
      •锁可以管理试图进入被保护代码段的线程。
      •锁可以拥有一个或多个相关的条件对象。
      •每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。

(3) synchronized 关键字

Lock 和 Condition 接口为程序设计人员提供了高度的锁定控制。然而,大多数情况下,并不需要那样的控制,并且可以使用一种嵌人到 Java语言内部的机制。从 1.0 版开始,Java中的每一个对象都有一个内部锁。如果一个方法用 synchronized关键字声明,那么对象的将保护整个方法。也就是说,要调用该方法,线程必须获得内部的对象。

public synchronized void methodO{
   method body
}
//等价于
public void methodQ{
   this.intrinsidock.1ock();
try{
   method body
}finally { this.intrinsicLock.unlockO; }
}
      内部对象锁只有一个相关条件。wait 方法添加一个线程到等待集中,notifyAU /notify方法解除等待线程的阻塞状态。

      使用 synchronized 关键字来编写代码要简洁得多。当然,要理解这一代码,你必须了解每一个对象有一个内部锁, 并且该锁有一个内部条件。由锁来管理那些试图进入synchronized 方法的线程,由条件来管理那些调用 wait 的线程。

内部锁和条件存在一些局限。包括:

    •不能中断一个正在试图获得锁的线程。

    •试图获得锁时不能设定超时

    •每个锁仅有单一的条件, 可能是不够

在代码中应该使用哪一种? Lock 和 Condition 对象还是同步方法?下面是一些建议:

    •最好既不使用 Lock/Condition 也不使用 synchronized 关键字。在许多情况下你可以使用 java.util.concurrent 包中的一种机制,它会为你处理所有的加锁。(后面再看)

    •如果 synchronized 关键字适合你的程序, 那么请尽量使用它,这样可以减少编写的代码数量,减少出错的几率。

    •如果特别需要 Lock/Condition 结构提供的独有特性时才使用 Lock/Condition。

public class Dame2 {

	public synchronized void test1() {
		int i = 5;
		while (i-- > 0) {
			System.out.println(Thread.currentThread().getName() + " : " + i);
			try {
				Thread.sleep(500);
			} catch (InterruptedException ie) {
			}
		}
	}

	public synchronized void test2() {
		int i = 5;
		while (i-- > 0) {
			System.out.println(Thread.currentThread().getName() + " : " + i);
			try {
				Thread.sleep(500);
			} catch (InterruptedException ie) {
			}
		}
	}

	public static void main(String[] args) {
	    Dame2 myt2 = new Dame2();
	    new Thread(() -> myt2.test2(), "test2").start();
		new Thread(() -> myt2.test1(), "test1").start();
		
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值