前言
多線程下的線程安全,主要是由於無法控制線程的執行順序,無法確定那個線程是先執行,是由CPU確定的,出現線程不安全的情況-》每次運行的結構都不相同(程序無法按照我們想要的結果)
下面是一個簡單的多線程例子,來說明下多線程下的數據安全
package com.el.jichu.thread.writerlock;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Auther: roman.zhang
* @Date: 2019/4/10 8:44
* @Version:V1.0
* @Description:LockDemo1
*/
public class LockDemo1 {
// private AtomicInteger atomicInteger=new AtomicInteger(0);
private int i=0;
public void add(){
i++;
//atomicInteger.getAndIncrement();
}
public static void main(String[] args) throws InterruptedException {
LockDemo1 demo1 = new LockDemo1();
for(int i=0;i<2;i++){
new Thread(()->{
for(int j=0;j<10000;j++){
demo1.add();
}
}).start();
}
Thread.sleep(3000);
// System.out.println("計算結果是:"+demo1.atomicInteger);
System.out.println("計算結果是:"+demo1.i);
}
}
每次運行結果:
第一次:11783
第二次:12670
上面之所以出現這種情況,是多線程下,無法控制線程間的執行順序,無法確定在執行完一個線程後(即完成一個整體操作後,再去執行另一個線程)。
解決方式
1.使用AtomicInterger 下面是AtomicInterger相關的類
直接上代碼:
package com.el.jichu.thread.writerlock;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Auther: roman.zhang
* @Date: 2019/4/10 8:44
* @Version:V1.0
* @Description:LockDemo1
*/
public class LockDemo1 {
private AtomicInteger atomicInteger=new AtomicInteger(0);
//private int i=0;
public void add(){
//i++;
atomicInteger.getAndIncrement();
}
public static void main(String[] args) throws InterruptedException {
LockDemo1 demo1 = new LockDemo1();
for(int i=0;i<2;i++){
new Thread(()->{
for(int j=0;j<10000;j++){
demo1.add();
}
}).start();
}
Thread.sleep(3000);
System.out.println("計算結果是:"+demo1.atomicInteger);
//System.out.println("計算結果是:"+demo1.i);
}
}
使用AtomicInter就能保證每次得到我們想要的結果
2.使用synchronized這個方式,不在介紹,介紹一個功能更多的Lock
直接上代碼:
package com.el.jichu.thread.writerlock;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Auther: roman.zhang
* @Date: 2019/4/10 8:44
* @Version:V1.0
* @Description:LockDemo1
*/
public class LockDemo1 {
//private AtomicInteger atomicInteger=new AtomicInteger(0);
private int i=0;
private Lock lock=new ReentrantLock();
public void add(){
lock.lock();
try {
i++;
} finally {
lock.unlock();
}
//i++;
//atomicInteger.getAndIncrement();
}
public static void main(String[] args) throws InterruptedException {
LockDemo1 demo1 = new LockDemo1();
for(int i=0;i<2;i++){
new Thread(()->{
for(int j=0;j<10000;j++){
demo1.add();
}
}).start();
}
Thread.sleep(3000);
// System.out.println("計算結果是:"+demo1.atomicInteger);
System.out.println("計算結果是:"+demo1.i);
}
}
下面介紹下AtomicInteger的原理(CAS)
CAS (硬件語言) 即每個線程從內存中拿到值(稱爲當前值),以及想把內存值改爲的目標值,每次去更新內存值時,都會將當前值與內存值比較,如果相同,則把內存值變爲目標值。
使用CAS方式實現線程同步
package com.el.jichu.thread.writerlock;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Auther: roman.zhang
* @Date: 2019/4/10 8:44
* @Version:V1.0
* @Description:LockDemo1
*/
public class LockDemo1 {
//private AtomicInteger atomicInteger=new AtomicInteger(0);
private int i=0;
private Lock lock=new ReentrantLock();
static Unsafe unsafe=null;
private static long valueOffset;
static {
//反射
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe= (Unsafe) theUnsafe.get(null);
//目標:通過unsafe去調用底層硬件原語
//無法直接操作內存,委屈求全,只能去通過對象中屬性的偏移量,去修改值
valueOffset = unsafe.objectFieldOffset(LockDemo1.class.getDeclaredField("i"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public void add(){
///使用CAS去實現同步
//爲什麼是個循環:因爲CAS會失敗,因此纔會使用循環
int current ;
do{
current=unsafe.getIntVolatile(this,valueOffset);
}while(!unsafe.compareAndSwapInt(this,valueOffset,current,current+1));
/**
* unsafe.compareAndSwapInt(當前對象,內存值,當前值,新值)
*/
/* lock.lock();
try {
i++;
} finally {
lock.unlock();
}*/
//i++;
//atomicInteger.getAndIncrement();
}
public static void main(String[] args) throws InterruptedException {
LockDemo1 demo1 = new LockDemo1();
for(int i=0;i<2;i++){
new Thread(()->{
for(int j=0;j<10000;j++){
demo1.add();
}
}).start();
}
Thread.sleep(3000);
// System.out.println("計算結果是:"+demo1.atomicInteger);
System.out.println("計算結果是:"+demo1.i);
}
}
AtomicXXX源碼:
3.使用自定義鎖,實現線程同步
手寫鎖代碼
package com.el.jichu.thread.writerlock;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
/**
* @Auther: roman.zhang
* @Date: 2019/4/10 10:32
* @Version:V1.0
* @Description:CustomLock
* 思路
* 1.沒有獲取鎖的線程,如何讓線程掛起,不在往下執行,等待其他線程釋放鎖。
* 2.釋放鎖之後,如何通知其他線程去獲取鎖
*/
public class CustomLock implements Lock {
//鎖的擁有者
AtomicReference<Thread> owner=new AtomicReference<>();
//一個容器存儲等待的線程
ConcurrentHashMap<Thread,Object> queue=new ConcurrentHashMap<>();
@Override
public void lock() {
while( !owner.compareAndSet(null,Thread.currentThread())){
//沒有獲取成功,則將該線程停下來
queue.put(Thread.currentThread(),"");
//正在運行的線程進入停車場
LockSupport.park();
queue.remove(Thread.currentThread());
}
}
@Override
public void unlock() {
while(owner.compareAndSet(Thread.currentThread(),null)){//釋放鎖
//通知其他線程去park線程,繼續去強鎖
// Thread next=null;
ConcurrentHashMap.KeySetView<Thread, Object> threads = queue.keySet();
for(Thread t:threads){
LockSupport.unpark(t);
}
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
}
測試代碼
package com.el.jichu.thread.writerlock;
import java.util.concurrent.locks.Lock;
/**
* @Auther: roman.zhang
* @Date: 2019/4/10 15:15
* @Version:V1.0
* @Description:LockDomo2
*/
public class LockDomo2 {
private int i;
//private AtomicInteger atomicInteger=new AtomicInteger(0);
private Lock customLock=new CustomLock();
public void add(){
customLock.lock();
try {
i++;
} finally {
customLock.unlock();
}
//atomicInteger.getAndIncrement();
}
public static void main(String[] args) throws InterruptedException {
LockDomo2 demo2 = new LockDomo2();
for(int i=0;i<2;i++){
new Thread(()->{
for(int j=0;j<10000;j++){
demo2.add();
}
}).start();
}
Thread.sleep(3000);
// System.out.println("計算結果是:"+demo1.atomicInteger);
System.out.println("計算結果是:"+demo2.i);
}
}