如何实现程序支持异步:java
class X{ //修饰非静态方法 synchronized void foo(){ //临界区 } //修饰静态方法 synchronized static void bar(){ //临界区 } //修饰代码块 Object obj = new Object(); void baz(){ synchronized(obj){ //临界区 } } }
Java编译器会在synchronized修饰的方法或代码块先后自动加上加锁lock()和解锁unlock(),这样作的好处就是加锁lock()和解锁unlock()必定 是成对出现的,毕竟忘记解锁unlock()但是个致命的Bug(意味着其余线程只能死等下去了)。安全
//修饰静态方法是用当前类的字节码文件做为锁 class X{ //修饰静态方法 synchronized(X.class) static void bar(){ //临界区 } }
//修饰非静态方法是用当前对象做为锁 class X{ //修饰非静态方法 synchronized(this) static void bar(){ //临界区 } }
如何用一把锁保护多个资源性能优化
受保护资源和锁之间合理的关联关系应该是N:1的关系,也就是说能够用一把锁来保护多个资源,可是不能用多把锁来保护一个资源,并发
示例一:异步
public class Account { /** *锁:保护帐⼾余额 */ private final Object balLock = new Object(); /** * 帐⼾余额 */ private Integer balance; /** * 错误的作法 * 非静态方法的锁是this, * this这把锁能够保护本身的余额this.balance,保护不了别人的余额 target.balance * */ synchronized void transfer(Account target,int amt){ if (this.balance > amt) { this.balance -= amt; target.balance += amt;//这段代码会出现线程安全,要保证线程安全的话要使用同一个锁 } } }
示例二:性能
public class Account { /** *锁:保护帐⼾余额 */ private final Object balLock = new Object(); /** * 帐⼾余额 */ private Integer balance; /** * 正确的作法,可是会致使整个转帐系统的串行 * * Account.class是全部Account对象共享的, * 并且这个对象是Java虚拟机在加载Account类的时候建立的, * 因此咱们不用担忧它的惟一性 * * 这样还有个弊端:全部的转帐都是串行了 */ void transfer2(Account target,int amt){ synchronized(Account.class){ if (this.balance > amt) { this.balance -= amt; target.balance += amt; } } } }
这样的话转帐操做就成了串行的了,正常的逻辑应该只锁转入帐号和被转入帐户;不影响其余的转帐操做。稍做改造:优化
示例三:this
public class Account { /** *锁:保护帐⼾余额 */ private final Object lock; /** * 帐⼾余额 */ private Integer balance; //私有化无参构造 private Account(){} //设置一个传递lock的有参构造 private Account(Object lock){ this.lock = lock; } /** * 转帐 */ void transfer(Account target,int amt){ //此处检查全部对象共享锁 synchronized(lock){ if (this.balance > amt) { this.balance -= amt; target.balance += amt; } } } }
这个方法虽然可以解决问题,可是它要求建立Account对象的时候必须传入同一个对象,线程
还有就是传递对象过于麻烦,写法繁琐缺少可行性。code
示例四:
public class Account { /** * 帐⼾余额 */ private Integer balance; /** * 转帐 */ void transfer(Account target,int amt){ //此处检查全部对象共享锁 synchronized(Account.class){ if (this.balance > amt) { this.balance -= amt; target.balance += amt; } } } }
用Account.class做为共享的锁,锁定的范围太大。 Account.class是全部Account对象共享的,并且这个对象是Java虚拟机在加载Account类的时候建立的,因此咱们不用担忧它的惟一性。使用Account.class做为共享的锁,咱们就无需在建立Account对象时传入了。
这样新的问题就出来了虽然用Account.class做为互斥锁,来解决银行业务里面的转帐问题,虽然这个方案不存在 并发问题,可是全部帐户的转帐操做都是串行的,例如帐户A转帐户B、帐户C转帐户D这两个转帐操做现实 世界里是能够并行的,可是在这个方案里却被串行化了,这样的话,性能太差。因此若是考虑并发量这种方法也不行的
正确的写法是这样的(使用细粒度锁):
示例五:
public class Account { /** * 帐⼾余额 */ private Integer balance; /** * 转帐 */ void transfer(Account target,int amt){ //锁定转出帐户 synchronized(this){ //锁住转入帐户 synchronized(target){ if (this.balance > amt) { this.balance -= amt; target.balance += amt; } } } } }
咱们试想在古代,没有信息化,帐户的存在形式真的就是一个帐本,并且每一个帐户都有一个帐本,这些帐本 都统一存放在文件架上。银行柜员在给咱们作**转帐时,要去文件架上把转出帐本和转入帐本都拿到手,而后作转帐。**这个柜员在拿帐本的时候可能遇到如下三种状况:
- 文件架上刚好有转出帐本和转入帐本,那就同时拿走;
- 若是文件架上只有转出帐本和转入帐本之一,那这个柜员就先把文件架上有的帐本拿到手,同时等着其 他柜员把另一个帐本送回来;
- 转出帐本和转入帐本都没有,那这个柜员就等着两个帐本都被送回来。
若是有客户找柜员张三作个转帐业务:帐户 A转帐户B 100元,此时另外一个客户找柜员李四也作个转帐业务:帐户B转帐户A 100元,因而张三和李四同时都去文件架上拿帐本,这时候有可能凑巧张三拿到了帐本A,李四拿到了帐本B。张三拿到帐本A后就等着 帐本B(帐本B已经被李四拿走),而李四拿到帐本B后就等着帐本A(帐本A已经被张三拿走),他们要等 多久呢?他们会永远等待下去…由于张三不会把帐本A送回去,李四也不会把帐本B送回去。咱们姑且称为死等吧。
只要破坏其中一个就能够避免死锁
用synchronized实现等待-通知机制
- synchronized 配合wait(),notif(),notifyAll()这三个方法可以轻松实现.
- wait(): 当前线程释放锁,进入阻塞状态
- notif(),notifAll(): 通知阻塞的线程有能够继续执行,线程进入可执行状态
- notif()是会随机地地通知等待队歹一个线程
- notifyAll()会通知等待队列中的全部线程,建议使用notifAll()
wait与sleep区别:
sleep是Object的中的方法,wait是Thread中的方法
wait会释放锁,sleep不会释放锁
wait须要用notif唤醒,sleep设置时间,时间到了唤醒
wait无需捕获异常,而sleep须要
wait(): 当前线程进入阻塞
**** 码字不易若是对你有帮助请给个关注****
**** 爱技术爱生活 QQ群: 894109590****