线程同步

75次阅读

共计 3006 个字符,预计需要花费 8 分钟才能阅读完成。

锁对象
有两种机制防止代码块受到并发的干扰:
1. 一种是 Synchronized 关键字,自动提供一个锁和相关的条件对象。
2.jdk 5.0 引入了 Reentrantlock 类。
Reentrantlock 的用法:
[Java] 纯文本查看 复制代码
?

this.banklock=new ReentrantLock();// 获取锁对象[/align]
banklock.lock();// 获得锁

try{// 业务代码

HashMap<String, Account> accounts = totalAccount.getAccounts();

}finally{

banklock.unlock();// 释放锁

}
使用 RentrantLock 构建的是一个可重入的锁,对于重入的概念不是非常的理解

锁的升级与对比
Java1.6 为了减少获得锁和释放锁带来的消耗,引入了“偏向锁”和“轻量级锁”,
Java1.6 中一共有 4 中锁状态,基本从低到高依次是:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。这几个锁会随着竞争情况逐步升级,锁可以升级当时不能降级。

偏向锁
为什么要使用锁:
在多线程应用中,如果有两个或两个以上线程需要同时去修改一个共享的数据,这种情况就有可能产生讹误的对象,这个情况通常称为:竞争条件。
假设:线程 1 正在执行修改操作,但是这个操作还没有完成,它被线程调度器剥夺了运行权,这个时候第 2 个线程修改了数据,然后第 1 个线程被唤醒继续执行,这个操作将会擦除第 2 个线程的跟新操作。
如果在这一系列的操作上加上锁机制,那么就不会出现这种数据讹误的情况,加上锁后将会这么执行:线程 1 开始启动,并且拿到锁,在执行结束前被停止,假定第 2 个线程启动了,由于第 2 个线程没有获得锁,将在调用 lock 方法的时候被阻塞,必须等待第 1 个线程执行完毕释放锁,才能正常执行。
为什么需要在 finally 里释放锁:
这是至关重要的,如果在临界区的代码抛出异常可以保证锁必须被释放,否则其他线程将永远阻塞。
条件对象
为什么使用条件:

假设一个线程获得了锁,但是这个线程并不满足某些条件,不能够有效的完成所有的工作,这个时候我们就需要手动的让线程等待,并释放锁,由其他满足条件的线程执行有效的工作。
如何使用条件:
// 返回一个与该锁相关的条件对象
[Java] 纯文本查看 复制代码
?

Condition newCondition = banklock.newCondition();

this.banklock=new ReentrantLock();// 获取锁对象

banklock.lock();// 获得锁

try{// 业务代码 i

If(xxx=false){

newCondition .wait();// 控制线程等待
}

HashMap<String, Account> accounts = totalAccount.getAccounts();

newCondition.signalAll();// 解除所有等待线程的阻塞状态

}finally{

banklock.unlock();// 释放锁

}
一个线程调用 wait 方法,它没有办法激活自身,只能等待其他线程调用 signaAll 方法,来激活。如果没有其他线程来激活它,将会出现死锁现象。如果所有的线程被阻塞,做最后一个线程在解除其他线程阻塞状态前调用了 wait 方法,那么这个程序就挂起了。
SignalAll 并不能立即激活线程,它仅仅解除线程的阻塞状态,以便在当前线程退出后,通过竞争来实现对象的访问。
另一个方法 signal,是随机解除一个等待线程,这比解除所有线程跟有效,但是也很危险,如果被唤醒的线程不满足条件,再次进入等待,并且没有其他线程可以调用 signal,系统就死锁了。

Synchronized 关键字

用法:
public synchronized void transferoff(){
If()
Wait()
notiifyAll()

    }

将方法申明为 synchroized(可以申明静态方法),要调用这个方法线程必须获取内部的对象锁,内部对象锁只有一个相关的条件对象,wait 方法添加一个线程到等待集中,notifyAll/notfiy 方法,解除等待线程的阻塞状态。

内部锁和条件的局限性:

  1. 不能中断一个正在试图获得锁的线程
  2. 试图获得锁时不能设定超时
  3. 每个锁仅有一个单一条件,可能不够

在代码中应该使用那种机制?Lock 和 condition 对象还是同步方法(synchrionized)?
用 java.util.concurrent 包中的一种机制,它会为你处理所有的枷锁,你会看到如何使用阻塞队列来同步完成一个共同任务。

  1. 如果 synchronized 适合你的程序,那么尽量使用它,可以减少代码,减少出错几率。
  2. 如果特别需要 lock 和 condition 结构提供的独有特性时,才使用。

锁和条件的关键之处:

  1. 锁用来保护代码片段,任何时候只能由一个线程访问被保护的代码。
  2. 锁可以管理试图进入被保护代码的线程
  3. 锁可以拥有一个或多个条件对象
  4. 每个条件对象管理已经进入被保护代码但不能运行的线程。

同步阻塞
Object lock=New Object();
每一个 java 对象有一个锁,通过 synchronized(lock) 进入一个同步阻塞,获得 Object 的锁,有时候程序员使用对象的锁来实现原子性,实际上称为客户端锁定,客户端锁定非常脆弱不推荐使用。

监视器概念
没有什么实质性的作用

Volatile 域
Volatile 为实例域提供一种免锁机制,如果申明一个域为 volatile,那么编译器和虚拟机就知道该域是有可能被另一个线程并发跟新,volatile 不能提供原子性,实例域有可能在方法中被从新赋值。
同步格言:“如果向一个变量写入值,而这个变量接下来可能会被另外一个线程读取,或者,从一个变量读值,而这个变量可能是之前被另外一个线程写入的,此时必须同步”

Final 变量
除非使用锁和 volatile,否则无法从多个线程安全的读取一个域,使用 final 变量申明一个实例域,就可以保证其他线程获取的值不会为 null

原子性
要对共享变量进行原子性修饰。

死锁
所有的线程进入阻塞状态,并且没有其他线程可以唤醒,出现死锁,程序挂起。

避免死锁的几种方式:

  1. 避免一个线程同时获取多个锁。
  2. 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
  3. 尝试使用定时锁,lock.tryLock(timeout),这种锁可以打破死锁。
  4. 对于数据库锁,加锁和解锁必须在一个数据库链接里,否则会出现解锁失败得情况。

线程局部变量
如果需要避免共享变量,可以使用 ThreadLocal 为每个线程提供各自的实例域,可以为单独的线程提供生成器。

锁测试与超时
使用 trylock 方法试图申请一个锁,如果成功获得锁则返回 true,否则立即返回 false,而且当前线程可以立即离去做其他事情。
如果使用带有超时参数的 trylock,线程在等待期间中断,将抛出 InterruptedException 异常,这是一个非常有用的特性,它允许打破死锁。

读 / 写锁
使用 ReentrantReadWriteLock 类可以获取一个读写锁对象,
使用 RW.readlock()得到一个可以被多个读操作公用的读锁,但会排斥写操作。
使用 RW.writeLock()得到一个写锁排斥所有其他读操作与写操作

为什么弃用 stop 和 suspend 方法
Stop 方法天生不安全,suspend 方法经常导致死锁。

正文完
 0