多线程(四):锁

当我们修改多个线程共享的实例时,实例就会失去安全性,所以我们应该仔细找出实例状态不稳定的范围,将这个范围设为临界区,并对临界区进行保护,使其只允许一个线程同时执

当我们修改多个线程共享的实例时,实例就会失去安全性,所以我们应该仔细找出实例状态不稳定的范围,将这个范围设为临界区,并对临界区进行保护,使其只允许一个线程同时执行。Java使用synchronized或Lock来定义临界区,保护多个线程共享的字段。

实例的全局变量(共享资源)被修改时,会出现线程安全,需要对修改的方法加锁,注意:只要要访问多个线程共享的字段的方法都需要加锁保护。不能前门(方法A)锁上了,后门(方法B)就不锁了,或者窗户(方法C)开着了,这都不行,都必须锁住。
临界区:只允许单个线程执行的程序范围。

一:synchronized

synchronized是Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码,也就是保证synchronized内的代码块同步执行,而不会并行执行。

synchronized可以作为代码块使用,也可以直接放在方法(对象方法、静态方法)的签名上。作为代码块必须指定一个对象锁,对象锁就是一个对象,可以是this对象(即调用该方法的实例对象)也可以是自己创建的某个对象,也可以是某个类的字节码对象,synchronized修饰的代码属于原子操作,不可再分割!

// 实例方法使用的是this锁,即调用该对象的实例
synchronized(this) {// code
}Object object = new Object();
synchronized(object) {// code
}// 静态方法是使用的类锁
synchronized(Xxx.class) {// code
}// 对象锁(修饰对象方法,锁为this,即调用该方法的实例对象)
public synchronized void foo() {
}// 类锁(修饰静态方法,锁为这个类,不是某一个对象)
public synchronized static void bar() {
}

当多个线程过来同时执行,如何保证同一时刻只有一个线程在执行(即同步执行)?

synchronized无论作为代码块还是直接修饰方法都会指定一个锁,当一个线程要想执行线程体就必须拿到这个锁,只有拿到锁的线程才能执行,这样只有一个线程能拿到锁,其他线程就拿不到锁,这样其它线程就执行不了,就必须等待,当拿到锁的那个线程执行完就去释放这个锁,这样其它等待的线程就去抢这个锁,哪个线程抢到了锁就去执行,抢不到就继续下一轮的抢。锁分为对象锁和类锁。

在用synchronized关键字的时候,尽量缩小代码块的范围,能在代码段上加同步就不要再整个方法上加同步。减小锁的粒度,使代码更大程度的并发。

1. 对象锁

public class ThisLock {public static void main(String[] args) {MyRunnable runnable = new MyRunnable();System.out.println("runnable=" + runnable);new Thread(runnable, "Thread-A").start();new Thread(runnable, "Thread-B").start();}
}class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + this);synchronized (this) {for(int i = 0; i < 5; i++) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() +  "\ti=" + i);try { Thread.sleep(1000); } catch (InterruptedException e) { }}}}
}

这里写图片描述

通过打印结果可以看到this锁就是当前的线程对象runnable,可以看到代码块外的代码是并发执行的,而代码块内部的代码是同步执行的,即同一时刻只有一个线程在执行同步代码块。Thread-A先拿到锁就先执行,直到执行完毕,释放锁,然后Thread-B拿到锁就执行。

示例代码中两个线程都是使用同一个runnable对象,如果两个线程各自使用各自的MyRunnable对象,则是并发的,不会是同步的。

// new Thread(runnable, "Thread-A").start();
// new Thread(runnable, "Thread-B").start();// 锁无效,因为每个new就是一个新的对象,两个线程使用两个不同的锁,达不到互斥的效果
new Thread(new MyRunnable(), "Thread-A").start();
new Thread(new MyRunnable(), "Thread-B").start();

2. 修饰对象方法

public class MethodLock {public static void main(String[] args) {MyRunnable runnable = new MyRunnable();System.out.println("runnable=" + runnable);new Thread(runnable, "Thread-A").start();new Thread(runnable, "Thread-B").start();}
}class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + this +  "\trunning...");myRun();}private synchronized void myRun() {for(int i = 0; i < 4; i++) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() +  "\ti=" + i);try { Thread.sleep(1000); } catch (InterruptedException e) { }}}
}

这里写图片描述

synchronized修饰方法,表示将会锁住整个方法体,锁的是当前对象,这个synchronized(this)代码块包括住方法的所有代码是一样的效果

// 两种方式效果一样
synchronized(this) {for(int i = 0; i < 4; i++) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() +  "\ti=" + i);try { Thread.sleep(1000); } catch (InterruptedException e) { }}
}private synchronized void myRun() {for(int i = 0; i < 4; i++) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() +  "\ti=" + i);try { Thread.sleep(1000); } catch (InterruptedException e) { }}
}

3. 类锁

public class ClassLock {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {ExampleService.test();}}, "Thread-A").start();new Thread(new Runnable() {@Overridepublic void run() {ExampleService.test2();}}, "Thread-B").start();}
}class ExampleService {// 静态方法public synchronized static void test() {for(int i = 0; i < 4; i++) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() +  "\ti=" + i);try { Thread.sleep(500); } catch (InterruptedException e) { }}}// 静态方法public synchronized static void test2() {for(int i = 0; i < 4; i++) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() +  "\ti=" + i);}}
}

这里写图片描述

两个线程分别调用不同的静态方法,两个方法是同步执行,而不是并发执行。因为两个线程都是使用的类锁ExampleService.class, 当Thread-A执行时拿到了ExampleService的类锁,那么Thread-B就拿不到这个类锁了,只有Thread-A执行完毕后释放类锁,Thread-B才能拿到类锁继续执行,所以是同步执行的。

4. 对象锁和类锁

public class ObjectClassLock {public static void main(String[] args) {TestService testService = new TestService();new Thread(new Runnable() {@Overridepublic void run() {testService.test();}}, "Thread-A").start();new Thread(new Runnable() {@Overridepublic void run() {testService.test2();}}, "Thread-B").start();}
}class TestService {// 锁的是testService这个对象public synchronized void test() {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t running...");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println((new Date() + "\t" + Thread.currentThread().getName() + "\t over"));}// 静态是锁的TestService这个类(TestService.class)// 对象锁和类锁不是同一个锁,所以两个方法不会互斥public synchronized static void test2() {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t running...");System.out.println((new Date() + "\t" + Thread.currentThread().getName() + "\t over"));}
}

这里写图片描述

Thread-A是访问的对象方法,锁为当前调用者即锁对象为testService,Thread-B虽然是通过对象调用的但是是访问的类方法,锁为类锁,即TestService.class, 因两个线程使用的是不同的锁,自然就互不影响,各自执行各自的,所以是并发执行的,而不是同步执行的。

5. 卖火车票示例

public class TrainTicketTest {int num = 2;public static void main(String[] args) {// 抢火车票功能TrainTicketTest synchronizedTest = new TrainTicketTest();for (int i = 0; i < 5; i ++) {new Thread(synchronizedTest.new TrainTicketThread()).start();}}class TrainTicketThread implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "\trunning");// 模拟业务耗时的时间try { Thread.sleep(500);} catch (InterruptedException e) { }if (num > 0) {System.out.println(Thread.currentThread().getName() + "\t抢到一张票\t目前还有" + num + "张\t");int temp = num - 1;num = temp;System.out.println(Thread.currentThread().getName() + "还剩余" + num + "张");}}}
}

因多线程可能每次运行效果都不同,这里给出一种效果,总共2张票,卖出去3张,卖超了
这里写图片描述

根据上面的运行结果试图对多线程代码执行分析(仅仅是自己的猜测)
这里写图片描述

出现多卖的原因是由于多线程CPU时间片“随机”的切换,同一段代码被多个线程同时执行,导致值的错乱,解决这种问题的办法就是同一段代码同一时间不允许多个线程执行,也就是只能允许一个线程来执行。
我们可以为代码加上一个锁,哪个线程想要执行这段代码必须手里拿着锁才能执行,当执行完毕就把锁丢出去,其它线程如果想要执行就必须抢到锁(注意多个线程必须使用同一个锁),只有抢到锁了才能执行加锁的代码,为代码加锁有两种方式synchronized和lock,锁就是一个对象。

public class TrainTicketTest {// 全局变量,必须保证所有线程都在使用同一个锁Object object = new Object();int num = 2;public static void main(String[] args) {// 抢火车票功能TrainTicketTest synchronizedTest = new TrainTicketTest();for (int i = 0; i < 5; i ++) {new Thread(synchronizedTest.new TrainTicketThread()).start();}}class TrainTicketThread implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "running");try { Thread.sleep(500);} catch (InterruptedException e) { }synchronized (object) {if (num > 0) {System.out.println(Thread.currentThread().getName() + "\t抢到一张票\t目前还有" + num + "张\t");int temp = num - 1;num = temp;System.out.println(Thread.currentThread().getName() + "还剩余" + num + "张");}}}}
}
public class TrainTicket2Test {int num = 2;public static void main(String[] args) {// 抢火车票功能TrainTicket2Test synchronizedTest = new TrainTicket2Test();for (int i = 0; i < 5; i ++) {new Thread(synchronizedTest.new TrainTicketThread()).start();}}class TrainTicketThread implements Runnable {@Overridepublic void run() {getTrainTicket();}}private synchronized void getTrainTicket() {System.out.println(Thread.currentThread().getName() + "running");try { Thread.sleep(500);} catch (InterruptedException e) { }if (num > 0) {System.out.println(Thread.currentThread().getName() + "\t抢到一张票\t目前还有" + num + "张\t");int temp = num - 1;num = temp;System.out.println(Thread.currentThread().getName() + "还剩余" + num + "张");}}
}

三个人在不停的过一个门,一个门一次只能通过一个,每通过一个人就记录通过的总人数及最后通过的人的姓名和地址。

Gate: 模拟门类

public class Gate {private int counter = 0;private String name = "Noboday";private String address = "Nowhere";public synchronized void pass(String name, String addrees) {this.counter++;this.name = name;this.address = addrees;check();}private void check() {if (name.charAt(0) != address.charAt(0)) {System.out.println("****BROKEN****" + toString());}}@Overridepublic synchronized String toString() {return "No." + counter + ":" + name + "," + address;}
}

UserThread: 模拟人不停的过门

public class UserThread extends Thread {private final Gate gate;private final String myName;private final String myAddress;public UserThread(Gate gate, String myName, String myAddress){this.gate = gate;this.myName = myName;this.myAddress = myAddress;}@Overridepublic void run() {System.out.println(myName + "BEGIN");while (true) {gate.pass(myName, myAddress);}}
}

Main: 三个人不停的过门,为了便于区分,每个人的姓名和地址首字母是相同的

public class Main {public static void main(String[] args) {Gate gate = new Gate();// 3个人过的是同一个门gate,即synchronized修饰方法=synchronized(this)=synchronized(gate)// 3个人是使用的同一把gate锁new UserThread(gate, "Alice", "Alaska").start();new UserThread(gate, "Bobby", "Brazil").start();new UserThread(gate, "Chris", "Canada").start();}
}

去掉Gate类中的pass和toString方法的synchronized,运行效果如图
这里写图片描述

这里写图片描述

  • Alice, Canada: Thread-0先执行this.name = name, 此时name=Chris, 此时CPU切换到Thread-1,然后执行了this.name = name; this.address = address; 此时name=Alice, address= Alaska; 此时CPU切换到Thread-0的address赋值,此时address=Canada, 所以最终的name=Alice,address=Canada, 两个值状态不一致。

  • Bobby,Brazil: 假如Thread-0是Bobby,假如Thread-0先执行this.name = name; 此时name=bobby;此时CPU切换到Thread-1, 假如Thread-1是Chris,Thread分别执行了给name和address分别赋值,此时name=Chris,address=Canada, 然后CPU切回到Thread-0,执行this.address=address, 此时adress为Brazil, 接着Thread-0又执行完了check()方法,此时CPU又切到Thread-1执行,开始执行check()方法,但是这个方法并没有一下子执行完,而是执行到if(name.charAt(0) != address.charAt(0))这个判断的时候又切到Thread-0了,注意此时没有没有执行if体中的代码,只是仅仅执行了条件判断,此时还没有进入if体就被切走了,切到Thread-0执行this.name=name,此时name=Bobby,此时又被切到Thread-1,执行刚才的if体,一打印,结果name=Bobby,address=Brazil, 这种问题的出现就是if判断和if体分开执行了,当再次执行if体的时候结果字段的值改变了

为什么toString方法也加锁?
所有访问或者操作多线程共享的全局变量时都应该加锁。


6. 生产一个消费一个示例

生产一个就要消费一个,消费不完不能生产。此示例生产者和消费者线程使用的是同一个对象锁person

wait和notify通常操作的是锁对象

  • wait的作用:暂停线程执行,将当前线程的状态置为阻塞状态,并释放锁
  • notify的作用:随机将调用notify方法的对象所对应的线程的状态置为就绪状态
public class MQExample {public static void main(String[] args) {Person person = new Person();new Thread(new Producer(person)).start();new Thread(new Consumer(person)).start();}
}class Producer implements Runnable {private Person person;public Producer(Person person) {this.person = person;}@Overridepublic void run() {synchronized (person) {int i = 0;while (true) {if (i % 2 == 0) {person.setName("小红");person.setGender("女");} else {person.setName("小明");person.setGender("男");}System.out.println(new Date() + "\t生产者生产了一个对象【" + person.getName() + ", " + person.getGender() +"】");i++;// 当生产完一个要释放锁try {person.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("----------------------------------------------------------");person.notify();}}}
}class Consumer implements Runnable {private Person person;public Consumer(Person person) {this.person = person;}@Overridepublic void run() {synchronized (person) {while (true) {System.out.println(new Date() + "\t消费者消费了一个对象《" + person.getName() + ", " + person.getGender() + "》");person.notify();try {person.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}
}class Person {private String name;private String gender;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}
}

这里写图片描述

该代码比较绕,进行分析一下

  1. 首先启动Producer生产者线程,进入run()方法拿到person锁,执行第一次while循环,打印【小红, 女】,执行person.wait()暂停当前线程执行,释放person对象锁
  2. 因Producer生产者线程在执行person.wait()释放了锁,而且生产者线程还被阻塞了,此时CPU该执行消费者线程Consumer,消费者线程能拿到person对象锁,就可以进入同步代码块从而执行第一个循环打印《小红, 女》,接下来执行person.notify() 会唤醒生产者线程Producer,将生产者线程的状态从阻塞状态设置为就绪状态,再接下来执行消费者的person.wait(),这行代码让消费者线程暂停执行,同时释放person锁
  3. 消费者因执行person.wait()导致消费者线程释放了person对象锁而且使消费者暂停执行,从而CPU就有机会执行生产者线程了,生成者能拿到person对象锁,而且当前状态为就绪状态,就有资格进入运行状态,此时生产者线程会接着上次没有执行完的代码继续执行,所以就执行了打印“——-”,接着执行生产者线程中的person.notify();此行代码的作用是让消费者置为就绪状态,此时因为生产者还持有锁,虽然消费者线程置为就绪状态,因拿不到锁,所以不具备运行的条件。当执行完生产者的person.notify();这行代码时,此时第一轮循环就结束了,接着继续循环第二轮,就继续打印【小明, 男】,然后执行person.wait()暂停当前线程执行,释放person对象锁,就这样步骤2,步骤3 一下循环下去执行

注意:Object.wait()和Object.notify() 只能用在同步方法或者同步代码块中synchronized


二:Lock

Lock需要手动加锁,释放锁也需要手动释放,释放锁最好(必须)放到finally代码块中释放。
注意:同一个锁可以加锁多次(调用多次lock() ),相应的加N次锁也必须解N次锁(unlock()),一般情况下都是加一次锁和解一次锁

package java.util.concurrent.locks;public interface Lock {// 加锁void lock();void lockInterruptibly() throws InterruptedException;boolean tryLock();boolean tryLock(long time, TimeUnit unit) throws InterruptedException;// 释放锁void unlock();Condition newCondition();
}

重入锁

package java.util.concurrent.locks;
public class ReentrantLock implements Lock, java.io.Serializable {public boolean isLocked();
}

读写锁

package java.util.concurrent.locks;
public interface ReadWriteLock {// 读锁Lock readLock();// 写锁Lock writeLock();
}

ReentrantLockTest

public class ReentrantLockTest {public static void main(String[] args) {Lock lock = new ReentrantLock();Runnable runnable = () -> {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\trunning...");lock.lock();try {for(int i = 0; i < 4; i++) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() +  "\ti=" + i);try { Thread.sleep(1000); } catch (InterruptedException e) { }}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}};new Thread(runnable, "Thread-A").start();new Thread(runnable, "Thread-B").start();}
}

这里写图片描述

使用ReentrantLock和使用synchronized 代码块都能达到上锁的目的。先执行Thread-A线程,当指定到lock.lock()当前线程就拿到锁,然后执行线程体,最后执行lock.unlock()释放锁,这样Thread-B就能拿到线程继续执行。

public class ReadWriteLockTest {private volatile static Map cacheMap = new HashMap<>();private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();public static void main(String[] args) {// 写的时候不能读,读的时候不能写,写的时候不能写,读的时候可以读new Thread(() -> {for(int i = 0;i < 5; i++) {put(i + "", i);}}).start();new Thread(() -> {for(int i = 0; i < 5; i++) {get(i + "");}}).start();new Thread(() -> {for(int i = 0;i < 5; i++) {put(i + "", i);}}).start();}public static Object put(String key, Object value) {try {writeLock.lock();System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t写\t" + key + "=" + value + "\tstart...");try { Thread.sleep(1000); } catch (InterruptedException e) { }cacheMap.put(key, value);System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t写\t" + key + "=" + value + "\tend");} catch (Exception e) {} finally {writeLock.unlock();}return value;}public static Object get(String key) {try {readLock.lock();System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t读\t" + key + "\tstart...");try { Thread.sleep(1000); } catch (InterruptedException e) { }Object value = cacheMap.get(key);System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t读\t" + key + "\tend");return value;} catch (Exception e) {} finally {readLock.unlock();}return null;}
}

这里写图片描述


Object.wait和notify只能用于synchronized,如果想使用Lock就需要使用Condition来代替

public interface Condition {// 等待,相当于Object.wait()void await() throws InterruptedException;long awaitNanos(long nanosTimeout) throws InterruptedException;boolean await(long time, TimeUnit unit) throws InterruptedException;// 唤醒,相当于Object.notify()void signal();  void signalAll();
}

ConditionTest

public class ConditionTest {public static void main(String[] args) throws InterruptedException {// 多个线程必须使用相同的锁和条件Lock lock = new ReentrantLock();Condition condition = lock.newCondition();System.out.println(condition);new Thread(new MyRunnable(lock, condition), "Thread-A").start();Thread.sleep(1000);new Thread(new MyRunnable2(lock, condition), "Thread-B").start();}
}class MyRunnable implements Runnable {private Lock lock;private Condition condition;public MyRunnable(Lock lock, Condition condition) {this.lock = lock;this.condition = condition;}@Overridepublic void run() {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t开始等待>>>");lock.lock();try {condition.await();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t已 苏 醒^_^");}
}class MyRunnable2 implements Runnable {private Lock lock;private Condition condition;public MyRunnable2(Lock lock, Condition condition) {this.lock = lock;this.condition = condition;}@Overridepublic void run() {lock.lock();try {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t开始通知...");condition.signal();System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t通知完成。。。");} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}
}

这里写图片描述

三:synchronized与Lock的区别

死锁

一个线程中有多个锁,多个线程同时执行,可能线程1获得A锁,线程2获得了B锁,然后线程A想得到B锁就一直等待有人释放,线程2想得到Asuo就等待两个人一直等待,两个线程各自持有一个,而且只有某个线程同时拥有AB锁才能执行,两个人都只得到一个,却谁都不愿意释放,导致两个线程都在等待。
就像一个门同时需要两把钥匙同时打开,张三拿一把,李四拿一把,他们两个谁都不把自己的那把给对方,张三说“李四你先把你的那把给我,我用户就还你”,李四说“张三你先把你的那把给我,我用户就还你”,两个人都比较固执,结果两个人一直僵持着

public class DeadLockTest {public static String a = "a";public static String b = "b";public static void main(String[] args) {new Thread(new MyTask0()).start();new Thread(new MyTask1()).start();}
}class MyTask0 implements Runnable {@Overridepublic void run() {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t running...");synchronized (DeadLockTest.a) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t first synchronized ...");try { Thread.sleep(1000); } catch (InterruptedException e) { }synchronized (DeadLockTest.b) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t second synchronized ...");}}}
}class MyTask1 implements Runnable {@Overridepublic void run() {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t running...");synchronized (DeadLockTest.b) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t first synchronized ...");try { Thread.sleep(1000); } catch (InterruptedException e) { }synchronized (DeadLockTest.a) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t second synchronized...");}}}
}

这里写图片描述
两个线程的第二个代码块都没有执行,main方法也一直在运行状态,没有结束。

悲观锁和乐观锁

在查询的sql上加for update 子句,每次查询时会对表或者行加锁,事务提交后会释放锁

public class DatabaseLock {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {new OrderService().updateOrder();}}).start();}
}class OrderService {@Transactionalpublic void updateOrder() {// for update 每次查询时会对行加锁,事务提交后会释放锁System.out.println("select * from tbl_order where id = 1 for update");System.out.println("update tbl_order set status = 1 where id = 1");System.out.println("update tbl_payment set amount = 6 where id = 1");}
}

乐观锁:在表上加一个version字段,当update操作是即给version自增也同时将version作为查询条件,如果update操作受影响的行数大于0就执行其它业务逻辑

乐观锁原理伪代码:

public class DatabaseLock {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {new OrderService().updateOrder();}}).start();}
}class OrderService {@Transactionalpublic void updateOrder() {String order = "select id, version from tbl_order where id = 1";String affectedRows = "update tbl_order set status = 1, version = version + 1 where id = 1 and version = #{order.version}";if (affectedRows > 0) {System.out.println("update tbl_payment set amount = 6 where id = 1");} else {// error}}
}

悲观锁每次查询就会加锁,这样同一时刻只能有一个数据库连接操作,效率非常低。开发一般都使用乐观锁。