概念:同一个对象被多个线程同时操作
案例2:不安全的取钱
sleep可以放大问题的发生性
不加延时可能不会出现问题,因为一开始第一个线程执行完了之后再执行第二个线程的话,第二个线程就拿到变化之后的资源进行判断了,加延时的话他们都拿到了未变化的资源。
那么怎么解决?
synchronized加在方法上做关键字修饰符不可以指定锁对象默认为this当前实例对象
程序执行完同步代码块会释放代码块。
程序在执行同步代码块是出现异常,JVM会自动释放锁去处理异常。
同步方法的弊端:
只有修改的需要锁,只读的不需要锁
案例1:同步买票
synchronized默认锁住当前类对象比如这个类对象名字叫BuyTicket
案例2:取钱
不能直接锁在run方法上
可以使用同步块,然后指定同步监视器(锁对象)
总结:
锁的话注意是锁的对象,默认是this,锁要锁增删改的对象,比如这个是银行
并发编程的包叫java.util.concurrent
集合建议都加泛型
解决办法:
不要像上面那样在一个同步代码块未执行完的时候又去拿另一个资源,这样不会释放第一个锁也拿不到第二个锁,可以把同步代码块放出去弄成同级的这样第一个的同步代码块执行完了之后会释放锁,然后给第二个线程去执行(白雪公主)
同步代码块:执行完才会释放锁
效率比synchronized好
但是Lock 内没有同步监视器(也就是锁的对象)
Lock同步的就是lock和unlock中间的代码段,这段代码块只能被一个线程获取,其他线程尝试获取会进入lock对象的等待队列
总结:同步synchroized,通讯wait notify
操作的是一个对象,生产者生产,消费者消费(互为依赖)
wait会释放锁
其他还有线程池和springboot @Async注解等
synchronize修饰方法的锁对象只能是this当前对象(锁的对象是方法的调用者),synchronize修饰代码块可以修改锁对象
同步方法锁的范围比较大,而同步代码块范围要小点,一般同步的范围越大,性能就越差,一般需要加锁进行同步的时候,肯定是范围越小越好,这样性能更好
非静态的同步方法是锁定类的实例(引用对象)的,而静态的同步方法是锁定类的;
也就是说,对于非静态的同步方法,在同一时刻,一个类的一个实例中,只有一个线程能进入同步的方法。但是对于多个实例,每一个实例的一个线程都可以进入同一同步的方法。
(具体看我文章JUC并发编程一)
3. lock锁和synchronize的区别
比较点 | lock | synchronize |
---|---|---|
所处层面 | 是Java中的一个接口,它有许多的实现类来为它提供各种功能 | Java中的一个关键字,当我们调用它时会从在虚拟机指令层面加锁 |
获得锁的方式 | Lock的使用离不开它的实现类AQS,而它的加锁并不是针对对象的,而是针对当前线程的,并且AQS中有一个原子类state来进行加锁次数的计数 | 可对实例方法、静态方法和代码块加锁,相对应的,加锁前需要获得实例对象的锁或类对象的锁或指定对象的锁。说到底就是要先获得对象的监视器(即对象的锁)然后才能够进行相关操作。 |
获锁失败 | 使用Lock加锁的程序中,获锁失败的线程会被自动加入到AQS的等待队列中进行自旋,自旋的同时再尝试去获取锁,等到自旋到一定次数并且获锁操作未成功,线程就会被阻塞 | 加锁的程序中,获锁失败的对象会被加入到一个虚拟的等待队列中被阻塞,直到锁被释放;1.6以后加入了自旋操作 |
释放锁 | 1、可调用ulock方法去释放锁比synchronized更灵活;2、还可以使用trylock(尝试获取锁)方法,意思是没等到释放就结束了; | 不能指定解锁操作,1、执行完代码块的对象会自动释放锁2、占有锁的线程发生了异常,此时JVM会让线程自动释放锁3、调用wait方法,释放锁进入wating状态 |
造成死锁 | 而Lock必须在finally中主动unlock锁,否则就会出现死锁 | synchronized在发生异常时,会自动释放掉锁,故不会发生死锁现(此时的死锁一般是代码逻辑引起的),比如:1、线程1(获得锁1,【一秒后获得锁2】),线程2(获得锁2,【一秒后获得锁1】),然后就会一直等待;2、线程1(获得锁,阻塞)、线程2(等待,傻傻的等)造成死锁 |
效率方面 | 比较高 | 比较低 |
是否判断获得锁 | 可以判断获得锁 | 无法判断获得锁的状态 |
重入锁 | 都是可重入锁,不可中断,非公平锁 | 可重入锁,可以判断锁什么时候中断,非公平(可自己设置) |
适用范围 | 适合锁少量的代码同步 | 适合锁大量的代码同步 |
1、 互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
2、 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
3、 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
4、 循环等待条件: 若干进程间形成首尾相接循环等待资源的关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。