java多线程二之线程同步的三种方法
java多线程的难点是在:处理多个线程同步与并发运行时线程间的通信问题。java在处理线程同步时,常用方法有:
1、synchronized关键字。
2、Lock显示加锁。
3、信号量Semaphore。
线程同步问题引入:
创建一个银行账户Account类,在创建并启动100个线程往同一个Account类实例里面添加一块钱。在没有使用上面三种方法的情况下:
代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class AccountWithoutSync {
private static Account account = new Account(); //实例化一个账户 public static void main(String[] args)
{
long start = System.currentTimeMillis();
//使用ExecutorService创建线程池
ExecutorService executor = Executors.newCachedThreadPool(); for(int i=0;i<100;i++)
{
executor.execute(new AddPennyTask());
}
//关闭线程池 即使线程池中还有未完成的线程 返回未完成的清单
executor.shutdown(); //关闭之后还是要保证未完成的线程继续完成 如果线程池中所有任务都完成了,isTerminated返回true
while(!executor.isTerminated())
{}
long end = System.currentTimeMillis(); //balance有余额的意思
System.out.println("现在账户里面的余额是:" + account.getBalance());
System.out.println("花费的时间以微秒为单位:"+(end-start)+"微秒");
} //这个线程只调用了一个方法
public static class AddPennyTask implements Runnable
{
@Override
public void run() {
account.deposit(1);
}
} //一个内部类 用于 账户的相关处理
public static class Account
{
private int balance =0;
public int getBalance()
{
return balance;
}
public void deposit(int amount)
{
int newBalance = balance + amount;
//为了让错误体现的更明显
try {
Thread.sleep(4); //5毫秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
balance= newBalance;
//其实就是balance +=amount;//不过换成这一段代码结果就在100和99左右
}
}
}
运行截图
错的不是一般的明显,明明存入了100块,显示只有2块,原因也很简单,就是100个线程同时对acount进行修改,当第100该线程把钱改成100时,第2个线程最后修改,把它改成了2,这群不听话的线程,那就好好管管他们吧,让他们乖乖听话,在这之前,还是要记住以下一些名词:
竞争状态:当多个线程访问同一公共资源并引发冲突造成线程不同步,我们称这种状态为竞争状态。
线程安全:是针对类来说的,如果一个类的对象在多线程程序中不会导致竞争状态,我们就称这类为线程安全的,上面的Account是线性不安全的,而又如StringBuffer是线程安全的,而StringBuilder则是线程不安全的。
临界区:造成竞争状态的原因是多个线程同时进入了程序中某一特定部分,如上面程序中的deposit方法,我们称这部分为程序中的临界区,我们要解决多线程不同步问题,就是要解决如何让多个线程有序的访问临界区。
使用synchronized关键字
1、同步方法:在deposit方法前加synchronized关键字,使得该方法变成同步方法:public synchronized void deposit(double amount){}
2、同步语句:对某一代码块加synchronized关键字,常用格式:
synchronized(exper) {statement} 其中exper是对象的引用,如上面的程序,在要在调用depsoit方法时,改成这样: synchronized(account){account.deposit(1);}
使用synchronized,其实是隐式地给方法或者代码块加了锁,任何同步的实例方法都可以转换为同步语句:
public synchronized void method(){}
转换为同步语句: public void method{sysnchronized(this){}}
利用lock加锁同步
java也可以用Lock显示的对临界区代码加锁以及解锁,这比用synchronized关键字更加直观灵活。
一个锁是一个Lock接口的实例,该接口定义了加锁解锁的方法,且一个锁可以多次调用其newCondition()方法创建名为Condition对象的实例,以此进行线程间的通信(在后面用到)。
有了Lock接口,我们还要实现它,java提供了RenentrantLock类,该类是为创建相互排斥锁而实现了Lock接口,由此就好办了,下面看一下书上的图:
代码如下:
public static class Account2
{
private static Lock lock = new ReentrantLock();
private int balance =0;
public int getBalance()
{
return balance;
}
public void deposit(int amount)
{
lock.lock();
try{
int newBalance = balance + amount;
Thread.sleep(4);
balance= newBalance;
}catch (InterruptedException e) {
e.printStackTrace();
}
finally{
lock.unlock();
}
}
}
给个运行截图:
100块存入,且时间明显比之前久了,100个线程都乖乖的排队访问临界区。另外注意在对lock()的调用后,紧跟随try catch finnaly语句,这是个好习惯。
利用信号量同步
信号量是个好东西,信号量机制在操作系统方面有着广泛的应用,如linux进程同步信号量,而在java里,Semaphore包包含了一些访问信号量的方法。
信号量可以用来限制访问共享资源的线程数,在访问临界区资源前,线程必须获取一个信号量,在访问完之后返回一个信号量。下图是关于类Semaphore,该类包含访问信号量的方法:
利用信号量Semaphorre,可以将上面的Account类改成这样:
public static class Account
{
private static Semaphore semaphore = new Semaphore(1); //创建一个信号量
private int balance =0;
public int getBalance()
{
return balance;
}
public void deposit(int amount)
{
try {
semaphore.acquire();
int newBalance = balance + amount;
Thread.sleep(4);
balance= newBalance;
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
semaphore.release(); //返回一个信号量
}
}
}
网上有很多关于java多线程的文章,有不少写成系列的,但个人觉得像多线程这么有有意思的部分,还是得好好静下心来品位,上面的三种方法,可以解决多线程同步问题,后面还会继续学习,嗯,加油。
本文出自于博客园兰幽,转载请说明出处。
java多线程二之线程同步的三种方法的更多相关文章
- Java中实现线程同步的三种方法
实现同步的三种方法 多线程共享数据时,会发生线程不安全的情况,多线程共享数据必须同步. 实现同步的三种方法: 使用同步代码块 使用同步方法 使用互斥锁ReetrantLock(更灵活的代码控制) 代码 ...
- JAVA之线程同步的三种方法
最近接触到一个图片加载的项目,其中有声明到的线程池等资源需要在系统中线程共享,所以就去研究了一下线程同步的知识,总结了三种常用的线程同步的方法,特来与大家分享一下.这三种方法分别是:synchroni ...
- 【转】 Linux 线程同步的三种方法
线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点.linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥锁(mutex) 通过锁机制实现线程间的 ...
- Linux 线程同步的三种方法(互斥锁、条件变量、信号量)
互斥锁 #include <cstdio> #include <cstdlib> #include <unistd.h> #include <pthread. ...
- IOS 多线程,线程同步的三种方式
本文主要是讲述 IOS 多线程,线程同步的三种方式,更多IOS技术知识,请登陆疯狂软件教育官网. 一般情况下我们使用线程,在多个线程共同访问同一块资源.为保护线程资源的安全和线程访问的正确性. 在IO ...
- 归纳一下:C#线程同步的几种方法
转自原文 归纳一下:C#线程同步的几种方法 我们在编程的时候,有时会使用多线程来解决问题,比如你的程序需要在后台处理一大堆数据,但还要使用户界面处于可操作状态:或者你的程序需要访问一些外部资源如数据库 ...
- Linux下线程同步的几种方法
Linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥锁(mutex) 锁机制是同一时刻只允许一个线程执行一个关键部分的代码. 1. 初始化锁 int pthrea ...
- Java多线程学习总结--线程同步(2)
线程同步是为了让多个线程在共享数据时,保持数据的一致性.举个例子,有两个人同时取钱,假设用户账户余额是1000,第一个用户取钱800,在第一个用户取钱的同时,第二个用户取钱600.银行规定,用户不允许 ...
- java核心知识点----创建线程的第三种方式 Callable 和 Future CompletionService
前面已经指出通过实现Runnable时,Thread类的作用就是将run()方法包装成线程执行体,那么是否可以直接把任意方法都包装成线程执行体呢?Java目前不行,但其模仿者C#中是可以的. Call ...
随机推荐
- Sass 基础(一)
css 是一些非常简单得语句的组合,既然简单的语句,就不可避免的有很多重复的,冗余的东西,而且没有传统编程语言变量,控制语句等高级特性,所以造成了css 编写低效,往往需要查找替换,大量复制来修改或者 ...
- Java的内存--存储(1)
有次去面试,面试官突然问我这个问题,当时我只知道怎么写最优化,但是具体不知道为什么那样写,身价立马下降哦 1. 以下开发习惯,你怎么看? for(int i=0;i<2;i++){ Person ...
- leetcode算法之 Single Number
题目描述: 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次.找出那个只出现了一次的元素. 要求: 时间复杂度O(n),空间复杂都O(1) 示例: 输入: [2,2,1] 输 ...
- 【杂题总汇】AGC027 C - ABland Yard
◆AGC027◆C - ABland Yard 终于知道为什么比赛的时候这么多人做C题了…… +传送门+(这是beta版的) ◇ 题目(自己翻译的,不要在意细节……) P.S. (@ 2018-9-2 ...
- ABAP术语-Company Code
Company Code 原文:http://www.cnblogs.com/qiangsheng/archive/2008/01/16/1040816.html The smallest organ ...
- sudo及visudo
sudo是一种权限管理机制,管理员可以授权普通用户去执行root的操作,而不需要知道root的密码,它依赖于/etc/sudoers这个文件,可以授权给哪个用户在哪个主机上能够以管理员的身份执行什么样 ...
- NC使用技巧
1. NC 1.1概述 1.1.1 优点: 1)网络工具中的瑞士军刀 2)侦听模式/传输模式. 3)可代替telnet获取banner信息. 4)传输文件/目录. 5)传输文本信息. 6)加密传输文件 ...
- Vue.js中 computed 和 methods 的区别
官方文档中已经有对其的解释了,在这里把我的理解记录一下Vue中的methods.watch.computed computed 的使用场景 HTML模板中的复杂逻辑表达式,为了防止逻辑过重导致不易维护 ...
- Node.js(二)----安装Cnpm
---恢复内容开始--- 1.安装CNPM 因为天草的 Great Wall 导致下载速度龟速....所以安装Cnpm淘宝镜像 2.命令 2.1 如果版本合适 设置镜像地址 npm config se ...
- Laravel 5.5搭建(lunix-ubuntu)
基本配置 PHP >= 7.0.0 PHP OpenSSL 扩展 PHP PDO 扩展 PHP Tokenizer 扩展 PHP XML 扩展 1:nginx sudo apt-get upda ...