最近接触到一个图片加载的项目,其中有声明到的线程池等资源需要在系统中线程共享,所以就去研究了一下线程同步的知识,总结了三种常用的线程同步的方法,特来与大家分享一下。这三种方法分别是:synchronized代码段、synchronized修饰方法/类、ThreadLocal本地线程变量。
我们通过一个例子来表现这三种方法:一张银行卡里面有300块钱,15个线程从这张银行卡中取钱,每个线程取一次且每次取20块钱;当当前余额不足100元时,则向账户中汇款20元。三种方法每种方法都有5个线程。我们预期的结果是当15个线程都执行完毕,银行卡里面的余额应该显示为80。
准备工作
我们需要一个账户的实体类Account,其中有一个属性money。Account类作为账户类的父类,我们在后面将用三种方法分别生成一个子类来继承这个类。Account类的具体代码如下:
public abstract class Account { // 抽象类
protected static Account account; // Account类的实例,全局只有一个
protected static final int DEFAULT_MONEY = 300; // 账户中默认有300块钱
protected static int money; // 记录当前账户中的余额 // 获取账户实例的方法,由于是static方法不能定义为抽象方法,所以要在子类中重写这个方法
public static Account getInstance() {
return null;
} // 抽象方法,设置当前账户中的余额,三种方法分别有不同的实现方法
public abstract void setMoney(int money); // 获取当前账户中的余额
public int getMoney() {
return money;
}
}
我们可以在每种方法中都把线程要进行的工作都进行一遍,但是这样的话代码就会重用,所以我们将这段代码提取出来形成一个工具类MyRunnable(实现自Runnable接口),在这个类的run()方法中进行三种方法的所有线程都应该做的事情,比如取出当前账户中的余额、判断余额是否小于100、设置账户余额(汇款或取款)等。MyRunnable类中的代码如下:
public class MyRunnable implements Runnable {
private Account account; // 账户的父类声明,在构造方法中传入具体子类的引用 // 构造方法中传入Account类的子类,即该线程绑定的账户操作的方法
public MyRunnable(Account account) {
this.account = account;
} @Override
public void run() {
String currentThreadName = Thread.currentThread().getName(); // 获取当前线程的名称
System.out.println(currentThreadName + " is running...");
System.out.println(currentThreadName + ":before=" + account.getMoney()); // 打印账户当前的余额
if (account.getMoney() < 100) { // 如果账户当前的余额小于100,则向账户中汇款20元
account.setMoney(account.getMoney() + 20);
} else { // 如果账户余额还大于100,则取出20元
account.setMoney(account.getMoney() - 20);
}
System.out.println(currentThreadName + ":after=" + account.getMoney()); // 打印操作后账户的余额
}
}
下面贴出三种方法的代码。
第一种方法:SYNCHRONIZED修饰代码段的方法
public class AccountForSyn extends Account { @SuppressWarnings("static-access")
private AccountForSyn() {
account.money = DEFAULT_MONEY;
} public static AccountForSyn getInstance() {
if (account == null) {
// 这里为了防止两个线程同时访问account实例,所以在同步块的前后分别进行了一次判断
synchronized (AccountForSyn.class) {
if (account == null) {
account = new AccountForSyn();
}
}
}
return (AccountForSyn) account;
} @SuppressWarnings("static-access")
@Override
public void setMoney(int money) { // 设置account账户中的余额
/**
* 核心代码
*/
synchronized (AccountForSyn.class) { // SYNCHRONIZED后面的参数是一个Object类型的参数,可以是任意的Object(最好是共享的资源)
account.money = money;
}
}
}
第二种方法:SYNCHRONIZED关键字修饰方法的方法
public class AccountForSynM extends Account { @SuppressWarnings("static-access")
private AccountForSynM() {
account.money = DEFAULT_MONEY;
} public static AccountForSynM getInstance() {
if (account == null) {
synchronized (AccountForSyn.class) {
if (account == null) {
account = new AccountForSynM();
}
}
}
return (AccountForSynM) account;
} @SuppressWarnings("static-access")
@Override
/**
* 核心代码
*/
public synchronized void setMoney(int money) {
account.money = money;
}
}
第三种方法:ThreadLocal本地线程变量的方法
public class AccountForLocal extends Account {
/**
* ThreadLocal是本地线程存储变量,里面存储着所有线程共享的资源。
* ThreadLocal的工作原理与SYNCHRONIZED关键字不同:
* SYNCHRONIZED关键字是对代码块上锁,使一个线程可以从头到尾一次性的执行其中的代码
* ThreadLocal是对线程共享的资源进行多次备份,再分发给全部的线程,线程对数据进行修改后提交
*/
private static ThreadLocal<AccountForLocal> threadLocal = new ThreadLocal<AccountForLocal>(); // 本地线程存储变量ThreadLocal的声明 @SuppressWarnings("static-access")
private AccountForLocal() {
account.money = DEFAULT_MONEY;
} public static AccountForLocal getInstance() {
account = threadLocal.get(); // 从ThreadLocal中获取线程共享资源
if (account == null) {
account = new AccountForLocal();
/**
* 核心代码
*/
threadLocal.set((AccountForLocal) account); // 如果资源不存在,则实例化一个之后提交给ThreadLocal
}
return (AccountForLocal) account;
} @SuppressWarnings("static-access")
@Override
public void setMoney(int money) {
account.money = money;
/**
* 核心代码
*/
threadLocal.set((AccountForLocal) account);
}
}
主函数的代码如下:
public class MainClass { // 主函数
public static void main(String[] args) {
// 定义15个线程
// SYNCHRONIZED修饰代码段的方法的五个线程
Thread thread11 = new Thread(new MyRunnable(AccountForSyn.getInstance()), "Thread A1");
Thread thread12 = new Thread(new MyRunnable(AccountForSyn.getInstance()), "Thread A2");
Thread thread13 = new Thread(new MyRunnable(AccountForSyn.getInstance()), "Thread A3");
Thread thread14 = new Thread(new MyRunnable(AccountForSyn.getInstance()), "Thread A4");
Thread thread15 = new Thread(new MyRunnable(AccountForSyn.getInstance()), "Thread A5");
// ThreadLocal本地线程变量的方法的五个线程
Thread thread21 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread B1");
Thread thread22 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread B2");
Thread thread23 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread B3");
Thread thread24 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread B4");
Thread thread25 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread B5");
// SYNCHRONIZED关键字修饰方法的方法的五个线程
Thread thread31 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread C1");
Thread thread32 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread C2");
Thread thread33 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread C3");
Thread thread34 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread C4");
Thread thread35 = new Thread(new MyRunnable(AccountForLocal.getInstance()), "Thread C5"); // 启动15个线程
thread11.start();
thread21.start();
thread31.start();
thread12.start();
thread22.start();
thread32.start();
thread13.start();
thread23.start();
thread33.start();
thread14.start();
thread24.start();
thread34.start();
thread15.start();
thread25.start();
thread35.start();
}
}
主函数代码
运行结果如下:
JAVA之线程同步的三种方法的更多相关文章
-
Java中实现线程同步的三种方法
实现同步的三种方法 多线程共享数据时,会发生线程不安全的情况,多线程共享数据必须同步. 实现同步的三种方法: 使用同步代码块 使用同步方法 使用互斥锁ReetrantLock(更灵活的代码控制) 代码 ...
-
java多线程二之线程同步的三种方法
java多线程的难点是在:处理多个线程同步与并发运行时线程间的通信问题.java在处理线程同步时,常用方法有: 1.synchronized关键字. 2.Lock显示加锁. 3.信号量Se ...
-
java中线程同步的几种方法
1.使用synchronized关键字 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法.在调用该方法前,需要获得内置锁,否则就处于阻塞状态. 注: synchro ...
-
Java修炼——线程同步的俩种方法
当多线程去同时抢占CPU资源时,有多线程的安全问题.这时候就需要将线程同步.线程同步有俩个方法. 1.同步代码块(synchronize),同步代码块需要同步监视器,同步监视器是针对对象进行操作.什么 ...
-
【转】 Linux 线程同步的三种方法
线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点.linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥锁(mutex) 通过锁机制实现线程间的 ...
-
Linux 线程同步的三种方法(互斥锁、条件变量、信号量)
互斥锁 #include <cstdio> #include <cstdlib> #include <unistd.h> #include <pthread. ...
-
IOS 多线程,线程同步的三种方式
本文主要是讲述 IOS 多线程,线程同步的三种方式,更多IOS技术知识,请登陆疯狂软件教育官网. 一般情况下我们使用线程,在多个线程共同访问同一块资源.为保护线程资源的安全和线程访问的正确性. 在IO ...
-
Linux下线程同步的几种方法
Linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥锁(mutex) 锁机制是同一时刻只允许一个线程执行一个关键部分的代码. 1. 初始化锁 int pthrea ...
-
归纳一下:C#线程同步的几种方法
转自原文 归纳一下:C#线程同步的几种方法 我们在编程的时候,有时会使用多线程来解决问题,比如你的程序需要在后台处理一大堆数据,但还要使用户界面处于可操作状态:或者你的程序需要访问一些外部资源如数据库 ...
随机推荐
-
ambari2.4.2_centos7 学习全纪录
目录: 为什么要用Ambari 概念概述 原理简介 安装 创建集群 创建集群 手动修改配置 NameNode HA 安装SmartSense 二次开发 为什么要用Ambari Ambari 是 Apa ...
-
【CQOI2011】动态逆序对 BZOJ3295
Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计 ...
-
Nancy之结合tinyfox给我们的应用提供简单的数据服务
说到提供数据服务给我们的一些应用,估计用的最多的也就是json和xml这两种数据格式 实现的方法也是多种多样,web api,mvc的jsonresult和contentresult...等等 本文是 ...
-
160809225_叶桦汀_C语言程序设计实验2 选择结构程序设计_进阶
#include<stdio.h> #include<math.h> int main() { int a,b,c,l,p,s; printf("请输入三个数:&qu ...
-
js修改浏览器url
var stateObject = {};var title = "";var newUrl = "/";history.pushState(stateObje ...
-
python 的一些小技巧
赋值: a, b, c = 'xixi', 'haha', 'hehe' 连接字典: >>> s = {1:'a', 2:'b', 3:'c'} >>> s.key ...
-
AIX LVM 常用命令记录
针对物理卷的操作指令 lsdev--列出ODM中的设备 chdev--修改一个AIX设备的属性 mkdev--创建一个AIX设备 chpv--修改物理卷的状态和属性 lspv--查看AIX中物理卷的相 ...
-
Java内部类的自我理解
本文借鉴网络上多位大牛的博客和文章.感谢各位不知名人士的分享. 一.什么事内部类? 内部类是指在一个外部类的内部再定义一个类.内部类作为外部类的成员,而且依附于外部类而存在的.内部类能够为静态,可用p ...
-
io 测试
//其中能够实现编码的只有OutputStreamWriter和对应inputStreamReader package net; import java.io.BufferedWriter; impo ...
-
swiper 应用
swiper之PC端的广告页面[当前示例对应网站:http://shang.shuaishou.com/] plugins:[红线部分] html: <div class="banne ...