当前位置: 首页 > 后端技术 > Java

到底如何保证线程安全,总结的很好,.

时间:2023-04-02 00:33:49 Java

1。线程安全级别“线程安全”的问题在之前的博客中已经提到过。一般我们常说某个类是线程安全的,某个类不是线程安全的。事实上,线程安全并不是一道“非黑即白”的单选题。按照“线程安全”的安全程度从强到弱,我们可以将java语言中各种操作共享的数据分为以下五类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。1.不可变在Java语言中,不可变对象必须是线程安全的。无论是对象的方法实现还是方法的调用者,都不需要采取任何线程安全措施。比如final关键字修饰的数据不可修改,可靠性最高。2.绝对线程安全绝对线程安全完全符合BrianGoetZ给出的线程安全定义。这个定义其实很严格。一个类要实现“不管运行环境如何,调用者不需要任何额外的同步措施”。往往要付出很大的代价。3、相对线程安全相对线程安全是指一个类是通常意义上的“线程安全”。它需要确保对该对象的各个操作是线程安全的。我们在调用的时候不需要额外的保护措施,但是对于一些特定顺序的连续调用,可能需要在调用端使用额外的同步方法来保证调用的正确性。在Java语言中,大多数线程安全的类都是相对线程安全的,比如Vector的synchronizedCollection()方法保证的集合,HashTable,Collections。4、线程兼容性线程兼容性是指我们通常意义上的一个类不是线程安全的。线程兼容性意味着对象本身不是线程安全的,但是你可以通过在调用端正确使用同步方法来确保对象可以在并发环境中安全使用。JavaAPI中的大多数类都是线程兼容的。比如前面的Vector和HashTable对应的集合类ArrayList和HashMap。5、线程对立线程对立指的是在多线程环境下无论调用者是否采用了同步错误,都不能并发使用的代码。由于Java语言天生就是多线程的,因此很少出现反对多线程的代码。线程对立的一个例子是Thread类的suend()和resume()方法。如果有两个线程同时持有一个线程对象,一个尝试中断线程,另一个尝试恢复线程,如果是并发执行,不管调用是否同步,目标线程都有死锁的风险。因此,这两种方法已被弃用。2、线程安全保证线程安全的实现方法按照是否需要同步手段来分类,分为同步方案和不同步方案。1、互斥同步互斥同步是最常用的保证并发正确性的手段。同步是指当多个线程并发访问共享数据时,保证共享数据在同一时刻只被一个线程使用(同时只有一个线程在操作共享数据)。互斥是实现同步的一种手段,临界区、互斥量和信号量是实现互斥的主要方式。所以,在这四个字里,互斥是因,同步是果;互斥是方法,同步是目的。在java中,最基本的互斥同步方式就是synchronized关键字。synchronized关键字编译后,会在同步块前后形成monitorenter和monitorexit两个字节码。这两条字节码指令需要一个引用类型的参数来指定要加锁和解锁的对象。另外,ReentrantLock也是通过互斥来实现同步的。在基本用法上,ReentrantLock与synchronized非常相似,它们都具有相同的线程可重入特性。互斥同步的主要问题是线程阻塞和唤醒带来的性能问题,所以这种同步也称为阻塞同步。从处理问题的方式来看,互斥同步是一种悲观的并发策略。人们一直认为,只要不采取正确的同步措施(如加锁),那么无论共享数据是否真正共享,都一定会出问题。如果有竞争,就必须锁定。2、非阻塞同步随着硬件指令集的发展,出现了一种基于冲突检测的乐观并发策略。通俗地说,就是先做手术。如果没有其他线程竞争共享数据,则操作成功;如果共享数据发生争用和冲突,将采用其他补偿措施。(最常见的补偿错误是不断重试直到成功)。这种乐观并发策略的很多实现不需要挂起线程,所以这种同步操作被称为非阻塞同步。CAS的非阻塞实现(compareandswap):CAS指令需要有3个操作数,分别是内存地址(java中理解为变量的内存地址,记为V),旧期望值(记为A)和新值(由B表示)。CAS指令执行时,当且仅当V处的值满足旧的期望值A时,处理器用B更新V处的值,否则不执行更新,但不管V处的值是否更新,会返回V的旧值,上面的处理是一个原子操作。CAS的缺点:ABA问题:因为CAS在操作值的时候需要检查值是否有变化,没有变化就更新,但是一个值本来是A,变成B,再变成A,然后用CAS检查时,它的值不会改变,但会改变。ABA问题的解决方案是使用版本号。在变量前面追加版本号,变量每更新一次版本号就加一,那么A-B-A就变成1A-2B-3C。JDK的atomic包提供了一个类AtomicStampedReference来解决ABA问题。该类的compareAndSet方法的作用是首先检查当前引用是否等于期望引用,当前标志是否等于期望标志,如果都相等则设置引用的值和以原子方式标记给定的更新值。3、不需要同步的解决方案为保证线程安全,不需要进行同步,两者之间没有因果关系。同步只是保证共享数据争用正确性的一种手段。如果一个方法不涉及共享数据,自然不需要任何同步操作来保证正确性,所以有些代码天生就是线程安全的。1)可重入代码可重入代码(ReentrantCode),也称为纯代码(PureCode),可以在代码执行的任何时刻打断它,然后再执行另一段代码,控制权返回后,原来的程序不会'不要抛出任何错误。所有可重入代码都是线程安全的,但并非所有线程安全代码都是可重入的。可重入代码的特点是不依赖堆上存储的数据和公共系统资源,使用的状态量全部通过参数传入,不调用非可重入方法。(打个比方:synchronized有锁重入的功能,即在使用synchronized时,当一个线程获得对象锁后,再次请求对象锁时,可以再次获得对象锁)2)线程本地存储如果一块代码需要的数据必须要和其他代码共享,那么看看共享数据的代码能不能保证在同一个线程中执行?如果可以保证,我们可以将共享数据的可见性限制在同一个线程中。这样就不需要同步,保证线程之间不存在数据争用问题。符合这一特征的应用并不少见。大多数使用消费队列的架构模式(如“生产者-消费者”模式)都会尽可能在一个线程中消费产品消费过程。其中最重要的一个应用例子就是经典Web交互模型中“一个请求对应一个服务器线程(Thread-per-Request)”的处理方式。这种处理方式的广泛应用使得很多Web服务器应用程序都可以使用线程。本地存储解决线程安全问题。