一、并发控制的方法
1、悲观锁:常用的互斥锁都属于悲观锁,一个线程访问共享资源时其他线程不能访问。
2、乐观锁:允许同时访问共享数据,只有在提交时利用如版本号检查是否有冲突,应用github。
3、什么时候用乐观锁、什么时候用悲观锁?
写操作比较多的时候悲观锁,读操作多用乐观锁。再就是一个事务执行时间很长时用乐观锁,冲突很多时考虑悲观锁。
二、什么是自旋锁?
正常的锁当一个线程拿不到共享资源时,会把该线程阻塞,等共享资源释放后,再唤醒该阻塞线程并进行调度;
自旋锁当一个线程拿不到共享资源时,它就一直while循环来询问该资源释放了没有,会一直占用cpu资源。
适用情况:如果大多事务的执行时间很短,自旋锁空转一会就能拿到资源,就用自旋锁,这时如果采用阻塞/唤醒思路的话,调度的时间消耗会比较大。
三、代码实现:
例1(leetcode1114按序打印):
方案1(synchronized关键字):
既可以实现自旋锁,也可以实现阻塞/唤醒思路,但要注意自旋锁时很容易死锁,所以synchronized包裹哪一部分代码考虑清楚,同时若出现“不可见”问题,考虑适用volatile关键字。
代码(自旋锁):
class Foo {
private volatile Integer flag = 0;
public Foo() {
}
public void first(Runnable printFirst) throws InterruptedException {
// 1、正常synchronized解决了“不可见问题”,
// 但因为这里flag!=0在synchronized代码块外边,
// 所以这里的flag用的是“线程存储空间中的值”,
// 不是主存中的最新值,所以使用volatile来定义flag变量;
// 2、另外synchronized(this)为什么不是synchronized(flag),
// 因为Integer flag是引用类型,改变flag的值,flag指向的存储空间就变了,
// 作为共享资源的变量存储空间是一直不能变的。
while (flag !=0) {}
synchronized(this) {
printFirst.run();
flag = 1;
}
}
public void second(Runnable printSecond) throws InterruptedException {
while (flag != 1) {}
synchronized(this) {
System.out.println(".." + flag);
printSecond.run();
flag = 2;
}
}
public void third(Runnable printThird) throws InterruptedException {
while (flag != 2);
synchronized(this) {
System.out.println(flag);
flag = 0;
printThird.run();
}
}
}
///
synchronized少包裹一些代码也行,如下:
class Foo {
private volatile Integer flag = 0;
public Foo() {
}
public void first(Runnable printFirst) throws InterruptedException {
while (flag !=0) {}
printFirst.run();
synchronized(this) {
flag = 1;
}
}
public void second(Runnable printSecond) throws InterruptedException {
while (flag != 1) {}
printSecond.run();
synchronized(this) {
flag = 2;
}
}
public void third(Runnable printThird) throws InterruptedException {
while (flag != 2);
printThird.run();
synchronized(this) {
flag = 0;
}
}
}
代码(阻塞/唤醒思路):
class Foo {
private Integer flag = 0;
public Foo() {
}
public void first(Runnable printFirst) throws InterruptedException {
// 可以用this,也可以另外定义一个一定不会被修改的Object,
// 但不能用flag,原因如上代码。
synchronized(this) {
while (flag !=0) {this.wait();}
printFirst.run();
flag = 1;
this.notifyAll();
}
}
public void second(Runnable printSecond) throws InterruptedException {
synchronized(this) {
while (flag != 1) {this.wait();}
printSecond.run();
flag = 2;
this.notifyAll();
}
}
public void third(Runnable printThird) throws InterruptedException {
synchronized(this) {
while (flag != 2) this.wait();
flag = 0;
printThird.run();
this.notifyAll();
}
}
}
方案2(Lock类):和synchronized关键字差不多,只不过是一个封装好的类,还是得借助volatile关键字实现解决可见性,下面代码是自旋锁思路,也可以用条件锁Condition类实现阻塞思路,如文章最后的链接中的方法(synchronized配合wait等方法 == Lock配合Condition,只不过前者更底层);
class Foo {
private volatile Integer flag;
private Lock lock = new ReentrantLock();
public Foo() {
flag = 0;
}
public void first(Runnable printFirst) throws InterruptedException {
while (flag != 0) {}
lock.lock();
printFirst.run();
flag = 1;
lock.unlock();
}
public void second(Runnable printSecond) throws InterruptedException {
while (flag != 1);
lock.lock();
printSecond.run();
flag = 2;
lock.unlock();
}
public void third(Runnable printThird) throws InterruptedException {
while (flag != 2);
lock.lock();
printThird.run();
flag = 0;
lock.unlock();
}
}
方案3(原子类AtomicInteger类):
也是既可以实现自旋锁,也可以实现阻塞唤醒,AtomicInteger不用=、!=、+等运算符,要调用get()方法、set()方法、incrementAndGet()等方法。
自旋锁代码:
class Foo { private AtomicInteger flag = new AtomicInteger(0); public Foo() { System.out.println(flag); } public void first(Runnable printFirst) throws InterruptedException { while (flag.get() != 0); printFirst.run(); flag.set(1); } public void second(Runnable printSecond) throws InterruptedException { while (flag.get() != 1); printSecond.run(); flag.set(2); } public void third(Runnable printThird) throws InterruptedException { while (flag.get() != 2); printThird.run(); flag.set(0); } }
阻塞/唤醒思路代码:
使用xx.wait、xx.notifyALL函数时,xx一定得对于所有线程可见。xx可以是另外定义的一个量,并配合synchronized关键字实现对所有线程可见,或者不用synchronized用volatile修饰一下也可以。对于AtomicInteger类 阻塞不好实现,wait、notifyAll还是配合synchronized关键字用。
方案4(信号量、计数器、阻塞队列等,这些方面实现阻塞/唤醒思路,同时因为这些工具类都是线程安全的,都能满足“可见性”,所以只要这些工具类能有类似AtomicInteger类get/set方法的方法,那也能实现自旋锁,不过我还没试过...),下面链接都属阻塞/唤醒思路的:
链接:力扣https://leetcode.cn/problems/print-in-order/solutions/488685/si-chong-bu-tong-de-shi-xian-fang-shi-zong-jie-by-/
end...