不当心就鸽了几天没有更新了,这个星期回家咯。在学校的日子要努力一点才行!html
只有光头才能变强java
回顾前面:编程
本文章的知识主要参考《Java并发编程实战》这本书的前4章,这本书的前4章都是讲解并发的基础的。要是能好好理解这些基础,那么咱们日后的学习就会事半功倍。c#
固然了,《Java并发编程实战》能够说是很是经典的一本书。我是未能彻底理解的,在这也仅仅是抛砖引玉。想要更加全面地理解我下面所说的知识点,能够去阅读一下这本书,总的来讲仍是不错的。缓存
首先来预览一下《Java并发编程实战》前4章的目录究竟在讲什么吧:安全
第1章 简介微信
ps:这一部分我就不讲了,主要是引出咱们接下来的知识点,有兴趣的同窗可翻看原书~多线程
第2章 线程安全性并发
第3章 对象的共享异步
第4章 对象的组合
那么接下来咱们就开始吧~
在前面的文章中已经讲解了线程【多线程三分钟就能够入个门了!】,多线程主要是为了提升咱们应用程序的使用率。但同时,这会给咱们带来不少安全问题!
若是咱们在单线程中以“顺序”(串行-->独占)的方式执行代码是没有任何问题的。可是到了多线程的环境下(并行),若是没有设计和控制得好,就会给咱们带来不少意想不到的情况,也就是线程安全性问题
由于在多线程的环境下,线程是交替执行的,通常他们会使用多个线程执行相同的代码。若是在此相同的代码里边有着共享的变量,或者一些组合操做,咱们想要的正确结果就很容易出现了问题
简单举个例子:
public class UnsafeCountingServlet extends GenericServlet implements Servlet { private long count = 0; public long getCount() { return count; } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { ++count; // To something else... } }
可是在多线程环境下跑起来,它的count值计算就不对了!
首先,它共享了count这个变量,其次来讲++count;
这是一个组合的操做(注意,它并不是是原子性)
++count
实际上的操做是这样子的:
因而多线程执行的时候极可能就会有这样的状况:
若是说:当多个线程访问某个类的时候,这个类始终能表现出正确的行为,那么这个类就是线程安全的!
有个原则:能使用JDK提供的线程安全机制,就使用JDK的。
固然了,此部分实际上是咱们学习多线程最重要的环节,这里我就不详细说了。这里只是一个总览,这些知识点在后面的学习中都会遇到~~~
使用多线程咱们的目的就是为了提升应用程序的使用率,可是若是多线程的代码没有好好设计的话,那未必会提升效率。反而下降了效率,甚至会形成死锁!
就好比说咱们的Servlet,一个Servlet对象能够处理多个请求的,Servlet显然是一个自然支持多线程的。
又如下面的例子来讲吧:
public class UnsafeCountingServlet extends GenericServlet implements Servlet { private long count = 0; public long getCount() { return count; } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { ++count; // To something else... } }
从上面咱们已经说了,上面这个类是线程不安全的。最简单的方式:若是咱们在service方法上加上JDK为咱们提供的内置锁synchronized,那么咱们就能够实现线程安全了。
public class UnsafeCountingServlet extends GenericServlet implements Servlet { private long count = 0; public long getCount() { return count; } public void synchronized service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { ++count; // To something else... } }
虽然实现了线程安全了,可是这会带来很严重的性能问题:
这就致使了:咱们完成一个小小的功能,使用了多线程的目的是想要提升效率,但如今没有把握得当,却带来严重的性能问题!
在使用多线程的时候:更严重的时候还有死锁(程序就卡住不动了)。
这些都是咱们接下来要学习的地方:学习使用哪一种同步机制来实现线程安全,而且性能是提升了而不是下降了~
书上是这样定义发布和逸出的:
发布(publish) 使对象可以在当前做用域以外的代码中使用
逸出(escape) 当某个不该该发布的对象被发布了
常见逸出的有下面几种方式:
静态域逸出:
public修饰get方法:
方法参数传递我就再也不演示了,由于把对象传递过去给另外的方法,已是逸出了~
下面来看看该书给出this逸出的例子:
逸出就是本不该该发布对象的地方,把对象发布了。致使咱们的数据泄露出去了,这就形成了一个安全隐患!理解起来是否是简单了一丢丢?
上面谈到了好几种逸出的状况,咱们接下来来谈谈如何安全发布对象。
安全发布对象有几种常见的方式:
public static Person = new Person()
;
从上面咱们就能够看到,使用多线程会把咱们的系统搞得挺复杂的。是须要咱们去处理不少事情,为了防止多线程给咱们带来的安全和性能的问题~
下面就来简单总结一下咱们须要哪些知识点来解决多线程遇到的问题。
使用多线程就必定要保证咱们的线程是安全的,这是最重要的地方!
在Java中,咱们通常会有下面这么几种办法来实现线程安全问题:
count++
操做,可使用AtomicLong来实现原子性,那么在增长的时候就不会出差错了!)何为原子性?何为可见性?当初我在ConcurrentHashMap基于JDK1.8源码剖析中已经简单说了一下了。不了解的同窗能够进去看看。
在多线程中不少时候都是由于某个操做不是原子性的,使数据混乱出错。若是操做的数据是原子性的,那么就能够很大程度上避免了线程安全问题了!
count++
,先读取,后自增,再赋值。若是该操做是原子性的,那么就能够说线程安全了(由于没有中间的三部环节,一步到位【原子性】~原子性就是执行某一个操做是不可分割的,
- 好比上面所说的count++
操做,它就不是一个原子性的操做,它是分红了三个步骤的来实现这个操做的~
- JDK中有atomic包提供给咱们实现原子性操做~
也有人将其作成了表格来分类,咱们来看看:
使用这些类相关的操做也能够进他的博客去看看:
对于可见性,Java提供了一个关键字:volatile给咱们使用~
volatile经典总结:volatile仅仅用来保证该变量对全部线程的可见性,但不保证原子性
咱们将其拆开来解释一下:
使用了volatile修饰的变量保证了三点:
通常来讲,volatile大多用于标志位上(判断操做),知足下面的条件才应该使用volatile修饰变量:
参考资料:
在多线程的环境下,只要咱们不使用成员变量(不共享数据),那么就不会出现线程安全的问题了。
就用咱们熟悉的Servlet来举例子,写了那么多的Servlet,你见过咱们说要加锁吗??咱们全部的数据都是在方法(栈封闭)上操做的,每一个线程都拥有本身的变量,互不干扰!
在方法上操做,只要咱们保证不要在栈(方法)上发布对象(每一个变量的做用域仅仅停留在当前的方法上),那么咱们的线程就是安全的
在线程封闭上还有另外一种方法,就是我以前写过的:ThreadLocal就是这么简单
使用这个类的API就能够保证每一个线程本身独占一个变量。(详情去读上面的文章便可)~
不可变对象必定线程安全的。
上面咱们共享的变量都是可变的,正因为是可变的才会出现线程安全问题。若是该状态是不可变的,那么随便多个线程访问都是没有问题的!
Java提供了final修饰符给咱们使用,final的身影咱们可能就见得比较多了,但值得说明的是:
就好像下面这个HashMap,用final修饰了。可是它仅仅保证了该对象引用hashMap变量
所指向是不可变的,可是hashMap内部的数据是可变的,也就是说:能够add,remove等等操做到集合中~~~
final HashMap<Person> hashMap = new HashMap<>();
不可变的对象引用在使用的时候仍是须要加锁的
要想将对象设计成不可变对象,那么要知足下面三个条件:
String在咱们学习的过程当中咱们就知道它是一个不可变对象,可是它没有遵循第二点(对象全部的域都是final修饰的),由于JVM在内部作了优化的。可是咱们若是是要本身设计不可变对象,是须要知足三个条件的。
不少时候咱们要实现线程安全未必就须要本身加锁,本身来设计。
咱们可使用JDK给咱们提供的对象来完成线程安全的设计:
很是多的"工具类"供咱们使用,这些在日后的学习中都会有所介绍的~~这里就不介绍了
正确使用多线程可以提升咱们应用程序的效率,同时给咱们会带来很是多的问题,这些都是咱们在使用多线程以前须要注意的地方。
不管是不变性、可见性、原子性、线程封闭、委托这些都是实现线程安全的一种手段。要合理地使用这些手段,咱们的程序才能够更加健壮!
能够发现的是,上面在不少的地方说到了:锁。但我没有介绍它,由于我打算留在下一篇来写,敬请期待~~~
书上前4章花了65页来说解,而我只用了一篇文章来归纳,这是远远不够的,想要继续深刻的同窗能够去阅读书籍~
以前在学习操做系统的时候根据《计算机操做系统-汤小丹》这本书也作了一点点笔记,都是比较浅显的知识点。或许对你们有帮助
参考资料:
若是文章有错的地方欢迎指正,你们互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同窗,能够关注微信公众号:Java3y。为了你们方便,刚新建了一下qq群:742919422,你们也能够去交流交流。谢谢支持了!但愿能多介绍给其余有须要的朋友
文章的目录导航: