java学习之浅谈多线程2--线程同步
- 如果一个共享资源被多个线程同时访问,可能会遭到破坏。举个例子说明这个问题,假设创建并启动100个线程,每个线程都往同一个账户中添加一个便士,代码如下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class AccountWithSync { private static Account account = new Account(); public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool(); //用线程池executor创建并启动100个线程
for(int i=0; i<100; i++) {
executor.execute(new AddAPennyTask());
} executor.shutdown(); //线程全部结束前执行此循环
while(!executor.isTerminated()) {
} System.out.println("What is balance?" + account.getBalance());
} private static class AddAPennyTask implements Runnable {
@Override
public void run() {
account.deposit(1);
}
} private static class Account {
private int balance = 0; //账户余额 public int getBalance() {
return balance;
} public void deposit(int amount) {
int newBalance = balance + amount; //设置这种延迟是为了加大数据的破坏程度让结果更加明显
try {
Thread.sleep(5);
} catch (InterruptedException e) {
} balance = newBalance;
}
}
}
该账户中的初始余额为0,当所有线程都结束时,余额应该为100,但是运行上面程序得到的结果却是不可预测的,如下图所示。它演示了当所有线程同时访问同一个数据时,就会出现数据破坏的问题。
那么,究竟是什么导致了程序的错误?下面给出一个可能的情况。
| 步骤 | 余额 | 任务1 | 任务2 |
|---|---|---|---|
| 1 | 0 | newBalance = balance + 1; | |
| 2 | 0 | newBalance = balance + 1; | |
| 3 | 1 | balance = newBalance; | |
| 4 | 1 | balance = newBalance; |
在步骤1中,任务1从账户中获取余额数目。在步骤2中,任务2从账户中获取同样数目的余额。在步骤3中,任务1向账户写入一个新余额。在步骤4中任务2也向该账户写入一个新余额。这就导致任务1什么也没做,因为任务2覆盖了任务1的结果。
这是多线程程序中一个普遍的问题,称为竞争状态。如果一个类的对象在多线程程序中没有导致竞争状态,则称这样的类为线程安全的。
- 为了解决上面的问题,应该防止多个线程同时进入程序的某一个特定部分(程序中的这部分称为临界区),也就是将这部分同步化。下面介绍三种解决方法:
1.使用关键字synchronized
通过在程序的deposit方法中添加关键字synchronized,使Account类成为线程安全的,如下:
public synchronized void deposit(double amount)
一个同步方法在执行前需要加锁。对于实例方法,要给调用该方法的对象加锁。对于静态方法,要给这个类加锁。如果一个线程调用一个对象上的同步实例方法(静态方法),首先给该对象(类)加锁,然后执行该方法,最后解锁。在解锁之前,另一个调用那个对象(类)中方法的线程将被阻塞,直到解锁。
因为deposit方法被同步化,如果任务1开始进入deposit方法,任务2就会被阻塞,直到任务1完成该方法的运行。
2.利用加锁同步
使用关键字synchronized的代码块在执行前都隐式地需要一个锁。还可以通过显示地加锁解决上面的问题。
一个锁是一个Lock接口的实例,它定义了加锁和释放锁的方法,如下:
+lock():void 加锁
+unlock():void 释放锁
+newCondition():Condition 返回绑定到Lock实例的新的Condition实例
ReentrantLock是为了创建相互排斥的锁的Lock的具体实现。可以创建具有特定的公平策略的锁。真正的公平策略确保等待时间最长的线程首先获得锁。假的公平策略将锁给任意一个在等待的线程。被多个线程访问的使用公正锁的程序,齐整体性能可能比那些使用默认设置的程序差,但是在获取锁且避免资源缺乏时变化很小。
+ReentrantLock() 等价于ReentrantLock(false)
+ReentrantLock(fair:boolean) 创建具有给定公平策略的锁。当公平性为true时,等待时间最长的线程将获得锁;否则没有特定的获得顺序
使用显示锁修改程序中的Account类如下:
private static class Account {
private int balance = 0;
private static Lock lock = new ReentrantLock(); //创建一个锁
public int getBalance() {
return balance;
}
//使用显示加锁避免竞争状态
public void deposit(int amount) {
lock.lock(); //获得锁
try {
int newBalance = balance + amount;
Thread.sleep(5);
balance = newBalance;
} catch (InterruptedException e) {
}finally {
lock.unlock(); //释放锁
}
}
}
在对lock()的调用之后紧随一个try-catch块并且在finally子句中释放这个锁是一个很好的习惯
通常使用synchronized方法或语句比使用排斥的显示锁简单些。然而,使用显示锁对同步具有状态的线程更加直观和灵活。
3.使用信号量
信号量可以用来限制访问共享资源的线程数。在访问资源之前,线程必须从信号量获取许可。在访问完资源之后,这个线程必须将许可返回给信号量。任务一旦获取许可,信号量中可用许可的总数量减1,一旦许可被释放,信号量中许可的总数加1.
+Semaphore(numberOfPermits:int) 创建一个带指定数目许可的信号量。公平策略为false
+Semaphore(numberOfPermits:int, fair:boolean) 创建一个带指定数目许可和公平策略的信号量
+acquire():void 获取这个信号量的许可。如果无许可可用,线程就被锁住直到有可用许可为止
+release():void 释放一个许可给该信号量
只有一个许可的信号量可以用来模拟一个相互排斥的锁。修改程序中的Account类,确保同一时间只有一个线程访问deposit方法从而解决了上面的问题,代码如下。
private static class Account {
private int balance = 0;
private static Semaphore semaphore = new Semaphore(1); //创建只有一个许可的信号量
public int getBalance() {
return balance;
}
//使用只有一个许可的信号量避免竞争状态
public void deposit(int amount) {
try {
semaphore.acquire(); // 获得许可
int newBalance = balance + amount;
Thread.sleep(5);
balance = newBalance;
} catch (InterruptedException e) {
} finally {
semaphore.release(); //释放许可
}
}
}
java学习之浅谈多线程2--线程同步的更多相关文章
- java学习之浅谈多线程3--线程间协作
通过保证临界区上多个线程的相互排斥,线程同步完全可以避免竞争状态的发生,但是有时还需要线程之间的协作.有两种方式可用于线程间的通信. 1.使用条件Condition Condition接口: +awa ...
- java学习之浅谈多线程4--SwingWorker
GUI事件处理和绘图代码在一个被称为事件分发线程的特殊线程中执行.如果一个事件需要很长的时间处理,线程就不能顾及到队列中的其他任务.为了解决这个问题,可以运行费时的任务来处理单独线程中的事件.Swin ...
- java学习之浅谈多线程1
创建任务和线程 任务就是对象,为了创建任务,必须首先为任务定义一个类.任务类必须实现Runnable接口.Runnable接口非常简单,它只有一个run方法.需要实现这个方法来告诉系统线程将如何运行. ...
- Java学习笔记——浅谈数据结构与Java集合框架(第一篇、List)
横看成岭侧成峰,远近高低各不同.不识庐山真面目,只缘身在此山中. --苏轼 这一块儿学的是云里雾里,咱们先从简单的入手.逐渐的拨开迷雾见太阳.本次先做List集合的三个实现类的学习笔记 List特点: ...
- Java学习笔记——浅谈数据结构与Java集合框架(第三篇、Map)
桃李春风一杯酒,江湖夜雨十年灯 --寄黄几复 之前图上写错了,是Hashtable类.t是小写的,它是个很古老的类,以至于命名都没有那么规范.. HashMap HashMap就是存储key-valu ...
- struts 2学习笔记—浅谈struts的线程安全
Sruts 2工作流程: Struts 1中所有的Action都只有一个实例,该Action实例会被反复使用.通过上面Struts 2 的工作流程的红色字体部分我们可以清楚看到Struts 2中每个A ...
- Java学习笔记——浅谈数据结构与Java集合框架(第二篇、Queue、Set)
江南好,何处异京华. 香散翠帘多在水,绿残红叶胜于花.无事避风沙. --<纳兰词> 诗词再好,大图不能忘 上大图: 先说说栈和队列: 栈就好比手枪的弹匣,你往里面压入子弹,最先压入的子弹就 ...
- 关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)
Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享 ...
- TensorFlow 2.0 深度学习实战 —— 浅谈卷积神经网络 CNN
前言 上一章为大家介绍过深度学习的基础和多层感知机 MLP 的应用,本章开始将深入讲解卷积神经网络的实用场景.卷积神经网络 CNN(Convolutional Neural Networks,Conv ...
随机推荐
- 如何获取e.printStackTrace()的内容
e.printStackTrace()通常是打印在控制台的,但是,有时候程序上线了需要看这个堆栈的内容就不容易了,一来生产环境打印的东西很多或者很少,二来有时候无法直接查看到,这个时候就需要把这些内容 ...
- (java部署篇)eclipse ~ 自动补全词的各种控制(转)
#这种方法只适用于Eclipse Classic版本(这个版本带有插件的源码) 在使用Eclispe的过程,感觉自动补全做的不好,没有VS的强大.下面说两个增强自动补全的方法: 1.增加Eclipse ...
- Hive sql语法详解
Hive 是基于Hadoop 构建的一套数据仓库分析系统,它提供了丰富的SQL查询方式来分析存储在Hadoop 分布式文件系统中的数据,可以将结构 化的数据文件映射为一张数据库表,并提供完整的SQ ...
- linix防火墙设置之顺序设置问题 -- 解决防火墙规则顺序和插入规则到指定序号的问题
转载于百度经验:https://jingyan.baidu.com/article/ae97a646ce58c2bbfd461d90.html 无论是硬件防火墙还是软件防火墙都会有一个规则序列的问题, ...
- Xvfb新建虚拟X窗口,通过x11vnc启动VNC Server并转发Xvfb启动的虚拟窗口
远程运行Linux窗口程序使用X Windows太重量级了,可以使用Xvfb新建虚拟X窗口,通过x11vnc启动VNC Server并转发Xvfb启动的虚拟窗口. 1 2 3 4 5 6 7 8 yu ...
- flask的session用法2
from flask import Flask, session, redirect, url_for, escape, request app = Flask(__name__) @app.rout ...
- importlib应用 - django
背景 仿django的中间件的编程思想 用户可通过配置,选择是否启用某个组件/某个功能,只需要配置 eg:报警系统,发邮件,发微信 ... ( 根据字符串导入模块, 利用反射找到模块下的类,实例化.执 ...
- python使用tesseract-ocr完成验证码识别
全自动区分计算机和人类的公开图灵测试(Completely Automated Public Turing test to tell Computers and Humans Apart) 简称CAP ...
- Mirror--程序访问镜像数据库的超时机制
程序在访问有镜像的数据库和无镜像的数据库时,采用的链接超时时间算法不一样,因此会导致在在有镜像的数据库上设置了15 S的超时时间,而实际的超时时间仅为3.6 S,从而导致有镜像的数据库更容易超时. 在 ...
- SchuledExecutorService 使用controller控制线程关闭
1:SchuledExecutorService 使用controller控制线程关闭 package com.li.controller; import org.springframework.s ...