个人笔记 Java多线程(四)

这段笔记是参照b站教程BV1Rv411y7MU整理而来的,用于个人备忘以便复习,需要的朋友可以自取。

线程同步(下)

1.原子类

1.1 常用原子类自增自减操作

我们知道++,–不是原子操作,除了使用synchronized进行同步操作外,也可以使用AtomicInteger、AtomicLong进行原子类进行实现。

public class Main02 {

    public static void main(String[]args) throws InterruptedException {

        for(int i=0;i<100;i++){
            new MyThread().start();
        }
        Thread.sleep(1000);
        System.out.println(MyThread.count.get());
        
    }

    static class MyThread extends Thread{

        private static   AtomicInteger count = new AtomicInteger();

        public static void addCount(){
            for (int i = 0; i < 1000; i++) {
                count.getAndIncrement();//count++
            }
            System.out.println(Thread.currentThread().getName()+" --> "+count.get());
        }

        @Override
        public void run() {
            addCount();
        }
    }
}

上述代码在控制台显示的结果中可能有的线程打印不为1000的倍数,原因是此时代码没有上锁,所以是不同步的,在一个线程打印的时候,其他线程还在执行自增操作,所以导致控制台输出的时候显示不为整数。
但是由于AtomicInteger保证了原子性,所以最后结果肯定是正确的,不会产生脏读。
AtomicInteger常用方法:

  1. .getAndIncrement()相当于x++
  2. .incrementAndGet()相当于++x
  3. .get()返回int值

1.2 CAS

CAS(Compare and Swap)是由硬件实现的。CAS可以将read-modify-write这类操作转换为原子操作。

i++自增操作包括三个子操作:读取i的值;i的值+1;把新值保存到主内存。而CAS原理为:将数据更新到主内存的时候,再次读取主内存变量的值,如果现在变量的值与期望的值(操作起始读取的值)不一样就更新

使用CAS实现一个线程安全的计数器

public class CASTest {

    public static void main(String[]args){

        CASCounter casCounter = new CASCounter();

        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(casCounter.incrementAndGet());
                }
            }).start();
        }
    }

    static class CASCounter{
        public volatile long value;//使用volatile修饰 使线程可见

        public long getValue(){
            return this.value;
        }

        public boolean compareAndSwap(long expectedValue,long newValue){
            /**
             * 如果当前value与期望值expectValue一样就把value替换称newValue
             */
            synchronized (this){
                if(this.value==expectedValue){
                    this.value = newValue;
                    return true;
                }else
                    return false;
            }
        }

        //定义自增方法
        public long incrementAndGet(){
            long oldValue;
            long newValue;
            do{
                oldValue = this.value;
                newValue = oldValue+1;
            }while ( !compareAndSwap(oldValue,newValue) );

            return newValue;
        }
    }
}

1.3 CAS导致ABA问题

CAS实现原子操作背后有一个假设:共享变量的当前值与当前线程提供的期望值相同的时候,就认为这个变量没有被其他线程修改过。

实际上这种假设并不是总一定成立,加入有共享变量count=0
A线程对count修改为10,
B线程对count修改为20,
C线程对count修改为0,
当前线程看到count变量的值为0,现在是否认为count变量的值是否认为没有被其他线程更新呢?这种结果是否可以被接受,这就是CAS中ABA问题:即共享变量经历A->b->A的更新
ABA是否被接受和实现算法有关,如果想要规避ABA问题,可以为共享变量引入一个修订号(时间戳),每次修改共享变量的时候,响应的修订号就加1。ABA变量更新过程为:[A,0]->[B,1]->[A,2]。通过修订号就可以判断变量是否被其他线程修改过。


1.4 原子变量类

原子变量类基于CAS实现,当对共享变量进行read-motify-write操作的时候,通过原子变量类可以保障操作的原子性与可见性。对变量的read-motify-write更新操作是指当前操作不是一个简单的赋值,而是指变量的新值依赖变量的旧值,如自增操作i++。

由于volatile只能保障可见性不能保证原子性,原子类内部就是借助了一个volatile变量,并且保障了该变量的read-motify-write操作的原子性,有时候把原子变量类看作是volatile变量的增强

原子变量类有12个,如:

分组原子变量类
基础数据型AtomicInteger、AtomicLong、AtomicBoolean
数组型AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
字段更新器AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicReferenceFieldUpdater
引用型AtomicReference
AtomicStampedReference(时间戳)
AtomicMarkableReference(标志)

1.4.1 AtomicLong代码演示

模拟服务器请求的计数器:
计数器类:

/**
 * 计数器类
 * 模拟服务器的请求总数,处理成功数,处理失败数
 */
public class Test {

    public static void main(String[]args) throws InterruptedException {

        //通过线程模拟操作
        //实际中可以使用ServletFilter中调用Indicator计数器的相关方法
        for (int i = 0; i < 10000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //每个线程就是一个请求,请求总数要+1
                    Indicator.getInstance().newRequestReceive();

                    int num = (int) (Math.random()*1000);
                    if(num % 2 == 0){
                        Indicator.getInstance().requestProcessSuccess();
                    }else {
                        Indicator.getInstance().requestProcessFail();
                    }
                }
            }).start();
        }

        Thread.sleep(1000);

        System.out.println("请求总数-->"+Indicator.getInstance().getRequestCount());
        System.out.println("成功总数-->"+Indicator.getInstance().getSuccessCount());
        System.out.println("失败总数-->"+Indicator.getInstance().getFailCount());
    }
}

主类:

/**
 * 模拟服务器的请求总数,处理成功数,处理失败数
 */
public class Test {

    public static void main(String[]args) throws InterruptedException {

        //通过线程模拟操作
        //实际中可以使用ServletFilter中调用Indicator计数器的相关方法
        for (int i = 0; i < 10000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //每个线程就是一个请求,请求总数要+1
                    Indicator.getInstance().newRequestReceive();

                    int num = (int) (Math.random()*1000);
                    if(num % 2 == 0){
                        Indicator.getInstance().requestProcessSuccess();
                    }else {
                        Indicator.getInstance().requestProcessFail();
                    }
                }
            }).start();
        }

        Thread.sleep(1000);

        System.out.println("请求总数-->"+Indicator.getInstance().getRequestCount());
        System.out.println("成功总数-->"+Indicator.getInstance().getSuccessCount());
        System.out.println("失败总数-->"+Indicator.getInstance().getFailCount());
    }
}

1.4.2 AtomicIntegerArray
  • 基本操作
//创建一个指定长度的原子数组
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
System.out.println(atomicIntegerArray);  //[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

//返回指定位置的元素
System.out.println(atomicIntegerArray.get(0));
System.out.println(atomicIntegerArray.get(1));

//设置指定位置元素的方法
atomicIntegerArray.set(0,10);
//设置数组元素新值的时候同时返回旧值
System.out.println(atomicIntegerArray.getAndSet(1,12)); //0
                                                                    //且数组为[10, 12, 0, 0, 0, 0, 0, 0, 0, 0]
//修改数组某个元素的值,把数组元素加上某个值并返回
System.out.println(atomicIntegerArray.addAndGet(1,10)); //22

//先得到指定位置的值,再修改数组
System.out.println(atomicIntegerArray.getAndAdd(2,33)); //0
                                                                //数组为[10, 22, 33, 0, 0, 0, 0, 0, 0, 0]

//CAS操作
//如果索引为0的地方的值为10就将其修改为222 替换成功返回true反之false
System.out.println(atomicIntegerArray.compareAndSet(0,10,222));

//自增自减
System.out.println(atomicIntegerArray.incrementAndGet(0)); //++atomicIntegerArray[0]
System.out.println(atomicIntegerArray.getAndIncrement(0)); //atomicIntegerArray[0]++
System.out.println(atomicIntegerArray.decrementAndGet(1)); //--atomicIntegerArray[1]
System.out.println(atomicIntegerArray.getAndDecrement(1)); //atomicIntegerArray[1]--
  • AtomicIntegerArray演示案例:
/**
 * 多线程中使用AtomicIntegerArray原子数组
 */
public class Test {

    //定义原子数组
    static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);

    public static void main(String[]args) throws InterruptedException {

        //定义一个县城数组
        Thread[] threads = new Thread[10];
        
        //给线程数组赋值
        for (int i = 0; i < 10; i++) {
            threads[i] = new AddThread();
        }

        //开启子线程
        for(Thread thread:threads){
            thread.start();
        }

        //在主线程中查看自增完以后原子数组中各个元素的值,在主线程中需要所有的子线程都执行完了再查看
        //把所有的子线程合并到主线程之中
        for(Thread thread:threads){
            thread.join();
        }
        System.out.println(atomicIntegerArray);
    }

    //定义线程类
    static class AddThread extends Thread{
        @Override
        public void run() {

            //把原子数组的每个元素自增1000次
            for(int i=0;i<1000;i++){
                for(int j=0;j<atomicIntegerArray.length();j++){
                    atomicIntegerArray.getAndIncrement(j % atomicIntegerArray.length());
                }
            }
        }
    }
}

1.4.3 AtomicIntegerFieldUpdater

AtomicIntegerFieldUpdater可以对原子整数字段进行更新,要求:

  1. 字符必须使用volatile修饰,使线程之间可见
  2. 只能是实例变量,不能是静态变量,也不能用final修饰

基本使用通过以下案例介绍:
User类

/**
 * 使用AtomicIntegerFieldUpdater更新的字段必须使用volatile修饰
 */
public class User {
    int id;
    volatile int age;

    public User(){}

    public User(int id,int age){
        this.id=id;
        this.age=age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                '}';
    }
}

线程类

public class SubThread extends Thread{

    private User user;

    //创建一个AtomicIntegerFieldUpdater更新器
    private AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");

    public SubThread(User user){
        this.user = user;
    }

    @Override
    public void run() {
        //在子线程中对user对象中age字段自增10次
        for (int i = 0; i < 10; i++) {
            System.out.println(atomicIntegerFieldUpdater.getAndIncrement(user));
        }
    }
}

主类

public class Test {
    public static void main(String[]args) throws InterruptedException {

        User user = new User(1,10);

        //开启10个线程
        for (int i = 0; i < 10; i++) {
            new SubThread(user).start();
        }

        Thread.sleep(1000);
        System.out.println(user);
    }
}

1.4.4 AtomicReference

可以原子读写一个对象

通过一个代码演示操作:

public class Test {

    //创建一个AtomicReference对象
    static AtomicReference<String> atomicReference = new AtomicReference<>("abc");

    public static void main(String[]args) throws InterruptedException {

        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if (atomicReference.compareAndSet("abc", "def")) {
                        System.out.println(Thread.currentThread().getName() + " --> 把字符串abc修改为def");
                    }
                }
            }).start();
        }

        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if(atomicReference.compareAndSet("def","abc")){
                        System.out.println(Thread.currentThread().getName()+" --> 把字符串def还原为abc");
                    }
                }
            }).start();
        }

        Thread.sleep(1000);
    }
}

  • AtomicReference中可能会出现ABA问题
public class Test {

    //创建一个AtomicReference对象
    static AtomicReference<String> atomicReference = new AtomicReference<>("abc");

    public static void main(String[]args) throws InterruptedException {

        //创建t1线程,想把adc改为def,再把字符串还原为abc
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                atomicReference.compareAndSet("abc","def");
                System.out.println(Thread.currentThread().getName()+"-->"+atomicReference.get());
                atomicReference.compareAndSet("def","abc");
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //睡一秒
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

               atomicReference.compareAndSet("abc","ghi");
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(atomicReference.get());
    }
}

上述代码中引发了ABA问题。在t1线程中我们进行了ABA操作,即将abc改为def再改为abc。t2线程中再进行判断,如果为abc就修改为ghi,此时控制台最后打印ghi。
而CAS的原理表示如果传过来的值和期望值一样就表示数据没有进行修改,这样就设置一个新的值。但实际上我们在t1线程中已经对它的值进行了修改,此时t2中仍然发生了改动。


1.4.5 AtomicStampedReference解决ABA问题
/**
 * 在AtomicStampedReference原子类中有一个证数标记值stamped,
 * 每次执行CAS操作的时候,需要对比它的stamped。
 */
public class Test {

    //public static AtomicReference<String> atomicReference = new AtomicReference<>("abc");

    //自定义AtomicStampedReference引用操作abc字符串,指定初始版本号为0
    public static AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>("abc",0);

    public static void main(String[]args) throws InterruptedException {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 固定写法! 后两项为期望版本号和新版本号,期望版本号和当前版本号一样的时候就赋新版本号(即+1)
                stampedReference.compareAndSet("abc","def",stampedReference.getStamp(),stampedReference.getStamp()+1);
                System.out.println(Thread.currentThread().getName()+"-->"+stampedReference.getReference());
                stampedReference.compareAndSet("def","abc",stampedReference.getStamp(),stampedReference.getStamp()+1);
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                int stamp = stampedReference.getStamp();//获得版本号
                System.out.println(stampedReference.compareAndSet("abc","ghi",stamp,stamp+1));
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(stampedReference.getReference());
    }
}

上述代码中线程t2的int stamp = stampedReference.getStamp();语句不可以移到Thread.sleep(1000)之前。
因为在t2 sleep期间t1还在工作,可能再次期间版本号发生了改变,导致CAS操作发生错误。推荐使用stampedReference.getStamp()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值