JAVA之线程同步的三种方法
最近接触到一个图片加载的项目,其中有声明到的线程池等资源需要在系统中线程共享,所以就去研究了一下线程同步的知识,总结了三种常用的线程同步的方法,特来与大家分享一下。这三种方法分别是:synchronized代码段、synchronized修饰方法/类、ThreadLocal本地线程变量。
我们通过一个例子来表现这三种方法:一张银行卡里面有300块钱,15个线程从这张银行卡中取钱,每个线程取一次且每次取20块钱;当当前余额不足100元时,则向账户中汇款20元。三种方法每种方法都有5个线程。我们预期的结果是当15个线程都执行完毕,银行卡里面的余额应该显示为80。
准备工作
我们需要一个账户的实体类Account,其中有一个属性money。Account类作为账户类的父类,我们在后面将用三种方法分别生成一个子类来继承这个类。Account类的具体代码如下:
1 public abstract class Account { // 抽象类 2 protected static Account account; // Account类的实例,全局只有一个 3 protected static final int DEFAULT_MONEY = 300; // 账户中默认有300块钱 4 protected static int money; // 记录当前账户中的余额 5 6 // 获取账户实例的方法,由于是static方法不能定义为抽象方法,所以要在子类中重写这个方法 7 public static Account getInstance() { 8 return null; 9 } 10 11 // 抽象方法,设置当前账户中的余额,三种方法分别有不同的实现方法 12 public abstract void setMoney(int money); 13 14 // 获取当前账户中的余额 15 public int getMoney() { 16 return money; 17 } 18 }
我们可以在每种方法中都把线程要进行的工作都进行一遍,但是这样的话代码就会重用,所以我们将这段代码提取出来形成一个工具类MyRunnable(实现自Runnable接口),在这个类的run()方法中进行三种方法的所有线程都应该做的事情,比如取出当前账户中的余额、判断余额是否小于100、设置账户余额(汇款或取款)等。MyRunnable类中的代码如下:
1 public class MyRunnable implements Runnable { 2 private Account account; // 账户的父类声明,在构造方法中传入具体子类的引用 3 4 // 构造方法中传入Account类的子类,即该线程绑定的账户操作的方法 5 public MyRunnable(Account account) { 6 this.account = account; 7 } 8 9 @Override 10 public void run() { 11 String currentThreadName = Thread.currentThread().getName(); // 获取当前线程的名称 12 System.out.println(currentThreadName + " is running..."); 13 System.out.println(currentThreadName + ":before=" + account.getMoney()); // 打印账户当前的余额 14 if (account.getMoney() < 100) { // 如果账户当前的余额小于100,则向账户中汇款20元 15 account.setMoney(account.getMoney() + 20); 16 } else { // 如果账户余额还大于100,则取出20元 17 account.setMoney(account.getMoney() - 20); 18 } 19 System.out.println(currentThreadName + ":after=" + account.getMoney()); // 打印操作后账户的余额 20 } 21 }
下面贴出三种方法的代码。
第一种方法:SYNCHRONIZED修饰代码段的方法
1 public class AccountForSyn extends Account { 2 3 @SuppressWarnings("static-access") 4 private AccountForSyn() { 5 account.money = DEFAULT_MONEY; 6 } 7 8 public static AccountForSyn getInstance() { 9 if (account == null) { 10 // 这里为了防止两个线程同时访问account实例,所以在同步块的前后分别进行了一次判断 11 synchronized (AccountForSyn.class) { 12 if (account == null) { 13 account = new AccountForSyn(); 14 } 15 } 16 } 17 return (AccountForSyn) account; 18 } 19 20 @SuppressWarnings("static-access") 21 @Override 22 public void setMoney(int money) { // 设置account账户中的余额 23 /** 24 * 核心代码 25 */ 26 synchronized (AccountForSyn.class) { // SYNCHRONIZED后面的参数是一个Object类型的参数,可以是任意的Object(最好是共享的资源) 27 account.money = money; 28 } 29 } 30 }
第二种方法:SYNCHRONIZED关键字修饰方法的方法
1 public class AccountForSynM extends Account { 2 3 @SuppressWarnings("static-access") 4 private AccountForSynM() { 5 account.money = DEFAULT_MONEY; 6 } 7 8 public static AccountForSynM getInstance() { 9 if (account == null) { 10 synchronized (AccountForSyn.class) { 11 if (account == null) { 12 account = new AccountForSynM(); 13 } 14 } 15 } 16 return (AccountForSynM) account; 17 } 18 19 @SuppressWarnings("static-access") 20 @Override 21 /** 22 * 核心代码 23 */ 24 public synchronized void setMoney(int money) { 25 account.money = money; 26 } 27 }
第三种方法:ThreadLocal本地线程变量的方法
1 public class AccountForLocal extends Account { 2 /** 3 * ThreadLocal是本地线程存储变量,里面存储着所有线程共享的资源。 4 * ThreadLocal的工作原理与SYNCHRONIZED关键字不同: 5 * SYNCHRONIZED关键字是对代码块上锁,使一个线程可以从头到尾一次性的执行其中的代码 6 * ThreadLocal是对线程共享的资源进行多次备份,再分发给全部的线程,线程对数据进行修改后提交 7 */ 8 private static ThreadLocal<AccountForLocal> threadLocal = new ThreadLocal<AccountForLocal>(); // 本地线程存储变量ThreadLocal的声明 9 10 @SuppressWarnings("static-access") 11 private AccountForLocal() { 12 account.money = DEFAULT_MONEY; 13 } 14 15 public static AccountForLocal getInstance() { 16 account = threadLocal.get(); // 从ThreadLocal中获取线程共享资源 17 if (account == null) { 18 account = new AccountForLocal(); 19 /** 20 * 核心代码 21 */ 22 threadLocal.set((AccountForLocal) account); // 如果资源不存在,则实例化一个之后提交给ThreadLocal 23 } 24 return (AccountForLocal) account; 25 } 26 27 @SuppressWarnings("static-access") 28 @Override 29 public void setMoney(int money) { 30 account.money = money; 31 /** 32 * 核心代码 33 */ 34 threadLocal.set((AccountForLocal) account); 35 } 36 }
主函数的代码如下:
1 public class MainClass { // 主函数 2 public static void main(String[] args) { 3 // 定义15个线程 4 // SYNCHRONIZED修饰代码段的方法的五个线程 5 Thread thread11 = new Thread(new MyRunnable(AccountForSyn.getInstance()), "Thread A1"); 6 Thread thread12 = new Thread(new MyRunnable(AccountForSyn.getInstance()), "Thread A2"); 7 Thread thread13 = new Thread(new MyRunnable(AccountForSyn.getInstance()), "Thread A3"); 8 Thread thread14 = new Thread(new MyRunnable(AccountForSyn.getInstance()), "Thread A4"); 9 Thread thread15 = new Thread(new MyRunnable(AccountForSyn.getInstance()), "Thread A5"); 10 // ThreadLocal本地线程变量的方法的五个线程 11 Thread thread21 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread B1"); 12 Thread thread22 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread B2"); 13 Thread thread23 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread B3"); 14 Thread thread24 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread B4"); 15 Thread thread25 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread B5"); 16 // SYNCHRONIZED关键字修饰方法的方法的五个线程 17 Thread thread31 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread C1"); 18 Thread thread32 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread C2"); 19 Thread thread33 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread C3"); 20 Thread thread34 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread C4"); 21 Thread thread35 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread C5"); 22 23 // 启动15个线程 24 thread11.start(); 25 thread21.start(); 26 thread31.start(); 27 thread12.start(); 28 thread22.start(); 29 thread32.start(); 30 thread13.start(); 31 thread23.start(); 32 thread33.start(); 33 thread14.start(); 34 thread24.start(); 35 thread34.start(); 36 thread15.start(); 37 thread25.start(); 38 thread35.start(); 39 } 40 }
运行结果如下: