文章目录
一、锁
1、悲观锁与乐观锁
(1)悲观锁
- 对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁
(2)乐观锁
- 在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。Java中,典型的乐观锁是CAS,Java原子类中的递增操作就通过CAS自旋实现的
(3)CAS
- compare and swap(比较与交换),是一种有名的无锁算法。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。一般情况下是一个自旋操作,即不断的重试
2、公平锁与非公平锁
(1)公平锁
- 指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁
(2)非公平锁
- 多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。
(3)优缺点
- 优点:
- 公平锁:等待锁的线程不会饿死
- 非公平锁:可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程
- 缺点:
- 公平锁:整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大
- 非公平锁:处于等待队列中的线程可能会饿死,或者等很久才会获得锁
二、synchronized方法锁、对象锁、类锁
1、概念
方法锁:类中非静态方法上的锁。注意:方法锁是对象锁,但是对象锁不一定是方法锁
对象锁:类中非静态方法上的锁或者用this或者某一个对象做锁。方法锁也是对象锁
类锁:类中静态方法上的锁或者用XXX.class做锁
2、synchronized的参数放入对象和Class有什么区别
(1)锁住的对象不同
- 成员方法锁住的实例对象,静态方法锁住的是Class
(2)访问控制不同
- 如果锁住的是实例,只会针对同一个对象方法进行同步访问,多线程访问同一个对象的synchronized代码块是串行的,访问不同对象是并行的。如果锁住的是类,多线程访问的不管是同一对象还是不同对象的synchronized代码块是都是串行的
三、synchronized的优化
四、volatile、synchronized、Lock之间的区别
volatile:具有可见性、有序性,不具有原子性。
synchronized和Lock:具有可见性、原子性,不具有有序性
五、锁的方法
1、wait、notify和notifyAll方法
wait、notify以及notifyAll都是Object对象的方法,他们必须在被synchronized同步的方法或代码块中调用,否则会报错。
wait:会使该线程进入等待状态(阻塞状态)。
notify:在所有等待线程中随机唤醒一个线程,让它获得锁。
notifyAll:唤醒所有等待的线程,让它们一起竞争锁,最后其中之一获得锁。
注意:notify()或者notifyAll()方法并不是真正释放锁,必须等到synchronized方法或者语法块执行完才真正释放锁
2、join和yield的用法
join:可以使得一个线程在另一个线程结束后再执行。
yield:可以让当前线程从“运行状态”进入到“就绪状态”,然后让所有线程去获取执行权,包括它自己本身。