热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

(三)java并发编程线程的安全性

(1)什么是线程安全?当一个类被多个线程访问的时,这个类始终能表现出正确的行为,那么就称这个类是线程安全的。(2)什么是有状态对象

(1)什么是线程安全?

当一个类被多个线程访问的时,这个类始终能表现出正确的行为,那么就称这个类是线程安全的。

 

(2)什么是有状态对象,什么是无状态对象呢?无状态对象一定是线程安全的。

有状态对象,就是有数据存储功能的对象。有状态对象,就是有示例变量的对象,可以保存数据,是非线程安全的。

无状态对象,就是不能保存数据。就是没有成员变量的对象,不能保存数据,是线程安全的。

 

(3)安全与不安全

不安全就是存在的数据是多个线程共享的才不安全,而每个线程自己独有的局部变量,则是安全的。所以可以总结如下:

1)常量始终是线程安全的,因为只存在只读操作。

2)每次调用方法前都新建一个实例是线程安全的,因为不会访问共享资源(共享堆资源)。

3)局部变量是安全的。因为每执行一个方法,都会独立的空间创建局部变量,它不是共享资源。局部变量包括方法的参数变量和方法的内部变量。

3.1)举个栗子

线程安全的调用方式,每次都new一个新的对象,每个对象在自己的堆空间中,多个线程不共享。

 

   public classSafeThreadClient {

      publicstatic voidmain(String[]args) {

 

          for(inti&#61;0;i<10000;i&#43;&#43;){

           UnsafeSequenceunsafeSequence &#61;newUnsafeSequence();

           Threadthread &#61;newThread(unsafeSequence);

           thread.start();

           }

 

       }

    }

线程安全不安全调用&#xff0c;如下&#xff0c;new一次&#xff0c;多个线程共享堆中的这一个对象的成员变量。

   public classUnsafeThreadClient {

      publicstatic voidmain(String[]args) {

           UnsafeSequenceunsafeSequence &#61;newUnsafeSequence();

          for(inti&#61;0;i<10000;i&#43;&#43;){

           Threadthread &#61;newThread(unsafeSequence);

           thread.start();

           }

 

       }

    }

   

   public classUnsafeSequenceimplementsRunnable{

      privateintvalue;

      publicintgetNext(){

      returnvalue&#43;&#43;;

       }

      publicvoidrun(){

           System.out.println(getNext());

       }

 

    }

 

3.2)上述代码内存分析&#xff1a;

非线程安全&#xff1a;


 

线程安全&#xff1a;


就像上面的例子&#xff0c;value每次都要加1&#xff0c;但是可能会出现这样的问题&#xff0c;当两个线程同时到达&#xff08;读取--修改--写入&#xff09;&#xff0c;同时读取value&#xff0c;同时对value&#43;1&#xff0c;然后写入到内存中&#xff0c;假如刚才的操作value4&#xff0c;然后两个线程同时操作&#xff0c;value的值应该加两次&#xff0c;但可能两个线程在读value值的时候都读到的是4&#xff0c;都会加1&#xff0c;写入内存都会认为是5。 而实际上两个线程对value应该加了两次&#xff0c;此时的value6才对&#xff0c;这就是在多线程情况下&#xff0c;这个类表现的某些错误行为&#xff0c;我们就说这个类是线程不安全的。

 

而究其结果是因为&#xff0c;在 读出--修改--写入这个过程中&#xff0c;其中的一个状态都依赖于上一个状态。

最终会导致数据的不完整性&#xff0c;或不确定性&#xff0c;就是在不恰当的执行顺序&#xff0c;导致不正确的结果&#xff0c;有一个名字就是线程交替执行的时候&#xff0c;就会发生竞争条件。

 

(4)竞争条件&#xff1f;

当某个计算的正确性取决于多个线程的交替时序时&#xff0c;那么就会发生竞争条件。

(5)复合操作

要避免竞争条件问题&#xff0c;就必须在某个线程修改该变量的时候&#xff0c;通过某个方式阻止其他线程使用这个变量&#xff0c;从而确保其他线程只能在修改操作完成之前或者之后读取和修改状态&#xff0c;而不是在修改状态中。

加入A B两个线程&#xff0c;从A执行角度来看&#xff0c;B执行的时候&#xff0c;要么B全部执行完&#xff0c;要么完全不执行B。那么AB的执行来说是原子的。原子操作是&#xff0c;对于访问同一个状态的所有操作&#xff08;包括操作本身&#xff09;这个操作是以原子方式执行的操作。

 

(5.1)利用java线程安全原子类AtomicLong

利用线程安全类来确保vaue在读取--修改--写入的一致性。修改代码如下所示&#xff1a;

 

package unsafeThread;

importjava.util.concurrent.atomic.AtomicLong;

 

/**

* Created by fang on 2017/11/18.

*/

   public classSafeSequenceimplementsRunnable{

      privatefinalAtomicLongvalue&#61;newAtomicLong(0);

 

      publicintgetNext(){

          value.incrementAndGet();

 

          return(int)value.get();

       }

 

      publicvoidrun(){

       System.out.println(getNext());

    }

 

}

 

packageunsafeThread;

 

/**

* Created by fang on 2017/11/18.

*/

   public classSafeThreadClient {

   public static voidmain(String[]args) {

 

   //利用AtomicLongjava.util.corrurnent包中的,线程安全类计数器.

    SafeSequence safeSequence &#61; new SafeSequence();

      for(inti&#61;0;i<10000;i&#43;&#43;){

           Threadthread &#61;newThread(safeSequence);

           thread.start();

           }

       }

    }

(5.2)内存分析&#xff1a;

 


java.util.concurrent.atomicjava封装好的原子性的类包&#xff0c;在这里我们用线程安全的类AtomicLong来管理对象的属性变量&#xff0c;所以这个类是安全的。&#xff08;看jdk8的源码可以看到AtomicLong实现线程安全的基本原理是CAS-compare and swap&#xff0c;具体就不在这里写了&#xff0c;后续文章详细讲解jdk源码&#xff09;

 

(5.3)利用java内置锁

java提供了一种内置机制来支持原子性:同步代码块&#xff0c;代码如下所示。

 

   public classUnsafeSequenceimplementsRunnable{

      privateintvalue;

      publicsynchronized intgetNext(){

          returnvalue&#43;&#43;;

       }

      publicvoidrun(){

           System.out.println(getNext());

       }

 

    }

我们在方法上添加上synchronized&#xff0c;被synchronized锁定的块就会以原子性的方式执行。

 

(6)重入

尽管synchronized锁住的不是同一个方法块,但是做到一个线程同时调这两个方法。

例如如下代码&#xff1a;

packagesafeThread;

 

/**

 * Created by fang on 2017/11/19.

 */

public classWidget {

   public synchronized voiddoSomething(){

       System.out.println("i&#39;m father, in execute...");

   }

}

 

 

packagesafeThread;

 

/**

 * Created by fang on 2017/11/19.

 */

public classLoggingWidget extends Widget {

   public synchronized voiddoSomething(){

       System.out.println("child " &#43; toString() &#43; ": callingdoSomething");

       super.doSomething();

   }

}

 

packagesafeThread;

 

/**

 * Created by fang on 2017/11/19.

 * Synchronized可重复锁,举例.

 */

public classReentrantSynchronized {

   /**

    * main主线程.

    * &#64;param args

    */

   public static voidmain(String[] args) {

       LoggingWidget loggingWidget &#61; new LoggingWidget();

       loggingWidget.doSomething();

 

   }

}

 

内存分析


就像家里的大门有一把锁,屋子里也有锁,对于屋子里的锁来说&#xff0c;我是同一个“线程”&#xff0c;既然有进入大门的权限&#xff0c;就一定有进入屋子的权限。

(7)活跃性与性能

一些保证线程安全的方法&#xff0c;线程安全保证了&#xff0c;但是程序的执行效率可能降低了。因为&#xff0c;当访问量很大的时候&#xff0c;如果是多cpu或者cpu多核的时候&#xff0c;请求只能一个一个的被执行&#xff0c;需要等待前一个完全执行才可以执行&#xff0c;可能会影响程序效率。

当使用锁的时候&#xff0c;我们应该清楚代码块中的实现功能&#xff0c;以及在执行该代码块时是否需要很长时间。无论是执行密度操作还是执行某个可能的阻塞操作&#xff0c;如果持有锁的时间过长&#xff0c;那么就可能带来活跃性或者性能问题。

 

(8)总结

了解到了线程安全和非安全线程&#xff0c;对象和人一样都要有“安全感”&#xff0c;下一篇对象的共享。

 

 


推荐阅读
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了源码分析--ConcurrentHashMap与HashTable(JDK1.8)相关的知识,希望对你有一定的参考价值。  Concu ... [详细]
  • 学习笔记17:Opencv处理调整图片亮度和对比度
    一、理论基础在数学中我们学过线性理论,在图像亮度和对比度调节中同样适用,看下面这个公式:在图像像素中其中:参数f(x)表示源图像像素。参数g(x)表示输出图像像素。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文由编程笔记小编整理,主要介绍了使用Junit和黄瓜进行自动化测试中步骤缺失的问题。文章首先介绍了使用cucumber和Junit创建Runner类的代码,然后详细说明了黄瓜功能中的步骤和Steps类的实现。本文对于需要使用Junit和黄瓜进行自动化测试的开发者具有一定的参考价值。摘要长度:187字。 ... [详细]
  • 本文介绍了利用ARMA模型对平稳非白噪声序列进行建模的步骤及代码实现。首先对观察值序列进行样本自相关系数和样本偏自相关系数的计算,然后根据这些系数的性质选择适当的ARMA模型进行拟合,并估计模型中的位置参数。接着进行模型的有效性检验,如果不通过则重新选择模型再拟合,如果通过则进行模型优化。最后利用拟合模型预测序列的未来走势。文章还介绍了绘制时序图、平稳性检验、白噪声检验、确定ARMA阶数和预测未来走势的代码实现。 ... [详细]
author-avatar
mongcheng
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有