对于以下代码期望输出是什么
public class Demo {
int i = 0;//
public void incr(){
i++;
}
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
for(int j=0;j<2;j++){
new Thread(() ->{
for(int k=0;k<10000;k++){
demo.incr();
}
}).start();
}
Thread.sleep(1000L);
System.out.println(demo.i);
}
}
- 运行后我们会发现,每次运行得到的结果都不一样。是一个介于一万到两万之间的值
- 原因–该操作不具备原子性(可以是一个步骤,也可以是多个步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分——不可中断性)曾个操作作为一个整体
- 并发编程的三大特性
原子性:即一个或者多个操作作为一个整体,要么全部执行,要么都不执行,并且操作在执行过程中不会被线程调度机制打断;而且这种操作一旦开始,就一直运行到结束,中间不会有任何上下文切换
可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。
我们去看执行i++方法编译后对应的字节码文件中的操作步骤会发现。在JVM中++有多个操作。多个线程执行i++违背了原子性
有序性:即程序执行的顺序按照代码的先后顺序执行。
public void incr(){
i++;//存放在堆中
}
// 反编译字节码
Code:
stack=3, locals=1, args_size=1
0: aload_0//从局部变量0中转载引用类值入栈
1: dup//复制栈顶一i个字长的数据。将赋值后的数据压栈
2: getfield #2 // Field i:I 获取对象字段的值 #2对应的成员变量I
5: iconst_1//1 入栈
6: iadd //将栈顶两int类型数据相加结果入栈
7: putfield #2 // Field i:I 给局部变量I赋值
10: return //void函数返回结果
-
JDK中原子操作类
更新基本类型 AtomicBoolean,AtomicInteger,AtomicLong
更新数组AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
更新引用AtomicReference,AtomicMarkableReference,AtomicStampeReference
原子更新字段类AtomicReferenceFiledUpdater,AtomicIntegerFiledUpdater,AtomicLongFiledUpdater
全部以Atomic开头
使用原子操作类修改以后的代码
package com.zzy.user.a;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 加入Atomic
*/
public class AtomicDemo {
// int i = 0;
AtomicInteger i = new AtomicInteger(0);
public void incr(){
// i++;
i.incrementAndGet();//对变量进行加1(这是一个原子操作)
}
public static void main(String[] args) throws InterruptedException {
AtomicDemo demo = new AtomicDemo();
for(int j=0;j<2;j++){
new Thread(() ->{
for(int k=0;k<10000;k++){
demo.incr();
}
}).start();
}
Thread.sleep(1000L);
System.out.println(demo.i);
}
}
//输出20000
incrementAndGet源码
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
//getAndAddInt方法 注意该方法调用了一个compareAndSwapInt方法
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
//为什么使用do-while? 先获取旧值
do {
var5 = this.getIntVolatile(var1, var2);//内存中的新值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//compareAndSwapInt
//到这里发现后面的实现代码就看不到了 该方法的操作就是比较和交换CAS
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
//unsafe 可以访问内存 提供了三个方法
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
Atomic
基于CAS(Compare and swap)比较和交换
什么是case:case操作需要输入两个值,一个旧值A和一个新值B,在操作期间会先比较旧值有没有变化,如果没有变化,才交换新值,否则不交换
为什么JDK提供了Atomic类,却很少使用?
只能对单个变量操作,不能用于代码段类来实现原子操作
do-while心慌CAS,这种实现会让线程处于高频率运行,长时间不成功会有很大CPU资源消耗
锁 ReentrankLock(重入锁
Lock的一种实现
使用锁来实现多线程访问
package com.zzy.user.a;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 加入Atomic
*/
public class LockDemo {
int i = 0;
Lock lock = new ReentrantLock();//可重入锁
public void incr(){
// 加锁
lock.lock();//获取锁
try{
//可以是代码块。各种业务操作
i++;
}finally {
lock.unlock();//释放锁
}
}
public static void main(String[] args) throws InterruptedException {
LockDemo demo = new LockDemo();
for(int j=0;j<2;j++){
new Thread(() ->{
for(int k=0;k<10000;k++){
demo.incr();
}
}).start();
}
Thread.sleep(1000L);
System.out.println(demo.i);
}
}
question Lock怎么实现多线程访问的?
package com.zzy.user.a;
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;
// 非公平锁
public class MyLock implements Lock {
//加锁和释放锁都是多线程操作 使用AtomicReference
//原子操作类持有线程
AtomicReference<Thread> owner = new AtomicReference<>();
//等待列表 --存放没拿到锁的线程
public LinkedBlockingQueue<Thread> waiter = new LinkedBlockingQueue<>();
// 实现加锁 可重入CAS操作
@Override
public void lock() {
//当前的原子操作线程如果是空 获取锁(多个线程进行
while(!owner.compareAndSet(null,Thread.currentThread())){
// 没有获取锁的线程进入等待队列
waiter.add(Thread.currentThread());
LockSupport.park();//让线程等待
//如果该线程被唤醒,从等待列表删除
waiter.remove(Thread.currentThread());//线程执行到这里说明被唤醒了
};
}
//实现解锁 可重入锁所有线程都可以释放锁只有真正持有当前锁的线程才能真正释放锁
@Override
public void unlock() {
//如果当前线程是持有锁的线程才能解锁 原子操作线程置为null
if(owner.compareAndSet(Thread.currentThread(),null)){
// 唤醒等待队列
Object[] objects = waiter.toArray();//转数组
for(Object object : objects){//遍历所有等待线程
Thread next = (Thread)object;
//唤醒线程
LockSupport.unpark(next);
}
}
}
@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;
}
}
Synchronization与Lock的区别
synchronization —关键字,JVM层实现–C语言实现
Lock—JDK源码实现
共同点:CAS机制–比较和交换