本文从以下介绍线程安全与锁优化
一、线程安全
二、锁优化
一、线程安全
1、线程安全的定义
Brian Goetz对线程安全的定义:
当多个线程访问一个对象的时候,如果不用考虑这些线程在运行时环境下的调度与交替执行,也不需要进行额外的同步,或者调用者也不需要做任何的其他协调操作,调用该对象的行为都可以获取到正确的结果,该对象就是线程安全的。
线程安全代码具备的特征:
代码本身封装了正确性的保障手段(如互斥同步),调用者无需关注多线程的问题,也无需实现任何措施来保证多线程的正确调用。
2、Java语言中的线程安全
线程的安全程度,由强到弱,Java语言各种操作共享数据分五类:不可变,绝对线程安全、相对线程安全、线程兼容、线程对立
(1)不可变
不可变的对象一定是线程安全的,无论是对象的方法实现还是调用者调用该方法,都不需要任何的线程安全措施保障。
java语言,共享数据为一个基本数据类型,只要将其用final修饰可以保证其不可变;
如果共享数据为一个对象,就需要对象的行为对其状态(属性)没有任何影响,则可保证不可变,如:String,substring()方法,不会改变原来的字符串,方法执行后会返回一个新的值;
保证对象行为不影响自己的状态方式很多:比如:将对象中带有状态的变量声明为final,如:Integer中的构造函数,用final声明了一个成员变量
private final int value;
public Integer(int value){
this.value = value;
}
(2)绝对线程安全
绝对的线程安全,是符合Brain Goetz给出的线程安全的定义的,Java API标注的自己是线程安全的类大多数不是绝对的线程安全类,如Vector
package net.oschina.tkj.jvmstu.thread; import java.util.Vector; public class TestVector { private static final Vector v = new Vector(); public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < v.size(); i++) { v.remove(i); } } }); Thread t1 = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < v.size(); i++) { System.out.println(v.get(i)); } } }); t.start(); t1.start(); while (Thread.activeCount() > 20) ; while (true) { for (int i = 0; i < 10; i++) { v.add(i); } } } }
出现异常问题:
解决办法在run()方法内加synchronized。
因此,java中声明线程安全的api并非是绝对的线程安全的。
(3)相对线程安全
对某个对象单独操作是线程安全的,如果多线程调用时,需要使用额外的线程安全手段来保证调用的正确性。如:上述的Vector,Hashtable等。
(4)线程兼容
线程兼容对象本身不是线程安全,可以通过使用同步的操作来保证线程安全。
(5)线程对立
不管调用者是否采用同步措施,都无法在多线程的环境中并发使用代码。
3、线程安全的实现
线程安全问题产生的原因:存在多个线程,并且多个线程有共享数据。
线程安全问题的解决办法:
(1)互斥同步(Mutual Exclusion &Synchronized)
Java里互斥同步的手段就是同步关注字Synchronized。
1>同步关键字Synchronized
①同步关键字在编译后,会在同步块前后生成monitorenter、monitorexit两个字节码指令,这两个字节码指令都需要一个reference类型的参数来指定锁定和解锁的对象;
②JVM规范中指出,在执行monitorenter指令时,首先要尝试获取对象的锁。如果该对象没有被锁定,或者当前的线程已经拥有了那个对象的锁,把锁计数器加1。相应的在执行monitorexit指令时会把锁计数器减1,当计数器的值为0,则锁被释放。如果获取对象的锁失败,那当前线程处于阻塞等待状态,直到对象的锁被占用的线程释放为止;
③JVM规范描述中,关于monitorenter,monitorexist指令注意点:
《1》synchronized对一个线程来说可重入,不会出现自己把自己锁死的情况;
《2》同步块在已进入的线程执行之前,会阻塞后面的其他线程的进入。
④java的线程要映射到操作系统的原生线程之上,因此,线程的阻塞和唤醒,都需要操作系统参与,这就需要从用户态转入到核心态,因此状态的转换需要花费处理器的大量时间,所以,Synchronized为java语言一个重量级的操作;
(2)java.util.concurrent.ReentrantLock实现同步
ReentrantLocak与synchronizd的操作类似,不过ReentrantLock为显式声明锁定与解锁操作(lock与unlock方法配合try,finally语句块一起使用)
ReentrantLock比synchronizd增加的功能如下:
《1》等待可中断:持有锁的线程长时间不释放锁,等待的线程可以选择放弃等待处理其他事情,对执行时间过长的同步块有用;
《2》公平锁:多线程等待同一个锁时,按照申请锁的先后顺序依次获得锁;而非公平锁,不保证这一点,任何一个等待锁的线程都有机会获得锁;synchronized为非公平锁,ReentrantLock默认为非公平锁,可通过构造方法的布尔值转为公平锁;
《3》锁可以绑定多个条件:指一个ReentrantLock对象可以绑定多个Condition对象。
(3)非阻塞同步
互斥同步最主要的问题是,进行线程的阻塞和唤醒时性能的问题,因此这种同步也称为阻塞同步。
悲观并发策略:该种策略认为,如果不去做正确的同步操作,那就会出现问题。无论线程是否真的竞争共享数据都需要加锁的操作。
乐观并发策略:基于冲突检测的乐观并发策略,简单讲就是先进行操作,如果没有线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,就进行其他补偿措施,这种乐观的并发策略都不需要将线程挂起,因此也称为非阻塞同步(Non-blocking synchronization)
二、锁优化
高并发JDK1.6重要主题,实现锁优化的技术:适应性自旋,锁消除,锁粗化,轻量级锁,偏向锁,这些技术旨为解决线程间高效共享数据,解决竞争问题,提高程序执行效率。
1、自旋锁与自适应锁
同步互斥对性能最大的影响是阻塞实现,挂起线程以及恢复线程都需要转入内核态来完成,这给操作系统的并发性能带来很大压力。
自旋锁:物理机器有一个以上处理器,可以同时处理多个线程并发,为了实现请求锁的线程等待,而不放弃处理器的执行时间。只需等待锁的线程执行一个忙循环(自旋),该技术为自旋锁。
自旋锁优点:避免了线程状态转化的开销
自旋锁缺点:需要占用处理器的时间,如果持有锁的线程持有锁时间过长,则会造成等待锁的线程持有处理器时间过长,资源浪费。所以给自旋锁设置自旋的次数默认为10。如果超过该次数未获取锁,则用传统的手段将该线程挂起。
自适应自旋:自旋的时间不固定,由前一次在同一个锁上的自旋时间及suo的拥有者的状态决定。
2、锁消除
jvm的即时编译器在运行时,对一些代码上要求同步,但是当检测到不可能存在共享数据的争用情况则对锁进行消除。
3、锁粗化
正常情况下,同步块使用时都是在尽量小的范围内使用,这样等待的锁的线程可以尽快拿到锁。
特殊情况下:一个操作反复的出现加锁解锁的操作,甚至锁的操作在循环中,即使不出现竞争数据,也会造成性能的不必要浪费。因此,此时会报加锁的范围扩大到(粗化)整个操作的外部,这样保证只加一次锁的操作。如:StringBuffer的appen()就是锁粗化操作。多个append(),加锁放到第一个append()之前,解锁放到最后一个append()之后。
4、轻量级锁
相对于使用系统的互斥量的传统锁而言。
轻量级锁提升同步性能依据:对于大部分锁而言,整个同步周期内都是不存在竞争的。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,如果存在竞争,除了互斥量的开销外,还额外的进行CAS操作,因此有竞争的情况轻量级锁更慢。
5、偏向锁
消除数据无竞争的情况下的同步,提高程序的性能。
轻量级锁在无竞争数据的情况下通过CAS消除同步使用的互斥量,那么偏向锁就是在无竞争的情况下把整个同步都消除掉,CAS的操作也无需执行。
相关推荐
线程安全与锁优化:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者再调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果。
绝对线程安全Vector的get()、remove()和size(),如果另一个线程恰好在错误的时间里删除了一个元素,导致序号i已经不再可用的话,再用i访问数组
在计算机编程领域,尤其是涉及到实时系统和并发编程时,线程锁和线程安全变量是至关重要的概念。LabWindows/CVI是一种流行的交互式C开发环境,特别适合于开发科学和工程应用。本实例将深入探讨如何在LabWindows/CVI...
4. **线程安全与同步机制**: - 确保线程安全,即在多线程环境下,程序的行为不受时序或交替操作的影响。 - `synchronized`关键字用于实现锁,保证同一时刻只有一个线程能执行特定代码块。 - 使用`volatile`修饰...
为了确保线程安全,我们需要在记录日志时进行同步控制,例如使用互斥锁(mutex)。 3. **日志输出函数**:这是核心功能,它接收日志消息并将其写入日志文件。在多线程环境中,我们需要确保每次只有一个线程可以执行...
### JAVA线程安全及性能优化的关键知识点 ...通过以上讨论,我们可以看出Java线程安全与性能优化是一个复杂而重要的主题,需要开发者深入理解Java内存模型,并灵活运用各种工具和技术来确保程序既正确又高效地运行。
"C# 高效线程安全,解决多线程写txt日志类.zip" 提供了一个专门用于多线程环境下写入txt日志文件的解决方案,确保了在并发写入时的数据一致性与程序稳定性。 首先,我们要理解什么是线程安全。线程安全是指当多个...
3. **死锁与竞态条件**:在进行线程安全测试时,还需要考虑潜在的死锁和竞态条件。死锁是两个或更多线程相互等待对方释放资源而无法继续执行的情况。竞态条件则发生在多个线程同时修改共享变量时,可能导致结果的不...
虽然提供了基本的线程安全性,但它们不是高度优化的并发解决方案,因为所有操作都需要全局锁定,可能导致性能瓶颈。 2. 并发集合(Concurrent Collections): Java的`java.util.concurrent`包提供了更为高效且...
`golang-set`库提供了一种实现,包括线程安全和非线程安全的高性能集,非常适合在Go的并发环境中使用。 首先,我们要理解什么是线程安全和非线程安全。线程安全指的是在多线程环境下,一个函数或方法在同一时刻可以...
文中还探讨性能优化策略,包括减少锁竞争、异步日志记录等技术手段,为 C++ 开发者在构建高效、可靠且线程安全的日志系统方面提供全面而深入的参考,助力其在实际项目中更好地处理多线程环境下的日志记录需求,确保...
在C#编程中,线程安全是多线程应用程序中至关重要的一个方面,尤其是在处理共享资源如文本日志文件时。本主题将深入探讨如何在C#中创建一个高效的线程安全日志类,用于在多线程环境中安全地写入txt日志。 首先,...
条件变量常与互斥锁结合使用,以确保线程在等待或被唤醒时的资源安全。 3. **读写锁通信机制**: 读写锁是一种更为精细的同步机制,特别适用于读多写少的情况。读写锁分为读锁和写锁,读锁可被多个线程同时持有,...
在C++编程中,线程安全容器是一种在多线程环境下能够确保数据一致性与正确性的数据结构。在并发编程中,线程安全是至关重要的,因为它可以防止竞态条件、死锁和其他同步问题,从而避免程序出现未定义的行为。本项目...
Java提供了一些线程通信的工具,如wait(), notify()和notifyAll(),它们都与对象的监视器(monitor)相关联。但在实际使用中,由于它们容易导致死锁和不易管理,现在更推荐使用java.util.concurrent包中的高级并发...
总结来说,Java中的`synchronized`关键字是实现线程同步的关键,它通过互斥锁确保对共享资源的访问是线程安全的。在多线程编程中,合理使用`synchronized`可以有效避免竞态条件,保证程序的正确性和稳定性。对于...
为了确保线程安全,我们需要使用锁或其他同步机制来控制对`DataSet`的并发访问。 2. **数据一致性**:`DataSet`的更新操作,如`AcceptChanges()`或`RejectChanges()`,应当在没有其他线程访问时进行,以保持数据的...
这些线程安全集合的实现方式主要是通过使用锁和无锁算法,例如 CAS(Compare-and-Swap)操作,来保证在多线程环境下的数据一致性。它们通常比使用传统的同步机制(如 `lock` 关键字)更高效,因为它们减少了锁的粒度...
Java中的线程安全与线程同步是多线程编程中至关重要的概念,它们涉及到如何在并发环境下保证程序的正确性和一致性。线程安全是指在多线程环境下,一个类或者方法能够正确处理并发访问,不会因为线程间的交互而导致...