java并发编程实践-线程安全性
线程是CPU资源调度的基本单位,如果一个程序中只有一个线程,则最多只能在一个处理器上运行,如果电脑/服务器是双处理器系统,则单线程的程序只能使用一半的CPU资源,所以,多线程是提高处理器资源利用率的重要方法。比如web系统中的servlet容器,它处理请求时会针对每一个请求创建一个线程调用servlet的service方法(https://m.runoob.com/servlet/servlet-life-cycle.html),servlet的实例在容器中只会创建一个,所以就涉及到了并发操作问题(多个线程访问处理同一个资源),下图摘自菜鸟教程中给出的一个请求示例:
从上图中可以看出,如果service()方法对某一个可变的公共变量做了操作,线程a,b,c如果未作合适的同步控制的话,程序必然会出现错误,修复这个问题有三种方式:
a.不在线程之间共享该状态变量,也就是service()方法不会调用公共的变量/资源。
b.将状态变量设置为不可修改的变量,也就是service()方法调用的公共变量的值是一个恒定的值,各个线程也不能对该变量进行修改。
c.在访问公共的状态变量时进行同步控制,也就是线程b/c要等线程a对公共变量的操作执行完毕之后才能获取访问权。
一个无状态的对象一定是线程安全的,如下示例:
package main; import javax.servlet.*;
import java.io.IOException; public class StatelessFactorizer implements Servlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
int id=(int)req.getAttribute("id");
id++;
req.setAttribute("id",id);
}
@Override
public void init(ServletConfig config) throws ServletException { } @Override
public ServletConfig getServletConfig() {
return null;
} @Override
public String getServletInfo() {
return null;
} @Override
public void destroy() { }
}
上面的代码中,id虽然做了++操作,但是它是属于方法的内部变量,也是线程私有的,这个值是从前端带过来的,所以这是一中“无状态的”操作方法。
package main; import javax.servlet.*;
import java.io.IOException; public class UnsafeCounting implements Servlet {
private long count=0;
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
count++;
req.setAttribute("count",count);
}
@Override
public void init(ServletConfig config) throws ServletException { } @Override
public ServletConfig getServletConfig() {
return null;
} @Override
public String getServletInfo() {
return null;
} @Override
public void destroy() { }
}
再如上面的示例,UnsafeCounting实例的service方法由多个线程调用时,每个线程都会操作count这个公共资源,但是count++这种操作方法并不是原子性的,所以这种操作不是线程安全的。这里定义一个术语:
竞态条件(Race Condition):并发编程时,由于不恰当的执行时序而出现不正确的结果的情况。
对于有竞态条件出现的情况下,需要使用“加锁机制”来保证操作步骤的原子性,也就是多个线程对操作步骤的顺序执行,java提供了一种内置的锁机制来实现原子操作:同步代码块(synchronized block),同步代码块由两部分组成:
a.加锁的对象,是一个引用,也就是说对哪个对象进行加锁,确定了加锁的对象,所有的线程要先拿到这个对象的锁才能进行下一步的操作
b.由该锁保护的代码块,也就是哪些操作步骤必须是原子操作,就将这些需要作为原子操作的步骤放到锁所保护的代码块种即可
如下示例:
synchronized(obj){//todo 需要是原子操作的步骤}
**锁的重入**
重入是一个重要特征,就是一个线程获取到了某个锁的执行权,它再次获取该锁时,需要保证能够获取成功,
package main; public class Widget { public synchronized void doSomething(){
System.out.println("Widget doSomething "+this);
} public static void main(String[] args) {
LoggingWidget loggingWidget=new LoggingWidget();
loggingWidget.doSomething();
}
} class LoggingWidget extends Widget{
@Override
public synchronized void doSomething() {
System.out.println("LoggingWidget doSomething "+this);
super.doSomething();
}
}
---------输出--------
LoggingWidget doSomething main.LoggingWidget@74a14482
Widget doSomething main.LoggingWidget@74a14482
上面的代码中,synchronized方法修饰的是非静态方法,它的锁对象是this,也就是方法调用所在的对象,如果synchronized放到静态方法上,则其加锁的对象是Class对象;从上面的打印中,super中输出的this和子类中输出的this是一样的,如果synchronized锁不能重入的话,子类在调用doSomething的时候已经获取到了“main.LoggingWidget@74a14482”的锁,就不能调用super.doSomething()了,显然是不合理的;锁的重入则很完美地避免了该问题的产生。
是不是为了简单起见,直接将synchronized加到方法上得了?当然不是,因为还要考虑性能问题,如下实例:
class DownLoadFile extends HttpServlet{
@Override
protected synchronized void service(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
//todo 下载一个大文件
File file=new File("c:/xxxx/uuu"); //todo 对某个公共资源进行操作
}
}
上面的代码中,下载大文件的操作比较费时,如果对方法进行加锁,则性能很差,这时候可以不对下载大文件进行加锁,如下:
class DownLoadFile extends HttpServlet{
@Override
protected void service(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
//todo 下载一个大文件
File file=new File("c:/xxxx/uuu");
synchronized(this){
//todo 对某个公共资源进行操作
}
}
}
上面的代码中,性能将会提升很多,因为每个请求可以发送请求后立马进行文件下载操作,文件下载完毕后再尝试获取锁对公共资源进行操作。
以上是Java线程安全性的总结,在保证安全的同时也需要考虑性能问题,因此加锁时需要做到:
a.识别哪些操作步骤需要是原子性操作,以保证程序的安全性
b.判断哪些操作比较费时,这些比较费时的操作一定不要持有锁,因为会严重影响程序性能,记住,只对需要加锁的步骤进行加锁就可以了
java并发编程实践-线程安全性的更多相关文章
- JAVA并发编程之线程安全性
1.一个对象是否是线程安全的,取决于它是否被多个线程访问.想要使得线程安全,需要通过同步机制来协同对对象可变状态的访问. 2.修复多线程访问可变状态变量出现的错误:1.程序间不共享状态变量 2.状态变 ...
- Java并发编程 (四) 线程安全性
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.线程安全性-原子性-atomic-1 1.线程安全性 定义: 当某个线程访问某个类时,不管运行时环境 ...
- Java并发编程 (五) 线程安全性
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.安全发布对象-发布与逸出 1.发布与逸出定义 发布对象 : 使一个对象能够被当前范围之外的代码所使用 ...
- [Java 并发] Java并发编程实践 思维导图 - 第二章 线程安全性
依据<Java并发编程实践>一书整理的思维导图.
- Java并发编程实践
最近阅读了<Java并发编程实践>这本书,总结了一下几个相关的知识点. 线程安全 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任 ...
- (转)Java并发编程:线程池的使用
背景:线程池在面试时候经常遇到,反复出现的问题就是理解不深入,不能做到游刃有余.所以这篇博客是要深入总结线程池的使用. ThreadPoolExecutor的继承关系 线程池的原理 1.线程池状态(4 ...
- Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
- [Java 并发] Java并发编程实践 思维导图 - 第一章 简单介绍
阅读<Java并发编程实践>一书后整理的思维导图.
- Java并发编程:线程池的使用(转)
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
随机推荐
- chfs共享系统搭建
手机电脑文件共享搭建(http://iscute.cn/chfs) 1.下载wget http://iscute.cn/tar/chfs/2.0/chfs-linux-amd64-2.0.zip 2. ...
- 模块一:时间模块、random模块、hashlib模块、日志模块
模块:就是一系列功能的集合体,有以下分类:*解释器内建模块*python标准库(位于python解释器安装目录的lib文件夹下)*第三方模块(通过(pip install 模块名)语法下载到pytho ...
- 通过网页下载qq音乐在线听歌曲
1.输入网址 打开 qq音乐网页版 https://y.qq.com/ 2.搜索喜欢的歌曲 3.播放喜欢的歌曲 4.谷歌浏览器检查代码 5.找到 network栏 搜索 m4a 6.找到相关地址 复制 ...
- nginx的nginx.conf目录简单配置
我的nginx.conf是在 etc/nginx/目录下 我是直接在http随便找了个地方添加如下代码的: server { listen 8066; server_name 192.168.0.2 ...
- Oracle 数据库升级过程中的主要步骤
Oracle 数据库升级包括六个主要步骤. Oracle 数据库的升级步骤工作流 步骤 1:准备升级 Oracle 数据库 熟悉 Oracle 数据库新版本的特性. 确定新版本的升级路径. 选择升级方 ...
- flask、element、vue项目实战:搭建一个加密excel数据的网站
文章目录 一.前端 1.1 上传文件 1.2 数据表格 1.3 加密选项 1.4 加密数据 1.5 下载按钮 二.后端 2.1 .upload 文件上传 2.2 table 数据表格接口 2.3 en ...
- Selenium+Python上传文件方法大全
转自:https://www.jianshu.com/p/fba37cc5d5e2
- Unit Test下使用H2内存数据库
1.Maven引入包 <dependency> <groupId>com.h2database</groupId> <artifactId>h2< ...
- Unity C#代码入门
Unity C#代码入门 1. 脚本基本结构 1.1 unity生成的模板 using System.Collections; using System.Collections.Generic; us ...
- html-list
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...