首页 技术 正文
技术 2022年11月12日
0 收藏 543 点赞 4,018 浏览 5116 个字

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

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,117
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,590
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,435
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,206
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,842
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,927