博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JAVA之线程同步的三种方法
阅读量:4324 次
发布时间:2019-06-06

本文共 7116 字,大约阅读时间需要 23 分钟。

  最近接触到一个图片加载的项目,其中有声明到的线程池等资源需要在系统中线程共享,所以就去研究了一下线程同步的知识,总结了三种常用的线程同步的方法,特来与大家分享一下。这三种方法分别是: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     @Override10     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     @Override22     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     @Override21     /**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
threadLocal = new ThreadLocal
(); // 本地线程存储变量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); // 如果资源不存在,则实例化一个之后提交给ThreadLocal23 }24 return (AccountForLocal) account;25 }26 27 @SuppressWarnings("static-access")28 @Override29 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 }
主函数代码

 

运行结果如下:

转载于:https://www.cnblogs.com/blog-wzy/p/5353264.html

你可能感兴趣的文章
文件操作
查看>>
java中的反射整理
查看>>
Android 跑马灯效果与EditText冲突
查看>>
memcached Logging
查看>>
eclipse经常使用快捷键
查看>>
ZOJ 2588 Burning Bridges(无向连通图求割边)
查看>>
IP转换hash以及返回
查看>>
map+pair Bayan 2015 Contest Warm Up D题
查看>>
Jumserver安装日志审计和资产管理
查看>>
【转】resultMap详解(包含多表查询)
查看>>
在VisualStudio中显示当前的分支名
查看>>
Android 网络请求详解
查看>>
边工作边刷题:70天一遍leetcode: day 33
查看>>
第10章 系统级I/O(下)
查看>>
mysql foreign key(外键) 说明与实例
查看>>
二叉树的非递归遍历
查看>>
android中Activity布局以及实例
查看>>
jsp中常用操作字符串的el表达式
查看>>
element-ui <el-input> 注册blur事件
查看>>
HTML5须知的特征和技术
查看>>