并发编程2-线程安全性

  在构建稳健的并发程序时,除了正确的使用线程和锁外。如何对状态访问进行管理是编码的核心。特别是对共享的(shared)和可变的(Mutable)状态的访问。“共享”意味着变量可以由多个线程同时访问,而“可变”则意味着变量的值在其生命周期内可以发生变化。我们讨论线程安全性更侧重于如何防止在数据上发生不受控的并发访问。

  一个对象是否需要是线程安全的,取决于它是否被多个线程访问。这是程序中访问对象的方式,而不是对象要实现的功能。我们可以通过同步机制来协同对对象可变状态的访问来使其线程安全。当无法实现协同,那么可能导致数据破坏甚至程序出现错误。有三种方式可以修复这个问题:

    1、不在线程之间共享该状态变量。

    2、将状态变量修改为不可变的变量。

    3、在访问状态变量时使用同步。

  在我们设计类与程序时应该考虑并发访问的情况,以免后期采用上述方法对设计进行重大修改。这一切都是为了保障线程安全性。

  那什么是线程安全性呢?

    线程安全性核心概念是正确性,正确性的含义是某个类的行为与其规范完全一致。我们根据实际的场景可以总结出:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。在线程安全类中封装了必要的同步机制,因此客户端无须进一步采取同步措施。

  例子:基于Servlet的因数分解服务,并通过扩展其功能,同时确保它的线程安全性

public class StatelessFactorizer implements Servlet{
    public void service(ServletRequest request, ServletResponse response){
        BigInteger i = extractFormRequest(request);
        BigInteger[] factors = factor(i);
        encodeIntoResponse(response,factors);
    }
}

  该类是无状态的,不包含任何域,也不包含任何对其他类中域的引用,计算过程中的临时状态仅存于线程栈上的局部变量中,并且只能由正在执行的线程访问。访问该类的线程不会影响另一个访问同一个类的线程的计算结果。两个线程之间没有共享状态。线程访问无状态对象的行为并不会影响其他线程中操作的正确性,因此无状态对象一定是线程安全的。大多数的Servlet都是无状态的。

  由非原子性操作来了解何为竞态条件:

    何为原子性:一个或多个操作,要么全部执行完成并且执行过程不被打断,要么不执行。我们用简单的例子在说明并发编程中的原子性:

    假定有两个操作A和B,如果从执行A的线程来看,当另一个线程执行B时,要么将B全部执行完,要么完全不执行B,那么A和B对彼此来说是原子的。此处我们只是复习一下概念不多做讲解。  

    在编码时我们常常遇见一个典型的非原子性操作

private long count = 0;
public long getCount(){return count};
public void service(ServletRequest request, ServletResponse response){
    ++count;
}

  看上去这个只是一个操作,实际上包含了三个独立操作。读取count的值,将值+1,然后将结果写入count。这是一个典型的“读取-修改-写入”的操作。当两个线程在没有同步的情况下去对一个count进行递增操作。我们想要的正常结果应该是2,但某些情况下,每个线程读到的值都是0,递增后都将count设为1。导致这种特殊情况的原因是不恰当的执行时序,我们称之为”竞态条件“。当某个计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。其本质是基于一种可能失效的观察结果来做出判断或者执行某个计算,最常见的竞态条件类型就是”先检查后执行“。

  后续的慢慢更新,目前是边读并发编程实战,边更新博客。本人文字功底不好,大部分都是书中所讲,我只是改动部分说法来尽量衔接书中讲解的知识。对并发编程有兴趣的朋友,个人强推并发编程实战。哈哈

  

    

原文地址:https://www.cnblogs.com/zhangbLearn/p/9630952.html

时间: 2024-10-30 09:44:48

并发编程2-线程安全性的相关文章

并发编程初探-线程安全性

在Java并发编程中,对于线程安全是非常重要的,也是必须要考虑的一个问题.可以这么说,只要涉及到网络的,都必须考虑线程安全问题.好了,开始噼里啪啦地开始敲代码之前,我觉得有必要了解一些文绉绉的理论知识,因为这些理论知识是我们敲出来的代码是否是线程安全的一个依据. 当多个线程访问某个状态变量并且其中有一个线程执行写入操作的时候,必须考虑采用同步机制来协同这些线程对变量的访问,Java中的主要同步机制是关键字synchronized,它提供了一种独占的加锁方式,但"同步"这个术语还包括类型的变量,显

并发编程之线程安全性

一.什么是线程安全性 并发编程中要编写线程安全的代码,则必须对可变的共享状态的访问操作进行管理. 对象的状态就是存储在实例或者静态变量中的数据,同时其状态也包含其关联对象的字段,比如字典集合既包含自己的状态, 也包含KeyValuePair. 共享即可以多个线程同时访问变量,可变即变量在其声明周期内可以发生变化. 代码线程安全性关注的是防止对数据进行不可控的并发访问. 是否以多线程的方式访问对象,决定了此对象是否需要线程安全性.线程安全性强调的是对对象的访问方式,而不是对象 要实现的功能.要实现

JAVA并发编程4_线程同步之volatile关键字

上一篇博客JAVA并发编程3_线程同步之synchronized关键字中讲解了JAVA中保证线程同步的关键字synchronized,其实JAVA里面还有个较弱的同步机制volatile.volatile关键字是JAVA中的轻量级的同步机制,用来将变量的更新操作同步到其他线程.从内存可见性的角度来说,写入volatile变量相当于退出同步代码块,读取volatile变量相当于进入同步代码块. 旧的内存模型:保证读写volatile都直接发生在main memory中. 在新的内存模型下(1.5)

Java并发编程:线程的同步

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

Java并发编程:线程的创建

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

【转】Java并发编程:线程池的使用

Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? 在Java中可以通过线程池来达到这样的效果.今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPool

68:Scala并发编程原生线程Actor、Cass Class下的消息传递和偏函数实战解析及其在Spark中的应用源码解析

今天给大家带来的是王家林老师的scala编程讲座的第68讲:Scala并发编程原生线程Actor.Cass Class下的消息传递和偏函数实战解析 昨天讲了Actor的匿名Actor及消息传递,那么我们今天来看一下原生线程Actor及CassClass下的消息传递,让我们从代码出发: case class Person(name:String,age:Int)//定义cass Class class HelloActor extends Actor{//预定义一个Actor  def act()

19、Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权.因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去.因此,一般情况下,当队列满时,会让生产者交出对

Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)

Java并发编程系列[未完]: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程:线程间的协作(wait/notify/sleep/yield/join) 一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线程创建完成时为新

Java并发编程:线程池的使用(转)

Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? 在Java中可以通过线程池来达到这样的效果.今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPool