悲观锁和乐观锁
- 悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改;Java中,synchronized关键字和Lock的实现类都是悲观锁
- 乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据,则报错或者重试;Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的
- 适用场景:悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确;乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升;
悲观锁1
public synchronized void add() {
//同步操作
}
悲观锁2
private ReentrantLock lock = new ReentrantLock();
public void add() {
lock.lock();
//同步操作
lock.unlock();
}
乐观锁
private AtomicInteger atomicInteger = new AtomicInteger();
public void add() {
atomicInteger.incrementAndGet();// 执行自增1
}
其他注意点
- 实例方法使用synchronized锁定的是当前实例
- 静态方法使用synchronized锁定的是当前类的所有实例
- synchronized(obj)同步代码块使用obj对象的监视器实现同步
synchronized关键字和Lock对象的选择
- 最好两个都不用,使用一种java.util.concurrent包提供的机制
- 如果synchronized关键字能满足用户的需求,就用synchronized
volatile
- 线程间可见
多线程情况下,内存和寄存器中的值不一定一样,每次线程要访问volatile修饰的变量时都是从内存中读取,而不是存寄存器中读取,因此每个线程访问到的变量值都是一样的 - 禁止指令重排序
单例模式中double check lock一定要配合volatile使用
ThreadLocal
使用ThreadLocal管理变量,则多线程情况下,每一个线程拥有一个该变量的副本,更改后不影响其他线程;简而言之,ThreadLocal用空间获取了时间,而同步锁用时间换取了空间
wait、notify
- Object类有两个监视器相关的方法wait、notify
- synchronized和wait、notify同时使用,wait、notify必须在synchronized中
/*
* 使用wait、notifyAll实现生产者消费者
*/
public class ProducerConsumer {
public static void main(String[] args) {
ProCon proCon = new ProCon();
//生产者
new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 20; i++) {
proCon.pro(i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
}
}
}).start();
//消费者1
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
proCon.con();
Thread.sleep(3000);
}
} catch (InterruptedException e) {
}
}
}).start();
//消费者2
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
proCon.con();
Thread.sleep(3000);
}
} catch (InterruptedException e) {
}
}
}).start();
}
}
class ProCon {
List<Integer> listProduct = new ArrayList<Integer>();
public synchronized void pro(int pro) throws InterruptedException {
if (listProduct.size() > 100) {
System.out.println(getPrefix() + "库存已满,请稍候再生产");
wait();
}
listProduct.add(pro);
System.out.println(getPrefix() + "生产了:" + pro);
notifyAll();// 通知消费者获取产品
}
public synchronized void con() throws InterruptedException {
if (listProduct.size() <= 0) {
System.out.println(getPrefix() + "库存不足,请及时补货");
wait();
}
Integer cur = listProduct.get(0);
listProduct.remove(cur);
System.out.println(getPrefix() + "取走:" + cur + " 剩余库存:" + listProduct.toString());
notifyAll();// 通知生产者生产产品
}
private String getPrefix() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ").format(new Date()) + Thread.currentThread().getName() + " ";
}
}