Java乐观锁的实现原理(案例)
简要说明:
表设计时,需要往表里加一个version字段。每次查询时,查出带有version的数据记录,更新数据时,判断数据库里对应id的记录的version是否和查出的version相同。若相同,则更新数据并把版本号+1;若不同,则说明,该数据发送并发,被别的线程使用了,进行递归操作,再次执行递归方法,知道成功更新数据为止
简单说说乐观锁。乐观锁是相对于悲观锁而言。悲观锁认为,这个线程,发生并发的可能性极大,线程冲突几率大,比较悲观。一般用synchronized实现,保证每次操作数据不会冲突。乐观锁认为,线程冲突可能性小,比较乐观,直接去操作数据,如果发现数据已经被更改(通过版本号控制),则不更新数据,再次去重复 所需操作,知道没有冲突(使用递归算法)。
因为乐观锁使用递归+版本号控制 实现,所以,如果线程冲突几率大,使用乐观锁会重复很多次操作(包括查询数据库),尤其是递归部分逻辑复杂,耗时和耗性能,是低效不合适的,应考虑使用悲观锁。
乐观锁悲观锁的选择:
乐观锁:并发冲突几率小,对应模块递归操作简单 时使用
悲观锁:并发几率大,对应模块操作复杂 时使用
package what21.thread.lock; public class OptimLockMain { // 文件版本号 static int version = 1; // 操作文件 static String file = "d://IT小奋斗.txt"; /** * 获取版本号 * * @return */ public static int getVersion(){ return version; } /** * 更新版本号 */ public static void updateVersion(){ version+=1; } /** * @param args */ public static void main(String[] args) { for(int i=1;i<=5;i++){ new OptimThread(String.valueOf(i),getVersion(),file).start(); } } }
package what21.thread.lock; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class OptimThread extends Thread { // 文件版本号 public int version; // 文件 public String file; public OptimThread(String name,int version,String file){ this.setName(name); this.version = version; this.file = file; } public void run() { // 1. 读取文件 String text = read(file); println("线程"+ getName() + ",文件版本号为:" + OptimLockMain.getVersion()); println("线程"+ getName() + ",版本号为:" + getVersion()); // 2. 写入文件 if(OptimLockMain.getVersion() == getVersion()){ println("线程" + getName() + ",版本号为:" + version + ",正在执行"); // 文件操作,这里用synchronized就相当于文件锁 // 如果是数据库,相当于表锁或者行锁 synchronized(OptimThread.class){ if(OptimLockMain.getVersion() == this.version){ // 写入操作 write(file, text); // 更新文件版本号 OptimLockMain.updateVersion(); return ; } } } // 3. 版本号不正确的线程,需要重新读取,重新执行 println("线程"+ getName() + ",文件版本号为:" + OptimLockMain.getVersion()); println("线程"+ getName() + ",版本号为:" + getVersion()); System.err.println("线程"+ getName() + ",需要重新执行。"); } /** * @return */ private int getVersion(){ return this.version; } /** * 写入数据 * * @param file * @param text */ public static void write(String file,String text){ try { FileWriter fw = new FileWriter(file,false); fw.write(text + " "); fw.flush(); fw.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 读取数据 * * @param file * @return */ public static String read(String file){ StringBuilder sb = new StringBuilder(); try { File rFile = new File(file); if(!rFile.exists()){ rFile.createNewFile(); } FileReader fr = new FileReader(rFile); BufferedReader br = new BufferedReader(fr); String r = null; while((r=br.readLine())!=null){ sb.append(r).append(" "); } br.close(); fr.close(); } catch (IOException e) { e.printStackTrace(); } return sb.toString(); } /** * @param content */ public static void println(String content){ System.out.println(content); } }
1、这里线程对文件写入的操作,就相当于修改表中的一条数据。
2、多个线程需要对这个文件写入时,同时只能有一个线程写入该文件。
3、同时只能有一个线程写入文件,就相当于与表的行锁,同一时间只允许一个线程操作。
4、线程初始化时,持有相同的版本号,写入文件操作完成后,然后变更版本号。
5、其他线程再次操作时,比较版本号,版本号较低的不在进行写入操作,或者抛出异常,或者再次执行该操作,这样就防止产生脏数据了。