作用
我们在日常生活中,会遇到多个人同时对一个账户取款的情况,线程同步就可以保证让一个用户取款的时候,其他用户不能对同一个账户取款。
如何实现
1.使用synchronized
做锁
Account account=new Account();
@Override
public void run() {
synchronized(account){
if(account.have()>=400){
account.withDraw(400);
System.out.println(Thread.currentThread().getName()+
"取款成功 "+"账户余额:"+account.have());
}else{
System.out.println(Thread.currentThread().getName()+
"全款失败 "+"账户余额:"+account.have());
}
}
}
将临界代码放入到锁中
同步代码块原理
synchronized(account)
1.括号中的内容叫同步监视器(锁)。必须是引用类型,不能是基本数据类型。
2.为了保证安全性,不要改变同步监视器的引用,因为如果改变了同步监视器的引用,就相当于把锁改掉了。所以可以把引用修饰为final
3.一般用线程共享的资源来做锁。
4.不要拿Integer
,和String
的对象引用来做锁
执行的原理
1.当一个线程来到同步代码块,发现锁是open
状态,锁就会close
状态,然后线程开始执行其中的代码。
2.第一个线程执行过程中,发生了线程转化,第一个线程就失去了cpu
,但锁依然是关闭状态。
3.第二个线程获取cpu
,发现锁是close状态,不能执行其中的代码。第二个线程会进入阻塞状态,让出cpu
,当第一个线程再次得到cpu
会继续执行以前没执行完的代码,把代码执行完了以后,锁就会变为open
状态
注意点
1.如果不同的同步代码块,使用相同的对象引用做锁,一个同步代码块的锁是close
状态,其他同步代码块也会是close
状态
同步方法
@Override
public void run() {
withDraw();
}
public synchronized void withDraw() {
if(account.have()>=400){
account.withDraw(400);
System.out.println(Thread.currentThread().getName()+
"取款成功 "+"账户余额:"+account.have());
}else{
System.out.println(Thread.currentThread().getName()+
"全款失败 "+"账户余额:"+account.have());
}
}
在方法的签名上加synchronized
,然后再run()
中调用该方法。
注意点
1.非静态同步方法的同步监视器就是this
,就是你当前的类对象
2.也就是说你在该类中的其他方法加上锁时,如果一个非静态方法被锁上了,因为所有的该类中的非静态方法都使用同一个锁,所以其他方法都被锁上了
3.在静态方法上也可以加锁,该锁的同步监视器:类名.class
如果该类中的静态方法被锁上了,其他的静态方法也会被锁上。
4.因为上面的两个原因,导致同步方法的效率是非常低的。同步代码块的效率是比同步方法的效率高的,所以一般不要使用同步方法
Lock锁
private Lock lock=new ReentrantLock();
@Override
public void run() {
lock.lock();
try{
if(account.have()>=400){
account.withDraw(400);
System.out.println(Thread.currentThread().getName()+
"取款成功 "+"账户余额:"+account.have());
}else{
System.out.println(Thread.currentThread().getName()+
"全款失败 "+"账户余额:"+account.have());
}
}finally {
lock.unlock();
}
Lock
锁是属于API范围的,而synchronized
是虚拟机调度的。
Lock通过对象名来调用方法,来实现锁的开闭,所以你必须要手动解锁。对于synchronized
是可以自动解锁的。
注意点
1.如果在run方法内部出现异常,直接抛给调用者,这时锁话没有开。为了防止这种的情况出现,要加try
和finall
2.ReentrantLock
是一种可重入锁。如果多个方法用来同一个lock
,多个方法是可以进入的,也是可重入锁的含义。