十个线程去分别为final i = 0 加一万次,最后结束输出i的值
public class AtomicInteger1 {
static int i = 0;
public static void main(String[] args) throws Exception {
Thread[] ts = new Thread[10];//十个线程放到一个数组里
for (int k = 0; k < ts.length; k++) {
ts[k] = new Thread(new Runnable() {
@Override
public void run() {
for (int k = 1; k <= 10000; k++) {
i++;
}
}
});
ts[k].start();
}
for (int k = 0
; k < ts.length; k++) {
ts[k].join();//释放cpu,让下个线程去运算
}
System.out.println(i);//十个线程结束后输出i的值
}
}
计算结果:很明显和十万差的有点多,而且再次运行 i 的值会不同。
这里的原因主要是i++操作不是一个原子操作。
详细解释可看i++的底层解释(数据原子性)
原因
ts1线程拿到时间片进入运行状态,它可能刚把 i =10读入到寄存器 ,它的时间片就到了。另外一个线程拿到时间片了,它进入运行状态,它的操作也是i++;但此时它读入的i的值还是10,在寄存器运算之后将i=11写入i,此时i=11;ts1再次得到时间片时,它对寄存器中的 i 进行操作(别忘了读入的 i 是等于10的),因此ts1的结果还是把i = 11写入 i。丢失了一次i++操作。同理程序丢失了很多的i++操作。所以结果不是100000.
处理方法
1.给对象 加锁 (把int转换成对象类型integer)
public class AtomicInteger1 {
static int i = 0;
static Integer a = Integer.valueOf(0);
public static void main(String[] args) throws Exception{
Thread[] ts = new Thread[10];
for (int k = 0; k < ts.length; k++) {
ts[k] = new Thread(new Runnable() {
@Override
public void run() {
for (int k = 1; k <= 10000; k++) {
i++;
synchronized(a) {//那个线程得到a对象的锁,它来操作
a=Integer.valueOf(a.intValue()+1);//锁不住intValue()+1
}
}
}
});
ts[k].start();
}
for (int k = 0; k < ts.length; k++) {
ts[k].join();
}
System.out.println(i);
System.out.println("a的结果是"+a);
}
}
运行代码:结果还是没有解决
==原因:==对象没锁上,integer对象的值是不变的,内存中一开始有个对象a表示0,a=Integer.valueOf(a.intValue()+1);//让a+1之后,用一个新对象赋值给a,其实只是a的指向了新对象。不是在原有基础上把0改成1。这样,a线程对数字0这个integer对象加锁,b线程对数字1这个ingeter这个对象加锁…所以这个代码里没有临界资源,锁不住的。
改良版:创建一个类,类里面有个属性,创建一个这个类的对象,保证锁的是这个对象,里面的属性让它去变
public class AtomicInteger1 {
static int i = 0 ; //error!
static Integer b = Integer.valueOf(0);//error!
static MyObject1 obj = new MyObject1();//对这个对象加锁
public static void main(String[] args) throws Exception{
Thread[] ts = new Thread[10];
for(int k = 0 ; k<ts.length ; k++){
ts[k] = new Thread(new Runnable(){
public void run(){
for(int k = 1 ; k <= 10000; k++){
i++;
synchronized(b){
b = Integer.valueOf(b.intValue()+1);
}
synchronized(obj){
obj.x++;
}
}
}
});
ts[k].start();
}
for(int k=0; k <ts.length ; k++){
ts[k].join();
}
System.out.println("i的结果是:"+i);
System.out.println("b的结果是:"+b);
System.out.println("obj.x的结果是:"+obj.x);
}
}
class MyObject1{
public int x=0;//属性x等于0,x可变
}
结果正确
缺点并发效率低,只有一个线程可以拿到目的对象的锁。其他九个线程总是在等待状态。
2.Atomic包下的AtomicInteger类,是个包装类,完成整数的原子更新,没有用到锁,CAS算法
cas:比较交换算法,可以理解成:刚才的ts1准备把i=11写入i时,它自身有一个期待值(期待值为10,因为之前i=10),会让期待值与当前 i 的值去比较,若值相等,可以进行写入操作,否则写入失败,线程重新去读入i=11到寄存器,完成运算,直到成功写入。完成此次的i++操作。
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicInteger1 {
static int i = 0;
static AtomicInteger s = new AtomicInteger(0);//这里换成了AtomicInteger对象
public static void main(String[] args) throws Exception {
Thread[] ts = new Thread[10];
for (int k = 0; k < ts.length; k++) {
ts[k] = new Thread(new Runnable() {
@Override
public void run() {
for (int k = 1; k <= 10000; k++) {
i++;
s.incrementAndGet();//获取加1后的值 相当于++s
}
}
});
ts[k].start();
}
for (int k = 0; k < ts.length; k++) {
ts[k].join();
}
System.out.println(i);//原来的结果
System.out.println(s);//正确结果
}
}
这次的结果:正确
总结 一下 :这个方法是效率高的,线程安全的