一、使用多线程遇到的问题

1.1  线程安全问题

在前面的文章中,多线程主要是为了提高我们应用程序的使用率。但同时,这会给我们带来很多安全问题。

如果我们在单线程中以顺序(串行->独占)的方式执行代码是没有任何问题的。但是到了多线程环境下(并行),如何没有设计和控制得好,就会给我们带来很多意想不到得状况,也就是线程安全问题。

因为在多线程得环境下,线程是交替执行得,一般他们会使用多个线程执行相同得代码,如果在此相同得代码里面有共享得变量,或者一些组合操作,我们想要的正确结果就很容易出现了问题。

简单举个例子:

下面的程序在单线程中跑起来,是没有问题的

1 public class UnsafeCountingServlet extends GenericServlet implementsServlet {2 private long count = 0;3

4 public longgetCount() {5 returncount;6 }7

8 public void service(ServletRequest servletRequest, ServletResponse servletResponse) throwsServletException, IOException {9

10 ++count;11 //To something else...

12 }13 }

但是在多线程环境下跑起来,它的count值计算就不对了。

首先,它共享了count这个变量,其次来说++count实际上是一个组合操作(并不是原子性的)

++count实际上操作是

读取count值

将值+1

将计算结果写入count

于是多线程执行的时候很可能出现这样的情况

当线程A读取到count值是8的时候,同时线程B也进去这个方法上了,也是读取count值为8

它俩同时对值+1

将计算结果写入到count。但是写入到count的结果是9

也就是说:两个线程进来了,但是正确的结果应该是10,而它返回的是9,这是不正常的!

如果说:当多个线程访问某个类的时候,这个类始终能表现出正确的行为,那么这个类是线程安全的。

有个原则:能够使用JDK提供的线程安全机制,就使用JDK的

1.3 性能问题

使用多线程我们的目的就是为了提供应用程序的使用率,但是多线程的代码没有好好设计的话,那未必会提高效率

反而会降低效率,甚至有可能造成死锁

就比如我们的Servlet,一个Servlet对象可以处理多个请求的,Servlet显然是一个天然支持多线程的。

又以下面的例子来说吧

public class UnsafeCountingServlet extends GenericServlet implementsServlet {private long count = 0;public longgetCount() {returncount;

}public void service(ServletRequest servletRequest, ServletResponse servletResponse) throwsServletException, IOException {++count;//To something else...

}

}

上面我们已经说了,这个类是线程不安全的。最简单的方式:如果我们在service方法上加上JDK为我们提供的内置锁synchronized,那么我们就可以实现线程安全了。

1 public class UnsafeCountingServlet extends GenericServlet implementsServlet {2 private long count = 0;3

4 public longgetCount() {5 returncount;6 }7

8 public void synchronized service(ServletRequest servletRequest, ServletResponse servletResponse) throwsServletException, IOException {9

10 ++count;11 //To something else...

12 }13 }

虽然实现了线程安全了,但是这会带来很严重的性能问题:

每个请求都得等待上一个请求的service方法处理了以后才可以完成对应的操作

这就导致了:我们完成一个小小的功能,使用多线程的目的是想要提高效率,但现在没有把握得当,却带来了严重的性能问题

在使用多线程的时候,更严重的时候还有死锁(程序卡住就不动了)

这些都是我们接下来要学习的地方:学习使用哪种同步机制来实现线程安全,并且性能是提高了而不是降低了

二、对象的发布与溢出

书上是这样定义发布与逸出的

发布(publish)使对象能够在当前作用域之外的代码中使用

逸出(escape)当某个不应该发布的对象被发布了

常见的逸出的有下面几种方式

静态域逸出

public修饰的get方法

方法参数传递

隐式的this

静态域溢出

public修饰get方法

方法参数传递我就不再演示了,因为把对象传递过去给另外的方法,已经是逸出了。

下面来看看this逸出的例子

逸出就是本不应该发布对象的地方,把对象发布了。导致我们的数据泄漏出去了,这就造成了安全隐患

2.1 安全发布对象

上面谈到了好几种逸出的情况,我们接下来谈谈如何安全发布对象

安全发布对象有常用的几种方式

在静态域中直接初始化:public static Person = new Person();

静态初始化由JVM在类的初始化阶段就执行了,JVM内部存在着同步机制,致使这种方式我们可以安全发布对象

对应的引用保存到volatile或者AtomicReference引用中

保证了该对象的引用的可见性和原子性

由final修饰

该对象是不可变的,那么线程就一定是安全的,所以是安全发布

由锁来保护

发布和使用的时候都需要加锁,这样才能保证该对象不会逸出

三、解决多线程遇到的问题

从上面我们就可以可能到,使用多线程会把我们的系统搞得挺复杂的,是需要我们去处理很多事情的,为了防止多线程给我们带来的安全和性能的问题,下面就来简单总结一下我们需要哪些知识点来解决多线程遇到的问题

3.1简述解决线程安全性的方法

使用多线程就一定要保证我们的线程是安全的,这是最重要的地方。

在Java中,我们一般会有下面这么几种方法来实现线程的安全问题:

无状态(没有共享变量)

使用final使该引用变量不可变(如果引用变量也引用了其他的对象,那么无论是发布或者使用时都需要加锁)

加锁(内置锁,显示Lock锁)

使用JDK为我们提供的类来实现线程安全(此部分类就很多了)

原子性(就比如上面的count++,使用AtomicLong来实现原子性,那么在增加的时候就不会出现差错了)

容器(ConcurrentHashMap等等)

3.2原子性和可见性

何为原子性,何为可见性。

3.2.1原子性

在多线程中很多时候都是因为某个操作不是原子性的,使数据混乱出错。如果操作的数据是原子性的,那么就可以很大程度上避免了线程安全问题。

count++,先读取,后自增,再赋值。如果该操作是原子性的,那么就可以说是线程安全的(因为没有中间三步环节,一步到位)原子性

原子性就是执行某一个操作不可分割的,

就比如上面所说的count++操作,它就不是一个原子性的操作,它是分三个步骤的来实现这个操作的

JDK中有atomic包提供给我们实现原子性操作

也有人将其做成了表格来分类,看看

3.2.2可见性

对于可见性,Java提供了一个关键字:volatile给我们用

我们可以简单的认为:volatile是一种轻量级的同步机制

volatile经典总结:volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性

我们将其拆开解释一下:

保证该变量对所有线程的可见性

在多线程的环境下:当这个变量修改时,所有线程都会知道该变量被修改了,也就是所谓的可见性

不保证原子性

修改变量赋值,实质上是在JVM中分了好几步,而在这几步内(从装载变量到修改)它是不安全的

使用volatile修饰的变量保证了三点:

一旦你完成写入,任何访问这个字段的线程将会得到最新的值

在你写入前,会保证所有之前发生的事已经发生,并且任何更新过的数据值也是可见的,因为内存屏障会把之前的写入值都刷新到缓存。

volatile可以防止重排序(重排序指的是程序执行的时候,CPU、编译器可能会对执行顺序做一些调整,导致执行顺序并不是从上往下的。从而出现了一些意想不到的效果)。而如果声明了volatile,那么CPU、编译器就会知道这个变量是共享的,不会被缓存在寄存器或者其他不可见的地方。

一般来说,volatile大多用于标志位上(判断操作)满足下面的条件才应该使用volatile变量

修改变量时不依赖变量的当前值(因为volatile是不保证原子性的)

该变量不会纳入到不可变条件中(加锁就没必要使用volatile这种轻量级同步机制了)

3.3 线程封闭

在多线程的环境下,只要我们不使用成员变量(不共享数据),那么就不会出现线程安全问题了。

就用我们熟悉的Servlet来举例子,写了那么多的Servlet,从没有加锁,我们所有的数据都是在方法上操作的,每个线程都拥有自己的变量互不干扰。

在方法上操作,只要我们保证不要再栈(方法)上发布对象(每个变量的作用域仅仅停留在当前的方法上),那么我们的线程就是安全的。

再线程封闭上还有另一种方法,就是之前提过的ThreadLocal

使用这个类的API就可以保证每个线程自己独占一个变量。

3.4 不变性

不可变对象一定是线程安全的。

上面我们共享的变量都是可变的,正是由于可变的才会出现线程安全问题。如果该状态是不可变的,那么随便多个线程访问都是没有问题的。

Java提供了final修饰符给我们使用,final的身影我们可能就见得比较多了,但值得说明的是:

final不仅仅是不能修改变量的引用,但是引用里面的数据是可以改的。

1 final HashMap hashMap = new HashMap<>();

不可变对象引用在使用的时候还是需要加锁的。

或者把Person类也设计成是一个线程安全的类

因为内部的状态是可变的,不加锁或者Person不是线程安全类,操作都是危险的。

要想将对象设计成不可变对象,那么需要满足以下三个条件

对象创建后状态就不能修改

对象所有域都是final修饰的

对象是正确创建的,没有this引用逸出

String我们在学习的过程中我们就知道它是一个不可变对象,但是它没有遵循第二点,因为JVM在内部做了优化。但是我们如果要自己设计不可变对象,是需要满足三个条件的。

3.5线程安全性委托

很多时候我们要实现线程安全未必就需要自己加锁,自己来设计

我们可以使用JDK给我们提供的对象来完成线程安全的设计

非常多的工具类以后会介绍

四、最后

正确使用多线程能够提高我们应用程序的效率,同时给我们会带来非常多的问题,这些都是我们在使用多线程之前需要注意的地方。

无论是不变性、可见性、原子性、线程封闭、委托这些都是实现线程安全的一种手段。要合理地使用这些手段,我们的程序才可以更加健壮!

java中多线程重要吗_Java基础知识_多线程必要知识点相关推荐

  1. java继承原理内存角度_Java基础知识巩固

    最近发现自己的Java基础知识还是有点薄弱,刚好有点空闲时间进行再补一补,然后进行整理一下,方便自己以后复习.其实个人认为Java基础还是很重要的,不管从事Java后端开发还是Android开发,Ja ...

  2. java 哈希表和向量_Java基础知识笔记(一:修饰词、向量、哈希表)

    一.Java语言的特点(养成经常查看Java (1)简单性:Java语言是在C和C++计算机语言的基础上进行简化和改进的一种新型计算机语言.它去掉了C和C++最难正确应用的指针和最难理解的多重继承技术 ...

  3. java不死神兔总数_Java基础知识强化之IO流笔记13:递归之不死神兔问题(斐波那契数列)...

    1.这个问题是如下的: 有一对兔子,从出生后第3个月起,每个月都生一对兔子,小兔子长到第3个月又生一对兔子,加入兔子都不死,问第20个月兔子的对数? 分析:我们找规律 兔子对数 第1个月:   1 第 ...

  4. java保护型数据成员_Java基础知识笔记第四章:类和对象

    编程语言的几个发展阶段 面向机器语言 面向过程语言 面向对象语言:封装.继承.多态 类 类声明 classPerson{ ....... }class植物{ ....... } 类体 类使用类体来描述 ...

  5. java中的比较运算符_Java基础---Java中的比较运算符(十三)

    Java中的比较运算符 比较运算符用于判断两个数据的大小,例如:大于.等于.不等于.比较的结果是一个布尔值( true 或 false ). Java 中常用的比较运算符如下表所示: 注意哦: 1. ...

  6. JAVA中的remote接口_JAVA基础:TravelAgentRemote(远程接口)

    创建TravelAgent EJB的第一步是定义它的远程接口.该接口规定了允许客户端调用的EJB业务方法.客户端与TravelAgent的createCaben()和findCabin()方法进行交互 ...

  7. java返回组件所在窗体_Java基础知识笔记第九章:组件及事件处理

    java Swing 图形用户界面(GUI : Graphics User Interface) 窗口 JFrame常用方法 JFrame()创建一个无标题的窗口. JFrame(String s)创 ...

  8. java异或什么意思_java基础知识,数据类型,运算符(003)

    一.java注释. 单行注释与取消快捷键:shift+ctrl+c或则ctrl+/. 多行注释:shift+ctrl+/,多行注释取消shift+ctrl+. 文档注释: 以"/**&quo ...

  9. 超详细的Java面试题总结之JavaWeb基础知识总结

    超详细的Java面试题总结之JavaWeb基础知识总结 Java互联网架构师08-29 16:01 打开网易新闻 查看更多精彩图片 Servlet总结: 在Java Web程序中,Servlet主要负 ...

最新文章

  1. mysql基于时间盲注_MYSQL基于时间的盲注详解
  2. 齐博地方门户系统v5_社区团购小程序哪个好?-小猪V5社区团购系统,社区团购系统,社区团购小程序,专注社区团购系统研发...
  3. 编程方法学22:NameSurfer概述
  4. vector的基本用法 (详解 + 代码演示)
  5. 选择排序 自带时间复杂度分析
  6. 微软Visual Studio 14 CTP 2 发布
  7. [html] 使用canvas制作一个印章
  8. orm对象关系映射演练 一行就是一个对象
  9. hdwiki下model目录功能
  10. android netd和kernelframeworks的通信逻辑
  11. Tomcat Connector的BIO与NIO模式的比较及区别
  12. 4.vue常见指令v-for的基本使用
  13. CSI笔记【2】:正交频分多路复用技术/OFDM
  14. 线性代数——向量、向量加法、向量数乘
  15. 美摄SDK 局部特效接入文档
  16. shell的基础学习(二)
  17. 第三方开发的网贷系统安全如何保障
  18. QT中鼠标移动到时会出现小手
  19. 公司注册商标的重要性
  20. 惠普c7000服务器装系统,C7000刀片系统安装实施方案.docx

热门文章

  1. 错误:'BasicLSTMCell' object has no attribute '_kernel'
  2. HDU 3832 Earth Hour
  3. webpack+ES6+Sass搭建多页面应用
  4. word2vec----CBOW
  5. 十天精通CSS3学习笔记 part3
  6. Linked List Cycle | ||
  7. 报表性能优化方案之善用参数注入
  8. 推荐微软Windows 8 Metro应用开发虚拟实验室
  9. servlet请求转发
  10. SpaceBase – 基于 Sass 的响应式 CSS 框架