死锁
- synchronized锁机制
- 同步机制的关键字 synchronized,当我们使用了同步后,线程A占用了该共享资源,就会马上组织其他线程对共享资源的写方法,此时共享资源的使用权仅仅属于线程A,这样就可以避免线程安全问题
- **synchronized可以所得东西?锁的是什么?**
- synchronized锁方法:
- synchronized修饰静态方法:锁定的是该方法
- synchronized修饰非静态方法:锁定的是方法的调用者
- synchronized锁代码块
- 锁的是当前传入的对象,同时修饰代码块时,需要this关键字,表示的就是谁获取该对象,就对谁上锁
- 当被synchronized修饰的代码执行完成之后,会立即释放,允许别人访问
- sleep()和wait()
- 前者会让出CPU但是不会释放锁,后者相反,不会让出CPU但是会释放锁
- sleep() 方法是 Thread 类中的方法,而 wait() 方法是 Object 类中的方法
- sleep() 方法不会释放 lock,但是 wait() 方法会释放,而且会加入到等待队列中
- sleep() 方法不依赖于同步器 synchronized(),但是 wait() 方法 需要依赖 synchronized 关键字
- 线程调用 sleep() 之后不需要被唤醒(休眠时开始阻塞,线程的监控状态依然保持着,当指定的休眠时间到了就会自动恢复运行状态),但是 wait() 方法需要被重新唤醒(不指定时间需要被别人中断)
- 避免死锁:
- 避免一个线程同时获取多个锁。
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
- 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
- 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
Java并发机制底层的实现原理
其实,所有并发操作,要想不出现问题,都需要实现**写后读操作**,即
**一个线程想要改变某个变量,一定是要基于其他线程的有效修改之后再去读取,读取到优秀修改之后,自己再去进行修改**。(所有线程都要遵守这一条)
**锁(适合高并发)、CAS(Compare and Swap)(适合低并发)机制**、**设置一个中介(每次想要修改时都会先访问中介查看资源是否修改过)(适合线程间并发、进程间并发、服务期间并发)**就是保证了上述的写后读操作.
CAS机制(Compare and Swap)
CAS 全称为Compare And Swap ,翻译过来就是**比较并且交换**,它并没有加锁,但是可以保证读后写
- Synchronized 是**悲观锁**,线程一旦得到锁,其他的线程就只能挂起了
- 悲观锁:是真正的上锁,总是**假设最坏**的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁
- CAS的操作则是**乐观锁**,他认为自己一定会拿到锁,所以他会一直尝试,直到成功拿到为止;
- 乐观锁:不是真正的上锁,总是**假设最好**的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁
- 机制
- CAS 里面至少包含了两个动作,分别是比较和交换
- CAS操作是**原子操作**,要么全部完成,要么全部没有完成(由此保证了读后写操作)
- 因为是原子操作,所以我们无法自己编写实现,需要有操作系统帮助实现(CPU中的CAH指令)
- 步骤(例如 a + 1 的操作,a 默认=0):
- 在多个线程修改一个值 a 的时候,会将 a copy 一份到自己的线程内存空间中(预期值),此时预期值就是 a ,要修改的值就是 a+1 的结果,结果就是 1(要修改的值),由于是多个线程,所以每个线程内部都会得到 a = 1。
- 接着就会执行**比较并且交换**, 让线程中的预期值和主内存中的 a 进行比较,如果相同,就会提交上去,如果不相同,说明 a 的值已经被别的线程修改过了,所以就会提交失败(这个比较和提交的操作是原子性的)。提交失败之后,线程就会重新获取 a 的值,然后重复这一操作。这种重复操作的方式称为自旋
- 优点:在**低并发**时,效率会更高
- 原因:缺少了**上锁加锁**的性能开销
- 缺点:
- 不适合高并发的线程
- 原因:因为CAS并不会对资源进行上锁,所以当一个线程对其修改之后,其他线程进行比较,发现预期值与实际值不符,就会重新进行计算,这个过程需要耗费CPU资源但是却是无用功,所以并不适合高并发
- 另外,实现锁(头部加锁)也需要CAS
- Java头对象的MarkWord共64bit,上锁需要32bit,因为是多个线程想要上锁,那就会有并发问题,所以就要使用CAS来实现读后写,如下图所示
- - CAS在轻量级锁中是**一直自旋**,在重量级锁中只**自旋一次**,然后进入堵塞队列
- 自旋CAS实现的基本 思路就是循环进行CAS操作直到成功为止,如下图