把java基础撸一边,从简单的开始。
线程部分:
学习原子类,先来了解一下一个概念,乐观锁与悲观锁。
乐观锁与悲观锁:
乐观锁:
简单理解:就是认为该数据在获取的时候不会被其他线程修改,更新的时候判断一下就可以了。是否一样,像是缓存。更新给个标志就可以。在前面的介绍的volatile就是个乐观锁,自认为不会被其他线程修改,其他线程读就不会出现脏数据的出现。乐观锁,感觉就像是个缓存数据一样,会更新,明知会更新,但取的时候放心大胆乐观地取。
悲观锁
简单理解,就是和乐观锁相反。其他线程必定会对数据进行修改,不敢放心大胆得取数据,所以要多这个数据要强行锁住,像是synchronize,对线程进行互斥。保证数据读取的时候不会被修改。
推荐文章:乐观锁与悲观锁
原子类:
悲观锁这样对数据进行了保护,操作数据的时候,只有一个线程在场。synchronize就证明了这点,但是这样让其他线程堵塞又执行是非常消耗性能的。那么乐观锁是怎么保证乐观锁的,volatile是肯定不行的。
volatile,这个修饰词只能保证可见性。不能保证原子性。
实例<一>:
public class Demo41 {
public volatile int val = 0 ;
public void set(){
val++;
}
public static void main(String[] age){
Demo41 demo41 = new Demo41();
new Thread(new Runnable() {
@Override
public void run() {
for (;;){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo41.set();
System.out.println(Thread.currentThread().getName() + " val : "+demo41.val);
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (;;){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo41.set();
System.out.println(Thread.currentThread().getName() + " val : "+demo41.val);
}
}
}).start();
...
}
}打印结果:
Thread-2 val : 12
Thread-0 val : 14
Thread-1 val : 14
Thread-2 val : 15
Thread-0 val : 17
java除了synchronize提供原子性,还提供了很多。比如原子类系列
import java.util.concurrent.atomic.Atomic**;实例<二>:
public class Demo41 {
public AtomicInteger val = new AtomicInteger(0) ;
public int set(){
return val.getAndIncrement();//自加1
}
public static void main(String[] age){
Demo41 demo41 = new Demo41();
new Thread(new Runnable() {
@Override
public void run() {
for (;;){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " val : "+demo41.set());
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (;;){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " val : "+demo41.set());
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (;;){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " val : "+demo41.set());
}
}
}).start();
}
}打印
Thread-0 val : 0
Thread-1 val : 1
Thread-2 val : 2
Thread-0 val : 3
Thread-1 val : 4
Thread-2 val : 5
Thread-0 val : 6简单看源码:
AtomicIn系列有很多,下面是AtomicInteger的源码。我拿个比较简单的讲,标题是简单看源码。
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// 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); }
}
private volatile int value;发现:
1:继承了Number。这个类有一些对数值类型的操作。
2:实现了Serialzable接口。
3:成员变量创建了Unsafe。
4:成员变量中有Long类型valueOffset,private修饰,不能被其他类改动,能猜到这个字段就是这个类操作的核心了。
4:静态代码块中,对valueOffset做了处理。unsafe.objectFieldOffset(File)这个方法,获取该属性在内存中的偏移位置。Long类型用来存储这个获取的地址。
5:拥有private和volatile修饰的value int类型值,也就是这个类计算的值了。
使用方法探究:
在上面实例的代码中,有两处使用一个是实例化的时候,一个是自增的时候。
public AtomicInteger val = new AtomicInteger(0) ;
public int set(){
return val.getAndIncrement();
}看构造方法的源码
public AtomicInteger(int initialValue) {
value = initialValue;
}只是个普通的复制功能。注意,发现5
在来看自加方法
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}这里使用到了unsafe类的操作。进行查看这个方法的源码
//内存地址V,旧的预期值A,即将要更新的目标值B。
/*public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = this.getIntVolatile(o, offset); //拿到内存位置的最新值
//拿到内存位置的最新值v,使用CAS尝试修将内存位置的值修改为目标值v+delta,如果修改失败,
则获取该内存位置的新值v,然后继续尝试,直至修改成功
} while(!this.compareAndSwapInt(o, offset, v, v + delta));
return var5;
}*/这里看到了的是CSA算法,即比较并替换,是一种实现并发算法时常用的技术。
这个比较是个do...while循环比较,这里可以看到那个Long类型存地址是干嘛的了,就是为了取数据的,获得现在内存存取的值,然后拿需要修改的值进行比较,如果是样的说明修改成功,如果没有修改成功,说明还有人改,所以继续循环(没有做重量锁,如synchronize所以会有多个线程进入)上面的注释写了,大家放大点看,也可以从下面推荐文章中看。
还要注意,为什么可以及时通知到数据的更改,是拥有有volatile修饰,可见性。在创建原子类的时候,value被volatile修饰。不得不说句,牛逼。
原子类系列,还有可以操作对象,数组。大同小异,就不一一介绍了。谢谢各位观看。
推荐文章:面试毕问的CAS,你懂吗?