最近接触到一个图片加载的项目,其中有声明到的线程池等资源需要在系统中线程共享,所以就去研究了一下线程同步的知识,总结了三种常用的线程同步的方法,特来与大家分享一下。这三种方法分别是: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. }

主函数代码

运行结果如下:

JAVA之线程同步的三种方法的更多相关文章

  1. Java中实现线程同步的三种方法

    实现同步的三种方法 多线程共享数据时,会发生线程不安全的情况,多线程共享数据必须同步. 实现同步的三种方法: 使用同步代码块 使用同步方法 使用互斥锁ReetrantLock(更灵活的代码控制) 代码 ...

  2. java多线程二之线程同步的三种方法

          java多线程的难点是在:处理多个线程同步与并发运行时线程间的通信问题.java在处理线程同步时,常用方法有: 1.synchronized关键字. 2.Lock显示加锁. 3.信号量Se ...

  3. java中线程同步的几种方法

    1.使用synchronized关键字 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法.在调用该方法前,需要获得内置锁,否则就处于阻塞状态. 注: synchro ...

  4. Java修炼——线程同步的俩种方法

    当多线程去同时抢占CPU资源时,有多线程的安全问题.这时候就需要将线程同步.线程同步有俩个方法. 1.同步代码块(synchronize),同步代码块需要同步监视器,同步监视器是针对对象进行操作.什么 ...

  5. 【转】 Linux 线程同步的三种方法

    线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点.linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥锁(mutex) 通过锁机制实现线程间的 ...

  6. Linux 线程同步的三种方法(互斥锁、条件变量、信号量)

    互斥锁 #include <cstdio> #include <cstdlib> #include <unistd.h> #include <pthread. ...

  7. IOS 多线程,线程同步的三种方式

    本文主要是讲述 IOS 多线程,线程同步的三种方式,更多IOS技术知识,请登陆疯狂软件教育官网. 一般情况下我们使用线程,在多个线程共同访问同一块资源.为保护线程资源的安全和线程访问的正确性. 在IO ...

  8. Linux下线程同步的几种方法

    Linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥锁(mutex) 锁机制是同一时刻只允许一个线程执行一个关键部分的代码.  1. 初始化锁 int pthrea ...

  9. 归纳一下:C#线程同步的几种方法

    转自原文 归纳一下:C#线程同步的几种方法 我们在编程的时候,有时会使用多线程来解决问题,比如你的程序需要在后台处理一大堆数据,但还要使用户界面处于可操作状态:或者你的程序需要访问一些外部资源如数据库 ...

随机推荐

  1. RemodelanyWhere11.0.2673版本下载

    百度云盘链接:http://pan.baidu.com/s/1geL5lez 密码:hisq 原文转载至:http://blog.sun0816.com/13623.html

  2. 使用parted给大于2T的磁盘分区

    1.使用命令parted /dev/sdb [root@server ~]# parted /dev/sdb GNU Parted 2.1 使用 /dev/sdb Welcome to GNU Par ...

  3. POJ 2255. Tree Recovery

    Tree Recovery Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 11939   Accepted: 7493 De ...

  4. [No00009D]使用visual studio 2015 update3打包程序安装包的简单方法(不需要InstallShield)

    注意: 该方法只适用于小型软件的打包发布: 该打包向导可以预先检查需要的运行库支持: 由于visual studio自2012后取消掉了自带的打包程序,如果有需要打包安装,需要使用一个叫用Instal ...

  5. PHP的GD库

    GD库 PHP通过GD库,可以对JPG.PNG.GIF.SWF等图片进行处理.GD库常用在图片加水印,验证码生成等方面. 绘制线条 要对图形进行操作,首先要新建一个画布,通过imagecreatetr ...

  6. 安全测试 - CSRF攻击及防御

    CSRF(Cross-site request forgery跨站请求伪造) 尽管听起来像跨站脚本(XSS),但它与XSS非常不同,并且攻击方式几乎相左.XSS利用站点内的信任用户,而CSRF则通过伪 ...

  7. LCX端口转发实现内网突破

    工具:lcx.exe 原理:当目标主机仅开放了web服务,而该服务又仅能供内网用户使用,外网用户根本无法直接访问.因此想要让外网用户能能够访问局域网中的系统服务,必须进行端口映射等操作才行.其原理就是 ...

  8. Underscore 整体架构浅析

    前言 终于,楼主的「Underscore 源码解读系列」underscore-analysis 即将进入尾声,关注下 timeline 会发现楼主最近加快了解读速度.十一月,多事之秋,最近好多事情搞的 ...

  9. python 抽象类、抽象方法、接口、依赖注入、SOLIP

    1.程序设计原则:SOLIP SOLIP设计原则 1.单一责任原则(SRP) 一个对象对只应该为一个元素负责 2.开放封闭原则(OCP) 对扩展开放,修改封闭 3.里氏替换原则(LSP) 可以使用任何 ...

  10. PHP函数

    2017.1.5 stream_get_contents函数:读取数据流中的剩余数据到字符串 [功能说明] 该函数同file_get_COntents()函数的作用相同,只不过该函数用于读取已经打开的 ...