您的位置:首页 > 其它

多线程同步

2017-01-23 10:54 267 查看
第一种同步方式:ReentrantLock类

一旦一个线程封锁了锁对象,其他任何线程都无法通过lock语句。当其他线程调用lock时,它们被阻塞,直到第一个线程释放锁对象。如果两个线程试图访问同一个Bank对象,那么锁以串行方式提供服务。但是,如果两个线程访问不同的Bank对象,每一个线程得到不同的锁对象,两个线程都不会发生阻塞。

锁是可重入的,因为线程可以重复地获得已经持有的锁。锁保持一个持有计数( holdcount)来跟踪对lock方法的嵌套调用。线程在每一次调用lock都要调用unlock来释放锁。由于这一特性,被一个锁保护的代码可以调用另一个使用相同的锁的方法。

void lock()获取这个锁;如果锁同时被另一个线程拥有则发生阻塞。
void unlock()释放这个锁。

ReentrantLock()构建一个可以被用来保护临界区的可重入锁。
ReentrantLock(boolean fair)构建一个带有公平策略的锁。一个公平锁偏爱等待时间最长的线程。但是,这一公平的保证将大大降低性能。所以,默认情况下,锁没有被强制为公平的。

Lock mylock=
new ReentrantLock();

条件对象

Condition myCondition = mylock.new Condition();

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

myCondition.await();当前线程现在被阻塞了,并放弃了锁。一旦一个线程调用await方法,它进入该条件的等待集。当锁可用时,该线程不能马上解除阻塞。相反,它处于阻塞状态,直到另一个线程调用同一条件上的signalAll方法时为止。这一调用重新激活因为这一条件而等待的所有线程。当这些线程从等待集当中移出时,它们再次成为可运行的,调度器将再次激活它们。同时,它们将试图重新进入该对象。一旦锁成
为可用的,它们中的某个将从await调用返回,获得该锁并从被阻塞的地方继续执行。由于无法确保该条件被满足—signalAll方法仅仅是通知正在等待的线程:此时有可能已经满足条件,值得再次去检测该条件。

如果没有其他线程来重新激活等待的线程,它就永远不再运行了。这将导致令人不快的死锁(
deadlock)现象。
在对象的状态有利于等待线程的方向改变时调用signalAll。
注意调用signalAll不会立即激活一个等待线程。它仅仅解除等待线程的阻塞,以便这些线程可以在当前线程退出同步方法之后,通过竞争实现对对象的访问。

另一个方法signal,则是随机解除等待集中某个线程的阻塞状态。

总结:

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

第二种方式 synchronized方法

Java中的每一个对象都有一个内部锁。如果一个方法用synchronized
关键字声明,那么对象的锁将保护整个方法。

内部对象锁只有一个相关条件。wait方法添加一个线程到等待集中,notifyAll/notify方法除等待线程的阻塞状态。换句话说,调用wait或notifyAll等价于xxx.await(),xxx.signalAll();

确保几个线程调用的是同一个对象。不然几个线程之间锁是不一样的。

非静态方法获得了对象锁,其他线程不能访问类的其他非静态方法。静态方法也一样。如果有线程获得了非静态方法锁,其他线程还可以访问静态方法。如果有线程获得了静态方法锁,其他线程还可以访问非静态方法。静态锁的是xxx.class,非静态锁的一般是this对象。

将静态方法声明为synchronized也是合法的。如果调用这种方法,该方法获得相关的类对象的内部锁。如果类有一个静态同步的方法,那么当该方法被调用时,对象的锁被锁住。因此,没有其他线程可以调用同一个类的这个或任何其他的同步静态方法。

内部锁和条件存在一些局限。包括:
不能中断一个正在试图获得锁的线程。
试图获得锁时不能设定超时。
每个锁仅有单一的条件,可能是不够的。

建议

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

如果synchronized关键字适合你的程序,那么请尽量使用它,这样可以减少编写的代码数量,减少出错的几率。
如果特别需要Lock/Condition结构提供的独有特性时,才使用Lock/Condition。

第三种方式:同步块

synchronized(obj){code}获得obj的锁,可以获得指定的锁。this,xxxx.class,lock都可以,使用这种方法自由度高一点。

第四种方式 volatile变量不能提供原子性

在以下3个条件下,域的并发访问是安全的:
变量是final,并且在构造器调用完成之后被访问。
对变量的访问由公有的锁进行保护。
变量是volatile的。

tryLock方法试图申请一个锁,在成功获得锁后返回true,否则,立即返回false,而且线程可以立即离开去做其他事情。

if(xxx.tryLock()){

lock();
try{
}catch(){
}finnally{
unlock();
}
}else{

}lock方法不能被中断。如果一个线程在等待获得一个锁时被中断,中断线程在获得锁之前一直处于阻塞状态。如果调用带有用超时参数的tryLock,那么如果线程在等待期间被中断,将抛出InterruptedException异常。这是一个非常有用的特性,因为允许程序打破死锁。
也可以调用lockInterruptibly方法。它就相当于一个超时设为无限的tryLock方法。

boolean tryLock()尝试获得锁而没有发生阻塞;如果成功返回真。这个方法会抢夺可用的锁,即使该锁有公平加锁策略,即便其他线程已经等待很久也是如此。
boolean
tryLock(long time, TimeUnit unit)尝试获得锁,阻塞时间不会超过给定的值;如果成功返回true。TimeUnit是一个枚举类型,可以取的值包括SECONDS、 MILLISECONDS、 MICROSECONDS和NANOSECONDS。

void lockInterruptibly()获得锁,但是会不确定地发生阻塞。如果线程被中断,抛出一个InterruptedException异常。

boolean await(long time, TimeUnit unit)进入该条件的等待集,直到线程从等待集中移出或等待了指定的时间之后才解除阻塞。如果因为等待时间到了而返回就返回false,否则返回true。

void awaitUninterruptibly()进入该条件的等待集,直到线程从等待集移出才解除阻塞。如果线程被中断,该方法不会抛出InterruptedException异常。

如果很多线程从一个数据结构读取数据而很少线程修改其中数据的话,可以用ReentrantReadWriteLock类

private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private Lock read = rwl.readLock();
private Lock write = rwl.writeLock();readLock得到一个可以被多个读操作共用的读锁,但会排斥所有写操作。
writeLock得到一个写锁,排斥所有其他的读操作和写操作。

stop和suspend方法有一些共同点:都试图控制一个给定线程的行为。stop方法天生就不安全,经验证明suspend方法会经常导致死锁.

stop方法终止所有未结束的方法,包括run方法。当线程被终止,立即释放被它锁住的所有对象的锁。如果转账钱转出去了,但是还没到目标账号,线程停止了。那就会出问题。会破坏对象。
suspend挂起一个持有一个锁的线程,那么,该锁在恢复之前是不可用的。就会导致死锁。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: