volatile与synchronized比较
volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,volatile只能修饰变量,而synchronized可以修饰方法,代码块,但是它就具备可见性,不具备原子性
CAS
是由硬件实现的,CAS可以将read-modify-write这类操作转化为原子操作
i++自增操作包括三个操作
从主内存读取i变量值
对i的值加1
再把加1之后的值保存到主内存
CAS原理在把数据更新到主内存时,再读取主内存变量的值,如果现在变量的值与期望的值(操作起始时读取的值)一样就更新
public class Test{
public static void main(String[] args) {
TwoTest twoTest=new TwoTest();
for (int i=0;i<100;i++){
//System.out.println(twoTest.incrementAndGet()+"----------"+Thread.currentThread().getName());
new Thread(){
@Override
public void run() {
System.out.println(twoTest.incrementAndGet()+"----------"+Thread.currentThread().getName());
}
}.start();
}
}
}
class TwoTest {
volatile private long value;
public long getValue(){
return value;
}
private boolean compareAndSwap(long expectedValue,long newValue){
synchronized (this){
if(value==expectedValue){
value=newValue;
return true;
}else {
return false;
}
}
}
public long incrementAndGet(){
long oldvalue;
long newvalue;
do {
oldvalue=value;
newvalue=oldvalue+1;
}while (!compareAndSwap(oldvalue,newvalue));
return newvalue;
}
}
CAS实现原子操作背后有一个假设,共享变量的当前值与当前线程提供的期望值相同,就认为这个变量没有被其他线程修改过
实际上这种假设不一定总是成立如共享变量count=0
A线程修改其为10
B线程修改其为20
C线程修改为0
这就是CAS中的ABA问题即共享变量经历ABA的更新
这跟算法有关
如果想要规避ABA问题,可以为共享变量引入一个修订号(时间戳)
原始变量类
原子变量类基于CAS实现的,对共享变量进行读写更新操作时,通过原子变量类可以保障操作的原子性与可见性,对变量的读写更新操作是指当前操作不是一个简单赋值,而是变量的新值,依赖变量的旧值,如自增操作i++,由于volatile只能保证可见性,不能保障原子性,原子变量类内部就是一个volatile变量,并且保障了该变量的读写操作的原子性,有时把原子类看作增强的volatile变量,有12个如:
分组 | 原子变量类 |
---|---|
基础数据型 | AtomicInteger,AtomicLong,AtomicBoolean |
数组型 | AtomicIntegerArray,atomiclongarray,atomicreferencearray |
字段更新器 | atomicintegerfieldupdater,atomiclongfieldupdater,atomicreferencefieldupdater |
引用型 | atomicreference,atomicstampedreference,atomicmarkablereference |
atomicintegerfieldupdater
atomicintegerfieldupdater可以对原子整数字段进行更新,要求:
字符必须使用volatile修饰,使线程之间可见
只能是实例变量,不能是静态变量,也不用使用final修饰
线程间的通讯
等待/通知机制
什么是通知机制
在单线程编程中,要执行的操作要满足一定的条件才能执行,可以把这个操作放在if语句块中
在多线程编程中,可能A线程的条件没有满足只是暂停,稍后其他线程B可能会更新条件使得A线程的条件得到满足,可以将A线程暂停,直到它得到条件后才被唤醒
等待/通知机制的实现
object类中的wait()方法使执行当前代码的线程等待,执行执行直到通知或被中断为止
注意:
wait()方法只能在同步代码中由锁对象调用
调用wait方法,当前线程会释放锁
Object类的notify()可以唤醒线程,这个也必须在同步代码中由锁对象调用,没有使用锁对象调用wait()/nofify会抛出异常,如果有多个等待的线程,方法只会随机唤醒一个,在调用方法后并不会立即释放锁对象,需要当前同步代码块执行完后才会释放锁对象,一般将notify方法放在同步代码块的最后
notify方法被调用不会立即执行
interrupt()方法中断wait()
当线程处于wait等待状态时,调用线程对象的interrupt方法会中断线程的等待状态
notify()和notifyall()
前者一次只能唤醒一个线程,还是随机唤醒的,要唤醒所有线程使用后者
public class TenTest {
static String a;
public static void main(String[] args) {
a="123";
new Thread(new Runnable() {
@Override
public void run() {
synchronized (a){
try {
System.out.println("关闭");
a.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开启");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (a){
try {
System.out.println("关闭");
a.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开启");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (a){
try {
System.out.println("关闭");
a.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开启");
}
}
}).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i=0;i<50;i++){
System.out.println(i);
if(i==25){
synchronized (a){
a.notifyAll();
}
}
}
}
}