再讲线程安全:

一、脏读

脏读:在于读字,意在在读取实例变量时,实例变量有可能被另外一个线程更改了,导致获取到的数据出现异常。

在非线程安全的情况下,如果线程A与线程B 共同使用对象实例C中的方法method,如果实例C存在实例变量,同时在method中会操作这个实例变量a,则有可能出现脏读的情况。

也就是期望值不同,读取的数据不同,因为线程A与B会同时使用实例C的方法。

例如:

  1. private String name;
  2.  
  3. public static void main(String[] args){
  4. Test2 test2 = new Test2();
  5. Thread t = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. // System.out.println(Thread.currentThread().getName());
  9. test2.testUnsafe("a","我是A");
  10. }
  11. });
  12.  
  13. Thread t2 = new Thread(new Runnable() {
  14. @Override
  15. public void run() {
  16. // System.out.println(Thread.currentThread().getName());
  17. test2.testUnsafe("b","我是B");
  18. }
  19. });
  20.  
  21. t.start();
  22. t2.start();
  23. }
  24.  
  25. public void testUnsafe(String name,String param){
  26. this.name = name;
  27. try {
          Thread.sleep(1000);
          System.out.println(this.name+":"+param);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
  28. }

执行结果如下:

代码中加上了sleep 为了模拟运算所需的时间。

可以看出来,大概是这样的过程:线程B在run时首先抢到了资源,并运行了实例方法,并把实例变量name修改为b,

然后继续执行,这时候线程A抢回了资源(因为不是同步的),它把实例变量那么又修改为b,这个是时候开始执行其他逻辑操作(这里用sleep模拟),

然后2个线程轮番执行最后的操作-打印。

因为这个时候实例变量name 已经变为a了, 所以线程B 出现脏读,和期望输出的  b:我是B  结果出现差异。

如果操作的是不同的对象实例(在synchronized 同步里 jvm 会创建多个锁,下面的例子控制2个实例,也就是创建了2个锁),就不会出现这个问题了,还有如果name不是实例变量,只是私有变量的话也不会出现这种情况。

修改一下看看:

  1. public static void main(String[] args){
  2.  
  3. Thread t = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. Test2 test2 = new Test2();
  7. test2.testUnsafe("a","我是A");
  8. }
  9. });
  10.  
  11. Thread t2 = new Thread(new Runnable() {
  12. @Override
  13. public void run() {
  14. Test2 test2 = new Test2();
  15. test2.testUnsafe("b","我是B");
  16. }
  17. });
  18.  
  19. t.start();
  20. t2.start();
  21. }

输出结果:

如果想要保证使用实例变量而又不出现这种问题,怎么办,同步~ 可以使用 synchronized 方法或 synchronized代码块。

例如:

  1. public synchronized void testUnsafe(String name,String param){
  2. this.name = name;
  3. try {
  4. Thread.sleep(1000);
  5. System.out.println(this.name+":"+param);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9.  
  10. }

输出结果:

关于synchronized 想看的可以下看之前的随笔(《JAVA 多线程(1)》)。

这里我想记录一下类与对象和实例的个人理解():

Class 是类,编写时候我们称为类,编译好的class我们可以称为class对象,或者说类对象,由类对象new出来的是实例或者说叫对象实例也就是instance。

它们分时期,分关系。类是抽象的概念,对象为具体的事物,人是类,张三是人,张三是人这个类具象,是一个对象实例的存在,它的嘴巴是实例变量,吃饭是实例方法,米饭和菜是吃饭这个对象方法中的方法私有变量,

打比方张三、李四在一起去吃饭(2个线程),都调用了吃饭这个方法(吃饭这个方法是人类共有的),分布是土豆A与土豆B,如果不做同步,有可能2个人会夹到同一条土豆丝。

好吧,上面的比方看看就好。

二、可重入锁

如果一个线程获取了或者说抢到了cpu资源,拿到了实例对象锁,那么在实例方法中在调用其他同步方法时,依然会获取到同一个锁,因为已经抢到了,

比方说,一个屋子有3个房间(方法),张三(线程),抢到了房间1的锁,由于李四和王五压根就没进入这个屋子,所有他们俩必须等张三出来才行,这样的话

张三进过房间1后,还能获得房间2、3的锁。

这个锁指的是对象锁,或者说一个实例对象只有一把锁会更好理解。因为李四和王五想进房间2,虽然不是房间1,但是只有一把锁,这个锁在张三手上。

可重入锁个人感觉主要贡献在于可继承,如果B基础了A,那么如果操作B,获取到了实例对象B的锁,那么也可以继续获得A的锁。

三、异常释放锁

当线程出现异常时,锁会自动释放。

四、同步不具有继承

如果类A 有同步方法 methodA,类B继承了类A并重写了methodA 方法,但是没有加上同步关键字,那么实际上,B类中重写的methodA 并不是同步方法。

五、同步方法与同步代码块

之前在1中写过,这里再提及,同步代码块同样时获得的是对象锁,当不同的实例方法各自有各自的同步代码块,当线程A访问methodA 时获得实例对象锁,如果线程B访问的实例对象与线程A相同,那么线程B如想

访问methodB中的同步代码块时同样需要等线程A使用完methodA,释放对象锁,才能执行。

六、非当前实例对象锁(任意对象监视器)

优点:同一个类中的多个方法或者同一个方法中有多个代码块,为了提高性能,使用多个对象锁,来加快运行速度,或者说减少阻塞。

例如:

  1. private Object object = new Object();
  2.  
  3. public static void main(String[] args){
    Test2 test2 = new Test2();
    Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
    test2.test();
    }
    });
  4.  
  5. Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
    test2.test2();
    }
    });
  6.  
  7. t.start();
    t2.start();
    }
    public void test2(){
    synchronized (object){
    System.out.println("我是第二个块"+Thread.currentThread().getName());
    }
    }
  8.  
  9. public void test(){
    synchronized (this){
    try {
    System.out.println("我是第一个块 开始:"+Thread.currentThread().getName());
    Thread.sleep(100);
    System.out.println("我是第一个块 结束:"+Thread.currentThread().getName());
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }

输出:

通过控制台打印结果可以看出,在A线程访问test方法执行的同时,B线程同样访问可test2方法。

所以,他们互不干扰,因为不是一把锁。

但是问题又出来,就因为这样提高了性能,但是又有可能会导致脏读,如果methodA与methodB 中有对同一个实例变量的写操作,那么及有可能出现脏读。

如下:

  1. private Object object = new Object();
  2. private static List<String> list = new ArrayList<>();
  3. public static void main(String[] args){
  4. Test2 test2 = new Test2();
  5. Thread t = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. test2.test();
  9. }
  10. });
  11.  
  12. Thread t2 = new Thread(new Runnable() {
  13. @Override
  14. public void run() {
  15. test2.test2();
  16. }
  17. });
  18.  
  19. t.start();
  20. t2.start();
  21. try {
  22. Thread.sleep();
  23. System.out.println(list.size());
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. public void test2(){
  29. synchronized (object){
  30. judge("test2");
  31. }
  32. }
  33.  
  34. public void test(){
  35. synchronized (this){
  36. judge("test");
  37.  
  38. }
  39. }
  40.  
  41. public void judge(String what){
  42. try {
  43. if(list.size() < 1){
  44. Thread.sleep(2000);
  45. list.add(what);
  46. }
  47. } catch (InterruptedException e) {
  48. e.printStackTrace();
  49. }
  50. }

输出结果:

出现了,以为是异步的,所以线程A和线程B都可以同时访问到judge方法,又同时进入到了if语句中,导致最终结果输出为2,而不是我们期望的1。

所以,方法就是,给judge加上同步锁:

  1. synchronized (list){
  2. if(list.size() < 1){
  3. Thread.sleep(2000);
  4. list.add(what);
  5. }
  6. }

这样拿到list对象锁的操作就变成同步的了~

明儿个继续~

JAVA 多线程(3)的更多相关文章

  1. 40个Java多线程问题总结

    前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...

  2. Java多线程基础知识篇

    这篇是Java多线程基本用法的一个总结. 本篇文章会从一下几个方面来说明Java多线程的基本用法: 如何使用多线程 如何得到多线程的一些信息 如何停止线程 如何暂停线程 线程的一些其他用法 所有的代码 ...

  3. Java多线程系列--“JUC锁”03之 公平锁(一)

    概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...

  4. Java多线程系列--“JUC锁”04之 公平锁(二)

    概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...

  5. Java多线程--让主线程等待子线程执行完毕

    使用Java多线程编程时经常遇到主线程需要等待子线程执行完成以后才能继续执行,那么接下来介绍一种简单的方式使主线程等待. java.util.concurrent.CountDownLatch 使用c ...

  6. Java多线程 2 线程的生命周期和状态控制

    一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...

  7. java 多线程 1 线程 进程

    Java多线程(一).多线程的基本概念和使用 2012-09-10 16:06 5108人阅读 评论(0) 收藏 举报  分类: javaSE综合知识点(14)  版权声明:本文为博主原创文章,未经博 ...

  8. 一起阅读《Java多线程编程核心技术》

    目录 第一章 Java多线程技能 (待续...)

  9. 第一章 Java多线程技能

    1.初步了解"进程"."线程"."多线程" 说到多线程,大多都会联系到"进程"和"线程".那么这两者 ...

  10. java从基础知识(十)java多线程(下)

    首先介绍可见性.原子性.有序性.重排序这几个概念 原子性:即一个操作或多个操作要么全部执行并且执行的过程不会被任何因素打断,要么都不执行. 可见性:一个线程对共享变量值的修改,能够及时地被其它线程看到 ...

随机推荐

  1. Maven - 在Eclipse中创建Maven项目

    本文的前提条件: windows7-64bit jdk1.8.0 Maven-3.5.0 1- 更新Eclipse中Maven配置 1.1- 修改Eclipse根目录下eclipse.ini文件 D: ...

  2. DevOps - 参考信息

    DevOps DevOps(Development+Operations)强调共同对业务目标负责,以实现用户价值作为唯一的评判标准:保证产品功能及时实现.成功部署和稳定使用: 是一种重视软件开发人员( ...

  3. 【新手向】使用nodejs抓取百度贴吧内容

    参考教程:https://github.com/alsotang/node-lessons 1~5节 1. 通过superagent抓取页面内容 superagent .get('http://www ...

  4. 推荐一篇关于java集合的博文,写的很nice

    这也是我自己在网上看到的一篇博文,作者的博文都很棒,以后还会持续为大家推荐好的博文,只要大家不骂我只会转别人的博文,自己不会写,其实这些都是基础,前辈们已经在实践中总结的很细很全了,所以也没必要去总结 ...

  5. mysql 开发基础系列5 字符串函数

    字符串函数 1.  concat (s1,s2,...sn) 连接里面的参数成一个字符串(注意上面写错了函数名称) SELECT CONCAT('ddd','CCC'); 2.  insert(str ...

  6. linux 命令 — sed

    sed stream editor,流编辑器 查找替换 sed 's/pattern/replace_string/' file 替换每一行第一次出现的pattern,将替换后的文本输出到stdout ...

  7. go使用rpc

    RPC是远程过程调用的缩写(Remote Procedure Call),通俗地说就是调用远处的一个函数,是分布式系统中不同节点间流行的通信方式.Go语言的标准库提供了一个简单的RPC实现 serve ...

  8. MySQL索引建立和使用的基本原则

    合理设计和使用索引 在关键字段的索引上,建与不建索引,查询速度相差近100倍.   差的索引和没有索引效果一样.   索引并非越多越好,因为维护索引需要成本.   每个表的索引应在5个以下,应合理利用 ...

  9. 飞跃式发展的后现代 Python 世界

    飞跃式发展的后现代Python世界 如果现代Python有一个标志性特性,那么简单说来便是Python对自身定义的越来越模糊.在过去的几年的许多项目都极大拓展了Python,并重建了“Python”本 ...

  10. python集合类型

    集合类型简介 集合也是容器,其内元素都是无序.唯一.不可变的.它常用来做成员测试.移除重复数据.数据计算(比如交集.并集.差集). 集合Set是dict的无value版.集合也使用大括号包围: > ...