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 }

主函数代码

运行结果如下:

时间: 2024-10-22 19:14:47

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

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

java多线程的难点是在:处理多个线程同步与并发运行时线程间的通信问题.java在处理线程同步时,常用方法有: 1.synchronized关键字. 2.Lock显示加锁. 3.信号量Semaphore. 线程同步问题引入: 创建一个银行账户Account类,在创建并启动100个线程往同一个Account类实例里面添加一块钱.在没有使用上面三种方法的情况下: 代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

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

线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点.linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥锁(mutex) 通过锁机制实现线程间的同步. 初始化锁.在Linux下,线程的互斥量数据类型是pthread_mutex_t.在使用前,要对它进行初始化.静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;动态分配:int pthread_mutex_init(pthread_m

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

互斥锁 1 #include <cstdio> 2 3 #include <cstdlib> 4 5 #include <unistd.h> 6 7 #include <pthread.h> 8 9 #include "iostream" 10 11 using namespace std; 12 13 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 14 15 int tmp; 16

C#线程同步的几种方法

我们在编程的时候,有时会使用多线程来解决问题,比如你的程序需要在后台处理一大堆数据,但还要使用户界面处于可操作状态:或者你的程序需要访问一些外部资源如数据库或网络文件等.这些情况你都可以创建一个子线程去处理,然而,多线程不可避免地会带来一个问题,就是线程同步的问题.如果这个问题处理不好,我们就会得到一些非预期的结果. 在网上也看过一些关于线程同步的文章,其实线程同步有好几种方法,下面我就简单的做一下归纳. 一.volatile关键字 volatile是最简单的一种同步方法,当然简单是要付出代价的

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

转自原文 归纳一下:C#线程同步的几种方法 我们在编程的时候,有时会使用多线程来解决问题,比如你的程序需要在后台处理一大堆数据,但还要使用户界面处于可操作状态:或者你的程序需要访问一些外部资源如数据库或网络文件等.这些情况你都可以创建一个子线程去处理,然而,多线程不可避免地会带来一个问题,就是线程同步的问题.如果这个问题处理不好,我们就会得到一些非预期的结果. 在网上也看过一些关于线程同步的文章,其实线程同步有好几种方法,下面我就简单的做一下归纳. 一.volatile关键字 volatile是

Linux下线程同步的几种方法

Linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥锁(mutex) 锁机制是同一时刻只允许一个线程执行一个关键部分的代码.  1. 初始化锁 int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr); 其中参数 mutexattr 用于指定锁的属性(见下),如果为NULL则使用缺省属性. 互斥锁的属性在创建锁的时候指定,在LinuxThreads实

Java实现ping功能的三种方法

Java实现ping功能的三种方法 检测设备的运行状态,有的是使用ping的方式来检测的.所以需要使用java来实现ping功能. 为了使用java来实现ping的功能,有人推荐使用java的 Runtime.exec()方法来直接调用系统的Ping命令,也有人完成了纯Java实现Ping的程序,使用的是Java的NIO包(native io, 高效IO包).但是设备检测只是想测试一个远程主机是否可用.所以,可以使用以下三种方式来实现: 1.Jdk1.5的InetAddresss方式 自从Jav

线程同步的几种方法的总结

线程同步的方式包括:互斥锁.读写锁.条件变量.信号量和令牌. 互斥锁和读写锁:提供对临界资源的保护,当多线程试图访问临界资源时,都必须通过获取锁的方式来访问临界资源.(临界资源:是被多线程共享的资源)当读写线程获取锁的频率差别不大时,一般采用互斥锁,如果读线程访问临界资源的频率大于写线程,这个时候采用读写锁较为合适,读写锁允许多个读线程同时访问临界资源,读写线程必须互斥访问临界资源.读写锁的实现采用了互斥锁,所以在读写次数差不多的情况下采用读写锁性能没有直接采用互斥锁来的高. 条件变量:提供线程

解决线程安全的三种方法

1:线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的. 我们通过一个案例,演示线程的安全问题: 电影院要卖票,我们模拟电影院的卖票过程.假设要播放的电影是 “上海堡垒”,本次电影的座位共50个(只能卖50张票). 我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这50张票)需要窗口,采用线程对象来模拟:需要票,Runnable接口子类