承接上篇博文总结一下造成线程不安全的几个原因以及解决方法:

①抢占式执行,调度过程随机:这个没有解决办法,我们无能为力

②多个线程同时修改同一个变量:适当调整代码结构,可以避免这种情况

③针对变量的操作不是原子性的(修改操作):加锁synkronized

④内存可见性(一个线程频繁读,一个操作合适时机写):需要通过volatile这个关键字来解决内存可见性问题(PS:volatile只能解决内存可见性问题)

⑤指令重排序:同样是加锁synkronized


目录

1.synkronized的四个使用实例

1)修饰普通方法

2)修饰代码块

3)修饰静态方法

4)锁类对象

2.synkronized的三个特性

1)互斥

2)刷新内存

3)可重入

3.死锁的其他场景

4.Java标准库的线程安全类

5.wait和notify

5.1wait

1)wait做的事

2)wait结束等待做的事

5.2notify

6.面试题:wait和sleep的区别



1.synkronized的四个使用实例

1)修饰普通方法

class test{//直接修饰一个普通的方法synchronized public void func(){}
}

2)修饰代码块

class test{public  void func2(){synchronized (this){}}
}

3)修饰静态方法

class test{//修饰静态方法synchronized public static void func2(){}
}

4)锁类对象

class test{public  void func2(){synchronized (test.class){}}
}

3和4其实本质是一样的

2.synkronized的三个特性

1)互斥

synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待

其实互斥本质上是使得操作变成原子性,就比如一个上篇博文里的count++,本质上是三个指令(load,add,save),经过加锁的的操作,就会将这个指令打包成原子性

2)刷新内存

目的也是解决内存可见性问题,这个功能和volatile的功能差不多

3)可重入

先来介绍下不可重入问题:

简单的来说,就是同一个线程连续加锁两次,如果出现了死锁,就是不可重入,如果没有出现死锁,就是可重入

死锁:

外层先加了一层锁,同时代码里层也加了锁

外层锁:进入方法则开始加锁,能过加锁成功,当前锁没有其他的线程占用;里层锁:进入代码块,开始加锁,这次加锁不能成功,因为此时锁被外层占用着,得等到外层锁释放了之后,里层锁才能加锁成功。外层锁要想执行完整个方法才能释放,但是想要执行完整个方法,就得让里层锁加锁成功才能往下走

此时就造成了死锁这个情况,相当于自己给自己困住了

死锁的出现大大的降低了开发效率,然而在日常开发中,外层锁和里层锁同时写是很正常的事,那么该如何解决这个事呢?

死锁的出现显然是被实现JVM的大佬们发现了,干脆就把synkronized实现成为可重入锁,可重入锁就代表着,外层锁和里层锁同时出现也能够保证不出现死锁的情况

可重入锁内部会记录当前锁被哪个线程占用着,同时也会记录一个“加锁次数”,例如一个线程A,第一次加锁的时候很明显能够加锁成功,此时锁的内部就记录着当前占用的是A,同时加锁次数是1,后续在对A加锁的时候,并不是真正的加锁,而是单纯的将加锁的次数加一,等到解锁的时候将计数减一就行了,直到计数变成0,就代表着当前的线程解锁了

可重入锁的意义就是降低了程序员的负担,提高了开发效率,但是也带来了代价,程序中需要有更高的开销(维护锁属于哪个线程,并且记录计数的加减,降低了运行的效率)

3.死锁的其他场景

1)一个线程一把锁

就如上述的例子

2)两个线程两把锁

举一个简单的例子,这种情况就相当于两个人线下交易某件物品,到了约定的地点,一手交钱一手交货,但是此时,卖家更跟买家说先给钱,而买家却说先检查物品,两个人就这样僵持住了。

3)N个线程,M把锁

举个例子:哲学家吃面条问题

五个哲学家围坐在一张圆桌旁,桌子中央一盘通心面(面假设无限),每个人面前有一只空盘,每两个人之间放一把叉子。为了吃面,每个哲学家都必须获得两把叉子,且只能从自己左边或右边取,假如五个哲学家同时拿起右手边的叉子,那么五个人都将等待相邻哲学家手中的叉子,出现“死锁”

这个问题也叫作 环路等待 

那么如何避免这种问题呢,其实只要事先约定好就行了,针对多把锁加锁的时候,有固定的的顺序即可,所有的线程都遵循同一个规则顺序,就不会出现环路等待

4.Java标准库的线程安全类

线程不安全类:

1.ArrayList

2.LinkedList

3.HashMap

4.TreeMap

5.HashSet

6.TreeSet

7.StringBuilder

线程安全类

1.Vector

2.HashTable

3.ConcurrentHashMap

4.StringBuffer

5.String

对于前四个线程安全类,在java源码中,都能够看到synkronized的身影,而最后一个String类的源码却没有synkronized,那么为什么他会是线程安全的类呢,原因是String是不可变对象,无法在多个线程同时修改同一个String,单线程都没办法

什么是不可变对象?

从不可变对象的定义来看,其实比较简单,就是一个对象在创建后,不能对该对象进行任何更改

具体可以看这篇博文,可以更好的帮助你理解

深入理解Java中的不可变对象 - Matrix海子 - 博客园 (cnblogs.com)

5.wait和notify

由于实际开发中线程之间是抢占式执行的,因此线程之间的执行先后顺序就难以预知,但是实际开饭过程中我们希望合理的协调多个线程之间的执行先后顺序。例如篮球场上的运动员,将每个运动员比作是一个线程,运动员之间的传球到最后上篮进球,是非常合理的,所以我们也希望多个线程之间能够像运动员一样相互配合相互协作

5.1wait

wait():让当前线程进入等待的状态

wait和notify都是Object对象的方法,调用wait方法的线程,就会进入阻塞的状态,阻塞到其他线程通过notify来通知

public class Demo3 {public static void main(String[] args) {Object object = new Object();synchronized (object){System.out.println("等待前");try {object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("等待前");}}
}

执行结果:

1)wait做的事

①先释放锁

②等待其他线程的通知

③收到通知后重新获得锁,并继续往下执行

2)wait结束等待做的事

①调用其他线程notify的方法

②wait等待超时

③其他线程调用该等待线程的interrupted方法,导致wait抛出InterruptedExceotion异常

5.2notify

notify 方法是唤醒等待的线程

①方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的
其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。

②如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 "先来后到")

③在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行
完,也就是退出同步代码块之后才会释放对象锁

wait和notify都是针对同一个对象来操作的,假如现在有一个对象a,有五个线程都调用了a.wait,使得五个线程都进入堵塞状态,如果调用了a.notify方法,就会把五个当中的一个给唤醒,具体唤醒哪一个并不确定

6.面试题:wait和sleep的区别

1.wait需要搭配synkronized使用,为sleep不需要

2.wait是Object的方法,而sleep是Thread的静态方法

【JavaEE学习日记】----多线程基础(下)相关推荐

  1. Java学习日记1——基础认知

    Java学习日记1--基础认知 学习Java阶段,如果发现不正确的描述,还请指正! 首先附上Java相关下载链接和配置教程链接 Java相关软件工具下载地址:官方下载 Java环境配置(win10配置 ...

  2. caffe学习日记--lesson5: VS下新建工程,探究Blob

    caffe学习日记--lesson5: VS下新建工程,探究Blob 在VS2013下新建工程,探究caffe的数据结构Blob,并使用.熟悉caffe 1.新建空白的控制台应用程序,添加main.c ...

  3. caffe学习日记--lesson4:windows下caffe DEMO (mnist and cifar10)

    caffe学习日记--lesson4:windows下caffe DEMO (mnist and cifar10) 1.下载数据 mnist官网:http://yann.lecun.com/exdb/ ...

  4. Python学习日记-day1基础篇 字符 输出 注释

    Python学习日记-day1基础篇 字符 输出 注释 by北栀一刺 # -*- coding: utf-8 -*- """ Spyder EditorThis is a ...

  5. 【JavaEE学习日记】----多线程基础(上)

    目录

  6. 脱发篇-多线程基础(下)来看看你知道多少

    看完了,发现对你有用的话点个赞吧! 持续努力更新学习中!!多线程其他的部分点击我的头像查看更多哦! 知识点 标注:在学习中需要修改的内容以及笔记全在这里 www.javanode.cn,谢谢!有任何不 ...

  7. python学习日记(基础数据类型及其方法02)

    python的变量 python中的变量不需要声明,变量载使用前必须被赋值,变量被赋值以后才会被创建. 在python中变量就是变量,没有数据类型.我们所说的类型是变量所指向内存中的对象的类型. py ...

  8. JS学习日记--正则基础语法

    一.正则表达式 正则表达式是由普通字符及特殊字符组成的对字符串进行过滤的逻辑公式 创建方式 字符量的方式: var reg = /abc/: 构造函数 var reg = new RegExp(&qu ...

  9. Python学习日记-函数基础

    目标:定义一个函数,求n~m之间整数平方的和 第一步,自定义一个求平方的函数square() def suqare(i):j = i * ireturn j (为了练习,假装库里没有) 第二步,调用s ...

最新文章

  1. python cgi nginx_nginx uwsgi和cgi python脚本
  2. JavaWeb学习总结(九)--JDBC入门
  3. 一个ABAP程序,能够下载指定的note到本地
  4. 【计算机视觉】基于OpenCV的人脸识别
  5. error: failed to push some refs to 'https://gitee.com/xxx/xxx'
  6. flink checkpoint 恢复_干货:Flink+Kafka 0.11端到端精确一次处理语义实现
  7. LeetCode Factorial Trailing Zeroes (阶乘后缀零)
  8. html返回顶部代码(简单)
  9. iis10 asp 如何连接mdb_如何攻破一个网站
  10. JavaScript运筹帷幄,掌控全局
  11. 音乐制作宿主软件-Cubase Elements 11.0.20 MacOS
  12. c语言中handle的用法,handle什么意思_handle的用法和短语例句
  13. 如何优雅地在Word中粘贴代码
  14. 数据挖掘-K-近邻算法
  15. seo网站推广优化,网站页面的SEO优化怎么做
  16. ubuntu训练深度学习模型电脑重启解决方法
  17. 2018面试总结(阿里巴巴蚂蚁金服、饿了么、人人车等)
  18. 根据激光雷达点云中点的坐标计算ring的值
  19. win10编译OpenCV4Android系列1-Android编译环境搭建
  20. 小学计算机应用ppt,人教版小学信息技术教材.PPT.pdf

热门文章

  1. 【MYSQL基础(一)】——数据类型的详细解析. 数据库基本操作
  2. 通过检测png图片透明区实现不规则按钮
  3. pygame 飞机大战子弹的编写(七)花样年华
  4. 非智能手机斗破苍穹Java_支付宝推JAVA版 非智能手机增支付功能
  5. Dev-C++使用技巧2(亲测)(更改字体和颜色、自动保存、快捷键选项、一键排版)
  6. 深度学习笔记(七):网络宽度(卷积核个数)的一些想法
  7. 近5成人每天通勤1—2小时,赚钱太难了
  8. 每月交通费6000多!张家口女子去北京上班,每天通勤5小时坚持3年
  9. Day 86/100 手机的XYZ轴和地球的XYZ轴
  10. AEB三维数据分析图 TTC对AEB相对动能减少量的影响 xyz轴分别是TTC、自车速度、相对动能减少量