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 ...
随机推荐
- Python 学习笔记(三)数字
Python 数字 int 整型 是正或负整数 2 long 长整型 整数最后是一个大写或小写的L 2L float 浮点型 由整数部分和小数部分组成 2.0 complex 复数 小 ...
- 菜鸟笔记 -- Chapter 6.4.2 详解继承
6.4.2 详解继承 6.4.2.1 继承入门 继承使得程序架构具有一定的弹性,在程序中复用一些已经定义完善的类不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性.基本思想是基于某个父类 ...
- django-多表操作2
#######多表操作二######## 昨天写了基于双下划线查找,都是两个表之间查找,那再多跨几个表呢?还是一样,一步一步分析 # 跨多表查询: 查询红楼梦这本书的作者的电话: (Author,Bo ...
- ABAP术语-Business Process
Business Process 原文:http://www.cnblogs.com/qiangsheng/archive/2008/01/11/1035316.html A prepared sce ...
- 解决WordPress设置错误的url网站不能访问的问题
通过WordPress后台首选项更改了网站url地址之后,网站就会出现访问不了的情况,一般来说,网站后台也登陆不上去了,我从网上寻找到了四种方法,这四种方法前三种都是需要登陆到后台的,但实际上出错后, ...
- 02 shell编程之条件语句
Shell编程之条件语句 学习目标: 掌握shell脚本条件测试 掌握if语句编程 目录结构: 条件测试 条件测试概述 l 对特定的条件进行判断,以决定如何执行操作 l 测试的方法 方法1:tes ...
- MySQL建表
-- 1.创建部门表dept 1 CREATE TABLE dept( 2 deptno INT PRIMARY KEY, 3 dname VARCHAR(20) UNIQUE NOT NULL, 4 ...
- 吐血分享:QQ群霸屏技术(初级篇)
QQ群,仿似一个冷宫;But,你真摒弃不起. 某人,坐拥2000多个2000人群,月收入10w+,此类人数少,皆因多年的沉淀,以形成完全的壁垒,难以企及的层次. 流量的分散,QQ群相对比较优质的地带, ...
- Python变量和循环
1.Python变量 比C语言,Java语言更加简洁,不需要加int等等类型定义,直接变量名 = 值,Python里甚至不需要分号.有些特定的不能当做变量名,变量只能由字母.数字和下划线组成,下划线可 ...
- C errno是否是线程安全的
本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/138 在使用多线程时,遇到了一个问题:线程例程中如果需要使用er ...