模拟一个实例,用一个线程负责写数据,一个线程负责读数据,清单如下:
FileManager.java 负责读和写的具体实现 Main.java 主类,入口 TaskThread.java 自定义线程类 TaskRead.java 读任务 TaskWrite.java 写任务
FileManager.java
package com.iteye.badpie.javacode.thread.sync; import java.util.Random; public class FileManager { private Random mRandom; private String mName = "默认名字"; private String mEmail = "默认电子邮件"; public FileManager() { mRandom = new Random(); } public void write(String id, String name, String email) { mName = name; try { Thread.sleep(getRandomInt()); } catch (InterruptedException e) { e.printStackTrace(); } mEmail = email; System.out.println(String.format("写数据 id:%s, name:%s, email:%s", id, name, email)); } public void read(String id) { try { Thread.sleep(getRandomInt()); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(String.format("读数据 id:%s, name:%s, email:%s", id, mName, mEmail)); } private int getRandomInt() { return mRandom.nextInt(2000); } }
Main.java
package com.iteye.badpie.javacode.thread.sync; import com.iteye.badpie.javacode.thread.sync.TaskThread.ITask; public class Main { public static void main(String[] args) { System.out.println("主线程开始运行"); FileManager fileManager = new FileManager(); ITask taskWrite = new TaskWrite("w", fileManager); ITask taskRead = new TaskRead("r", fileManager); new TaskThread(taskWrite).start(); new TaskThread(taskRead).start(); System.out.println("主线程运行结束"); } }
TaskThread.java
package com.iteye.badpie.javacode.thread.sync; public class TaskThread extends Thread { private ITask mTask; public TaskThread(ITask task) { mTask = task; } @Override public void run() { mTask.execute(); } public interface ITask { public void execute(); } }
TaskRead.java
package com.iteye.badpie.javacode.thread.sync; import com.iteye.badpie.javacode.thread.sync.TaskThread.ITask; public class TaskRead implements ITask { private String mId; private FileManager mFileManager; public TaskRead(String id, FileManager fileManager) { mId = id; mFileManager = fileManager; } @Override public void execute() { for (int i = 0; i < 5; i++) { mFileManager.read(mId); } } }
TaskWrite.java
package com.iteye.badpie.javacode.thread.sync; import com.iteye.badpie.javacode.thread.sync.TaskThread.ITask; public class TaskWrite implements ITask { private String mId; private FileManager mFileManager; public TaskWrite(String id, FileManager fileManager) { mId = id; mFileManager = fileManager; } @Override public void execute() { for (int i = 0; i < 5; i++) { mFileManager.write(mId, i + "的名字", i + "的电子邮件"); } } }
某一次运行结果如下
主线程开始运行 主线程运行结束 写数据 id:w, name:0的名字, email:0的电子邮件 读数据 id:r, name:1的名字, email:0的电子邮件 写数据 id:w, name:1的名字, email:1的电子邮件 读数据 id:r, name:2的名字, email:1的电子邮件 读数据 id:r, name:2的名字, email:1的电子邮件 写数据 id:w, name:2的名字, email:2的电子邮件 读数据 id:r, name:3的名字, email:2的电子邮件 写数据 id:w, name:3的名字, email:3的电子邮件 读数据 id:r, name:4的名字, email:3的电子邮件 写数据 id:w, name:4的名字, email:4的电子邮件
分析一下结果,有明显的问题,写数据时,0的名字和0的电子邮件,1的名字和1的电子邮件等等,他们是一一对应的,但是在第一次读数据时就出现了数据错误,竟然把1的名字和0的电子邮件配对了。
可能导致此问题的原因是:在写数据时,当前数据只写了一半,就有读操作产生,于是就将本次的前半段数据(比如1的名字)和上一次的后半段数据(比如0的电子邮件)配对了。
解决此问题的方法是对写和读做同步,意思是说当我在写数据时就不允许读,在读数据时,不允许写,这样每次写或者读总是能够保证数据是统一的,配对是正确的。
java中的每一个实例对象都有一个锁,称之为lock,就像这样的场景:数据在屋子里,门上有一把锁,读数据的人和写数据的人都在门外排队,一个人进屋就将门反锁住,他读或者写完毕了打开门出来,再让下一个人进屋,这个人再将们反锁住,于是,这就能保持屋子里的数据始终是配对的,不会将第一个人的一半数据和第二个人的一半数据配对。
实现方式如下:
只需要对此处的FileManager做一个小小的改动就可以了(由synchronized关键字负责锁门)
package com.iteye.badpie.javacode.thread.sync; import java.util.Random; public class FileManager { private Random mRandom; private String mName = "默认名字"; private String mEmail = "默认电子邮件"; private Object mLock; public FileManager() { mRandom = new Random(); // 实例化一个锁 mLock = new Object(); } public void write(String id, String name, String email) { // 锁门 synchronized (mLock) { mName = name; try { Thread.sleep(getRandomInt()); } catch (InterruptedException e) { e.printStackTrace(); } mEmail = email; System.out.println(String.format("写数据 id:%s, name:%s, email:%s", id, name, email)); } // 打开门 } public void read(String id) { // 锁门 synchronized (mLock) { try { Thread.sleep(getRandomInt()); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(String.format("读数据 id:%s, name:%s, email:%s", id, mName, mEmail)); } // 打开门 } private int getRandomInt() { return mRandom.nextInt(2000); } }
再次执行,结果如下:
主线程开始运行 主线程运行结束 读数据 id:r, name:默认名字, email:默认电子邮件 读数据 id:r, name:默认名字, email:默认电子邮件 读数据 id:r, name:默认名字, email:默认电子邮件 读数据 id:r, name:默认名字, email:默认电子邮件 读数据 id:r, name:默认名字, email:默认电子邮件 写数据 id:w, name:0的名字, email:0的电子邮件 写数据 id:w, name:1的名字, email:1的电子邮件 写数据 id:w, name:2的名字, email:2的电子邮件 写数据 id:w, name:3的名字, email:3的电子邮件 写数据 id:w, name:4的名字, email:4的电子邮件
上面的锁也可以这样挂:
package com.iteye.badpie.javacode.thread.sync; import java.util.Random; public class FileManager { private Random mRandom; private String mName = "默认名字"; private String mEmail = "默认电子邮件"; public FileManager() { mRandom = new Random(); } public void write(String id, String name, String email) { // 锁门 对当前实例挂锁 synchronized (this) { mName = name; try { Thread.sleep(getRandomInt()); } catch (InterruptedException e) { e.printStackTrace(); } mEmail = email; System.out.println(String.format("写数据 id:%s, name:%s, email:%s", id, name, email)); } // 打开门 } public void read(String id) { // 锁门 synchronized (this) { try { Thread.sleep(getRandomInt()); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(String.format("读数据 id:%s, name:%s, email:%s", id, mName, mEmail)); } // 打开门 } private int getRandomInt() { return mRandom.nextInt(2000); } }
还可以这样挂锁:
package com.iteye.badpie.javacode.thread.sync; import java.util.Random; public class FileManager { private Random mRandom; private String mName = "默认名字"; private String mEmail = "默认电子邮件"; public FileManager() { mRandom = new Random(); } public synchronized void write(String id, String name, String email) { mName = name; try { Thread.sleep(getRandomInt()); } catch (InterruptedException e) { e.printStackTrace(); } mEmail = email; System.out.println(String.format("写数据 id:%s, name:%s, email:%s", id, name, email)); } public synchronized void read(String id) { try { Thread.sleep(getRandomInt()); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(String.format("读数据 id:%s, name:%s, email:%s", id, mName, mEmail)); } private int getRandomInt() { return mRandom.nextInt(2000); } }