輕量級同步機制:volatile關鍵字
volatile的作用
關鍵作用是使變量在多個線程之間可見
public class VolatileText {
public static void main(String[] args) throws InterruptedException {
Student student=new Student();
new Thread(new Runnable() {
@Override
public void run() {
student.GetMethon();
}
}).start();
Thread.sleep(1000);//睡眠之后修改布爾值
student.GetName(false);//改變布爾值以結束程序運行
}
static class Student
{
public boolean flag=true;
public Student GetName(boolean flag)
{
this.flag=flag;
return this;
}
public void GetMethon()
{
System.out.println("開始");
while (flag){//死循環
}
System.out.println("結束");
}
}
}
程序並沒有因為我修改之后結束運行,因為線程對共享變量具有不可見性,main線程修改布爾值之后,子線程看不到值的修改。因此要想實現線程的可見性這里可以加上volatile關鍵字修飾公共變量
volatile關鍵字的作用:使線程在強制公共內存中讀取變量值,保證可見性
volatile非原子特性
public class Text10 extends Thread {
private volatile static int count;
@Override
public void run() {
Addcount();
}
public static void Addcount()
{
for (int i = 0; i < 1000; i++) {
count++;
}
System.out.println(Thread.currentThread().getName()+"-->"+count);
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Text10 text10=new Text10();
text10.start();
}
}
}
按道理輸出1000的整數倍數才對,但是變量在自增的過程中沒有更新到又被讀取再修改,因此volatile不具備原子性,正確辦法將方法加上synchronized關鍵字
volatile與synchronized比較
-
volatile關鍵字是線程同步的輕量級實現,所以volatile性能肯定比synchronized要好,volatile只能修飾變量,而synchronized可以修飾方法代碼塊,在開發中使用synchronized比例還是挺大的。
-
多線程訪問volatile變量不會發生阻塞,而synchronized可能會阻塞。
-
volatile能保證數據的可見性,但是不能保證原子性,而synchronized可以保證原子性,也可以保證可見性,因為
-
synchronized會將線程的工作內存和主內存進行同步
-
volatile關鍵字保證多個線程之間的可見性,synchronized關鍵字解決線程訪問公共資源的同步性。
區別 synchronized volatile 使用上 只能用於修飾方法、代碼塊 只能修飾實例變量或者類關鍵字 原子性保證 能保證,鎖可以保護數據不被打斷 無法保證 可見性保證 能保證,排它方式使同步代碼串行 能保證,可以讀取公共變量 有序性保證 能保證,在同步串行的時候 能保證,禁止JVM以及處理器進行排序 阻塞情況 會發生阻塞 不會發生阻塞
常用原子類進行自增自減操作
i++不是原子操作,除了使用synchronized進行同步,也可以使用AtomicInteger/AtomicLong進行實現
import java.util.concurrent.atomic.AtomicInteger;
public class Text10 extends Thread {
private static AtomicInteger count=new AtomicInteger();
@Override
public void run() {
AddCount();
}
public static void AddCount()
{
for (int i = 0; i < 1000; i++) {
count.getAndIncrement();//相當於i++
//count.incrementAndGet();//相當於++i
}
System.out.println(Thread.currentThread().getName()+"-->"+count.get());
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Text10 text10=new Text10();
text10.start();
}
}
}
CAS
CAS(Compare And Swap)是由硬件實現的,
CAS可以將read(讀)-modify(修改)-write(寫)轉化為原子操作
i++自增過程:
從主內存調取i變量值
對i值加1
再把加1過后的值保存到主內存
CAS原理:在把數據更新到主內存時,再次讀取主內存變量的值,如果現在變量的值與期望的值一樣就更新。
使用CAS原理實現線程安全計數器
public class CASText {
public static void main(String[] args) {
CASControl casControl=new CASControl();
for (int i = 0; i <10000 ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"==>"+casControl.incrementAndGet());
}
}).start();
}
}
}
class CASControl
{
volatile static long value;//使用volatile修飾線程可見性
public long getValue()
{
return value;
}
private boolean Expect(long oldValue,long newValue)
{
synchronized (this)
{
if(value==oldValue)
{
value=newValue;
return true;
}
else
return false;
}
}
public long incrementAndGet()
{
long oldvalue;
long newValue;
do {
oldvalue=value;
newValue=oldvalue+1;
}while (!Expect(oldvalue, newValue));
return newValue;
}
}
CAS中的ABA問題
CAS實現原子操作背后有一個假設:共享變量當前值與當前線程提供的期望值相同,就認為變量沒有被其他線程過。
實際上這種假設不一定成立,假如count=0
A線程對count值修改為10
B線程對count值修改為30
C線程對count值修改為0
當前線程看到count=0,不能認為count沒有被其他線程更新,這種結果是否能被接受?
這就是CAS中的ABS問題即共享變量經過了A=》B=》A的更改
是否能夠接受ABA問題跟算法實現有關
如果想要規避ABA問題,可以為共享變量引入一個修訂號,或者時間戳,每次修改共享變量值,相應的修訂號加1.,就會變更為[A,0]=>[B,1]=>[A,2],每次對共享變量的修改都會導致共享變量的增加,通過這個標識就可以判斷。AtomicStampedReference類就是基於這個思想產生的。
原子變量類
原子類變量是基於CAS實現的,當對共享變量進行read(讀)-modify(修改)-write(寫)操作時,通過原子類變量可以保障原子性與可見性,對變量的read(讀)-modify(修改)-write(寫)操作不是指一個簡單的賦值,而是變量的新值,依賴變量的舊值,如自增操作i++,由於volatile只能保證原子的可見性,而不能保證原子性,原變量類內部就是一個借助volatile變量,並且保障了該變量的read-modify-wirte操作的原子性,有時把原子變量看作一個增強的volatile變量,原子變量類有12個
分組 | 原子變量類 |
---|---|
基礎數據型 | AtomicInteger、AtomicLong、AtomicBoolean |
數組型 | AtomicIntegerArry、AtomicLongArry、AtomicReferenceArry |
字段更新器 | AtomocIntegerFiledUpdater、AtomicLongFieldUpdate、AtomicReferenceFiledUpdater |
引用型 | AtomicReference、AtomicStampedReference、AtomicMarkableReference |
使用AtomicLong定義計數器
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
public class Text15 {
//構造方法私有化
private Text15(){}
//私有靜態對象
private static final Text15 text=new Text15();
//公共靜態方法返回該類的實例
public static Text15 getInstance()
{
return text;
}
//使用原子類記錄保存請求總數 成功數 失敗數
private final AtomicLong RequestCount=new AtomicLong(0);
private final AtomicLong SuccessCount=new AtomicLong(0);
private final AtomicLong FailCount=new AtomicLong(0);
//進行自增
public void RequestCount()
{
RequestCount.incrementAndGet();
}
public void SuccessCount()
{
SuccessCount.incrementAndGet();
}
public void FailCount()
{
FailCount.incrementAndGet();
}
//查看總數
public long GetRequestCount()
{
return RequestCount.get();
}
public long GetSuccessCount()
{
return SuccessCount.get();
}
public long GetFailCount()
{
return FailCount.get();
}
}
class Text16
{
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
Text15.getInstance().RequestCount();//請求數量
int num=new Random().nextInt();
if(num%2==0)//如果是偶數就成功
{
Text15.getInstance().SuccessCount();
}
else
Text15.getInstance().FailCount();
}
}
}).start();
Thread.sleep(1000);
System.out.println("請求總數:"+Text15.getInstance().GetRequestCount());
System.out.println("請求成功"+Text15.getInstance().GetSuccessCount());
System.out.println("請求失敗"+Text15.getInstance().GetFailCount());
}
}