• 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 }
    主函数代码

    运行结果如下:

  • 相关阅读:
    spring注解事务管理
    Spring切入点表达式常用写法
    JPA和事务管理
    maven仓库
    struts2拦截器
    js中 转义字符
    jquery中变量加$和不加$有什么区别!
    spring大乱炖
    第一章 java多线程
    CS:APP 05 笔记
  • 原文地址:https://www.cnblogs.com/blog-wzy/p/5353264.html
Copyright © 2020-2023  润新知