unsafesequence提供线程安全注解
类 Annotation(注解)
就像名字一样,这些注解是针对类的。主有要以下三个:
- **@ThreadSafe **
是表示这个类是线程安全的。具体是否真安全,那要看实现者怎么实现的了,反正打上这个标签只是表示一下。不线程安全的类打上这个注解也没事儿。
- @Immutable
表示类是不可变的,包含了 @ThreadSafe 的意思。
- @NotThreadSafe
表示这个类不是线程安全的。如果是线程安全的非要打上这个注解,那也不会报错。
- @GuardedBy( lock )
里面的Lock是告诉维护者:这个状态变量,这个方法被哪个锁保护着。这样可以强烈的提示类的维护者注意这里。
1、@GuardedBy( "this" ) 受对象内部锁保护
2、@GuardedBy( "fieldName" ) 受 与fieldName引用相关联的锁 保护。
3、@GuardedBy( "ClassName.fieldName" ) 受 一个类的静态field的锁 保存。
4、@GuardedBy( "methodName()" ) 锁对象是 methodName() 方法的返值,受这个锁保护。
5、@GuardedBy( "ClassName.class" ) 受 ClassName类的直接锁对象保护。而不是这个类的某个实例的锁对象。
==这四个注解,对用户和维护者是有益的,用户可以立即看出来这个类是否是线程安全的,维护者则是可以根据这个注解,重点检查线程安全方面。另外,代码分析工具可能会利用这个注解。==
例子
import javax.annotation.concurrent.*;;
@ThreadSafe
public class Sequence {
@GuardedBy("this") private int value;
public synchronized int getNext() {
return value++;
}
}
线程的风险
活跃度风险
如果线程A等待线程B独立占有的资源,B永远不会释放,A将永远等待下去,各种形式活跃度失败,包括==死锁==、饥饿、活锁
性能危险
虽然线程能获取纯粹的性能收益,但线程会给程序带来一定程度的开销,上下文切换-当调度程序临时挂起当前运行程序时,另一个程序开始运行,这在多线程的应用程序中很频繁,并且带来巨大的系统开销**
线程安全性
当多个线程访问一个类是,如果不用考虑这些线程在运行时期的调度和交替执行,并且不需要额外的同步和调用方代码的协助,那么这个类是线程安全的
无状态和有状态对象的区别
- 有状态就是有数据存储,有实例变量的对象,可以保存数据,是线程不安全的
public class A {
public A() {}
public String hello() {
return "Hello 谁?";
}
}
public class Client {
public Client() {
A a = new A();
System.out.println(a.hello());
A b = new A();
System.out.println(b.hello());
}
}
在Client中生成了两个A的实例,不管是对象a还是b,它们是没有状态的。对于Client来说a和b是没有差别的(但a != b)。所以同一个无状态会话Bean的实例都是相同的,可以被不同的客户端重复使用。
- 无状态就是不能保存数据,一次性操作,没有实例变量,不能保存数据,是线程安全,
public class B {
private String name;
public B(String arg) {
this.name = arg;
}
public String hello() {
return "Hello" + this.name;
}
}
public class Client {
public Client() {
B a = new B("中国");
System.out.println(a.hello());
B b = new B("世界");
System.out.println(b.hello());
}
}
<font size=4>==无状态对象永远是线程安全的==</font>
把cotroller改为多例就一定线程安全吗,不一定,因为statci变量是多线程共享,也不一定能达成线程安全,一般来说spring容器的bean默认是单例,controller单例为的就是性能,不然每次请求创建一个实例,服务器不得消耗巨大。controller防止多线程问题就是避免在controller中写入变量和有状态对象,方法和局部变量是存储在栈内存中,而栈内存是线程私有的,因此只要不往controller中添加成员变量,都是线程安全
原子性
一套不可被分割的操作,一起成功要么一起失败
==例如:i++→实际包含三个操作==
- 获取当前i值
- i加1
- 写入新值
因此线程不安全
竞争条件
两个或多个对同一个资源进行操作的时候会产生竞争条件现象,主要是这个资源是非原子性,第一个线程没有结束操作,第二个线程就进来了,导致最后与预期结果不一致
保证同步
java中用lock或者synchronized来保证同步,但是这是悲观锁,会引起阻塞状态,CAS算法用来解决这个问题
CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
==CAS不会造成阻塞状态==
java中用atomic类来替代多线程中成员变量的操作,atomic中用到的就是CAS算法,可以保证原子性
锁
synchronized(独占锁)只有持有锁对象的线程能进入,其他的只能等待或阻塞,从而实现原子性
==但是响应速度太慢,不要用在方法上,最好用代码块实现==
可重入锁(ReentrantLock )-可实现公平锁
- 可重入锁的概念就是一个线程可以连续多次获得锁资源,每次获取的时候内部有一个计数器stat 会加1,释放一个减去一,直到计数器为零,这个锁才可以背其他的线程获取
- 通过加true实现公平锁
ReentrantLock fairLock = new ReentrantLock(true)
不可冲入锁造成的代码锁死:
public class widget{
public synchronized void doSomething(){...}
}
public class LoggingWidget extends Widget{
public synchronized void doSomething(){
....
super.doSomething();
}
}
总结:
冲入锁针对的是同一个线程的多次获取锁
共享对象
可见性
A线程修改共享数据data,B线程读取到了A线程修改之前的数据,访问了过期的数据
volatile变量
volatile变量,他确保对一个变量的更新以可预知的方式告知其他线程,编译器会监视这个变量,对它的操作也不会被其他的内存操作一起被重排序
==但是不支持原子性,所以适用于只有一个线程对其进行写操作,其他线程读==
重排序
什么是重排序?
执行任务的时候,为了提高编译器和处理器的执行性能,编译器和处理器(包括内存系统,内存在行为没有重排但是存储的时候是有变化的)会对指令重排序。编译器优化的重排序是在编译时期完成的,指令重排序和内存重排序是处理器重排序
- 编译器优化的重排序,在不改变单线程语义的情况下重新安排语句的执行顺序
- 指指令级并行重排序,处理器的指令级并行技术将多条指令重叠执行,如果不存在数据的依赖性将会改变语句对应机器指令的执行顺序
- 内存系统的重排序,因为使用了读写缓存区,使得看起来并不是顺序执行的
==会导致多线程可见性问题==
ThreadLocal维护线程
它允许你将每个线程与持有数值的对象关联在一起。ThreadLocal提供了get与set访问器,为每个适用它的线程维护一份单独的拷贝,通过get总是返回由当前执行线程通过set设置的最新值,从而实现了线程的封闭性
不可变性
不可变对象永远是线程安全的,可以安全的用于任意线程,甚至发布它们时亦不需要同步
只有满足如下状态,一个对象才是不可变的
- 它的状态在创建后不能被修改
- 所有的域都是final类型
-
它被正确创建(创建期间没有发生this引用的溢出)
组合对象
委托线程线程安全
将对象中方法的线程安全委托出去,例如用currentHashmap来委托本来用synchonized修饰的添加方法,将线程安全委托到底层的状态变量
当委托无法胜任线程安全
调用了线程安全CurrentHashMap也不一定安全,因为方法运行中有判断,检查在运行,这套操作并非原子操作
如果类中含有复合操作,单独委托线程安全并不会达到安全的状态,比如在currentHashMap操作上加判断等操作,需要在这套操作上面加锁
同步容器
==只是容器中单个方法时线程安全,但多个方法结合在一起则需要调用者额外添加锁实现线程安全==
早期的jdk同步容器包括verctor和hashtable,都是由synchronized工厂方法创建,通过synchronized对每个公共方法进行同步
ConcurrentHashMap
- 分段锁:任意数量并发访问map,有限数量的写线程还可以并发修改map,优点:高吞吐量
CopyOnWriteArrayList
CopyOnWriteArrayList是同步List的并发替代品
Synchronizer(阻塞队列)
协调生产者线程和消费者线程之间的控制流
闭锁
闭锁是一种Synchronizer,可以延迟线程的进度知道线程到达终止状态。闭锁就像一道大门:知道闭锁到达终点状态之前,门一直是关闭的,没有线程可以通过,在终点状态到来之时,门开了所有线程都能通过
第一部分的总结:
- 所有并发问题都归结为如何协调访问并发状态。可变状态越少,保证线程安全越容易
- 尽量将域变量生命为final类型,除非他们的需求是可变的
- 不可变对象天生是线程安全,可以在没有锁或者防御性复制的情况下自由共享
- 用锁来守护每个可变变量
- 对同一不变约束中的所有变量都使用相同的锁
- 在运行复合操作期间需要持有锁
- 在设计过程中就要考虑线程安全,或在文档中说明它不是线程安全的
- 文档化你的同步策略