java中的lock和synchronized区别是什么

来源:互联网 发布:seo博客 编辑:程序博客网 时间:2024/05/17 03:17
创建一个多线程步骤:
classs *Runable implements Runnable{
public void run(){
doMost...
}
}
*Runnable r=new *Runnable();
Thread thread=new Thread(r);
thread.start();
thread.interrupt();程序应该对线程中断做出恰当响应
====================================================================
private Lock banklock = new ReentrantLock();
public class Bank{
public void transfer(int from,int to,int amount){
bankLock.lock();
try{
原子操作
}finally{
bankLock.unlock();
}
}
}
======================================================================
条件对象:
通常,线程进入临界区,却发现在某一条件满足之后它才能执行。要使用一个条件对象来管理那些已经获得了一个锁但是却不能锁有用工作的线程。
if(bank.getBanlance(from)>=amout)
bank.transfer(from,to,amout);//有可能在判读晚if后,线程别中断,当线程被还原时候可能余额已经不足;
应该如此写:
bankLock.lock();
try{
while(accounts[from]<amount){
dosoming...
}
}finally{
bankLock.unlock();
}
现在,当账户中没有足够的余额时,应该做什么呢?等待直到另一个线程向账户中注入了资金,但是这一线程刚刚获得了对bankLock的排它性访问,因此别的线程没有进行存款操作的机会,这就是为什么我们需要条件对象的原因
一个锁对象可以有一个或多个相关的条件对象。你可以用newCondition方法获得一个条件对象。习惯上给每一个条件对象命名为可以反映他所表达的条件名字。例如,在此设置一个条件对象来表达“余额充足”
class Bank{
private Condition sufficientFunds;
public Bank(){
sunfficientFunds=bankLock.newCondition();
}
}
如果transfer方法发现余额不足,他调用sufficinetFunds.await();
当前线程现在被阻塞,并放弃了锁,我们希望这样可以使得另一个线程可以进行增加账户余额的操作
等待获得锁的线程和调用await方法的线程存在本质上的不同,一旦一个线程调用await方法,它进入该条件的等待集。当锁可用时,该线程不能马上解除阻塞,相反,它处于阻塞状态,直到另一个线程调用同一条件上的signalAll方法是为止。当另一个线程转账时,它应该调用
sufficientFunds.signalAll();
public void tranfer(int from,int to,itn amount){
bankLock.lock();
try{
while(accounts[from]<amout)
sufficientFunds.await();
sufficientFumnds.signalAll();
}finally{
bankLock.unlock();
}
}
void await()//将该线程放到条件的等待集中
void signalAll()//解除该条件的等待集中的所有线程的阻塞状态
void signal()//从该条件的等待集中随机地选择一个线程,解除其阻塞状态
=====================================================================
synchronized关键字
在前面一节中,介绍了如何使用Lock和Condition对象,在进一步深入之前,总结一下有关锁和条件的关键之处:
1.锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码
2.锁可以管理试图进入被保护代码段的线程
3.锁可以拥有一个或多个相关的条件对象
4.每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程
Lock和Condition接口被天际到Java5.0中,这也向程序设计人员提供了攻读的封锁控制。然而,大多数情况下,并不需要那样的控制,并且可以使用一种嵌入到Java语言内部的机制。从1.0版开始,Java中的每一个对象都有一个内部锁。如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。也就是说,要调用该方法,线程必须获得内部的对象锁。
换句话说:
public synchronized void method(){
method body
}
pblic void method(){
this.intrinsicLock.lock();
try{
method body;
}finally{
this.intrinsicLock.unlock();
}
}
例如:可以简单地声明Bank类的transfer方法为synchronized,而不是使用一个显示的锁,内部对象锁只有一个相关条件,wait方法添加一个线程到等待集中,notifyAll/notify方法解除等待线程的阻塞状态。换句话说,调用wait或notifyAll等价于
intrinsicCondition.await();
intrinsicCondition.signalAll();
class Bank{
public synchronized void transfer(int from,int to,int amout)throws InterrupterException{
while(accounts[from]<amount)
wait();
account[from]-=amout;
account[to]+=amout;
notifyAll();
}
public synchronized double getTotalBalance(){...}
public double[] accouts;
}
可以看到,使用synchronized关键字来编写代码要简洁得多,当然,要理解这一代码,你必须了解每一个对象有一个内部锁,并且该锁有一个内部条件。
=====================================================================
对象锁:
有时程序员使用一个对象的锁来实现额外的原子操作,实际上称为客户端锁定,例如,考虑Vector类,一个列表,它的方法是同步的。现在,假定在Vector<Double>中存储银行余额。这里有一个transfer方法的原始实现:
public void transfer(Vector<Double> accounts,int from,int to,int amount){
accounts.set(from,accounts,get(from)-amount);
accounts.set(to,accounts.get(to)+amount);
System.out.println("...");
}
Vector类的get和set方法是同步的,但是,这对于我们并没有什么帮助,在第一次对get的地哦啊用已经完成之后,一个线程完全可能在transfer方法中被剥夺运行权。于是,另一个线程可能在想通过的存储位置存入不同的值,但是,我们可以截获这个锁:
public void transfer(Vector<Double> accounts,int from,int to,int amout){
synchronized(accounts){
accounts.set(from,accounts.get(from)-amount);
accounts.set(to,accounts.get(from)+amount);
}
System.out.println("...");
}
这个方法可以工作,但是他完全依赖于这样一个事实,Vector类对自己的所有课修改方法都使用内部锁,然而,这是真的吗?Vector类的文档没有给这样的承诺,不得不仔细研究源码并希望将来的版本能介绍非同步的可修改方法,如你所见,客户端锁定是非常脆弱的,通常不推荐使用。
=======================================================================
读/写锁
java.util.concurrent.locks包定义了两个锁类,我们已经讨论的ReentrantLock类和ReentrantReadWriteLock类,如果很多线程从一个数据结构读取数据而很少线程修改其中数据的话,后者是十分有用的。在这种情况下,允许对读者线程共享访问是合适的。当然,写者线程依然必须是互斥访问的。
下面是使用读写锁的必要步骤:
1.构造一个ReentranReadWriteLock对象
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
2.抽取读写锁:
private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();
3.对所有的访问者加读锁
public double getTotalBalance(){
readLock.lock();
try{...}
finally{readLock.unlock();}
}
4.对所有的修改者加锁
public void tranfer(...){
writeLock.lock();
try{...}
finally{writeLock.unlock();}
}
Lock readLock() 得到一个可以被多个读操作共用的读锁,但会排斥所有写操作
Lock writeLock()得到一个写锁,排斥所有其他的读操作和写操作
===================================================================
阻塞队列
现在,读者已经看到了形成Java并发程序设计基础的底层构建块。然而,对于实际编程来说,应该尽可能远离底层结构,使用有并发出处理的专业人士实现的较高层次的结构要方便得多,要安全的多。
===================================================================
线程池
newCachedThreadPool方法构建了一个线程池,对于每个任务,如果有空闲线程可用,立即让它执行任务,如果没有可用的空闲线程,则创建一个新的线程。newFixedThreadPool方法构建一个具有固定大小的线程池。如果提交的任务数多于空闲的线程数,那么把得不到的服务的任务放置到队列中,当其他任务完成以后再运行他们。(这最好自己写一下)
===================================================================
1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候     线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,     如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断     如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情     ReentrantLock获取锁定与三种方式:    a)  lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁    b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;    c)tryLock(long timeout,TimeUnit unit),   如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;    d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断 2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中 3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
原创粉丝点击