多线程2

CAS(无锁优化,自旋)

            全称:Compare And Set

       java类库里Atomic 开头的类都是使用CAS来保证线程安全的,没有加锁
       
       cas(V,Expected,NewValue)   
          cas是方法
           如想将0改为1   
            那么 V是需要改的值:0     Expected:期望的值:也应该为0   
                NewValue  是新的值:为1
                 
                 也就是  if  V==E  
                            V=value
                            否则就再试一次
                         如果值和期望的值一样,则进行修改,否则就再是一次,或者结束
                CAS是cpu指令级别的支持,中间不能被打断

ABA问题

         修改一个值时,如将1修改为2 ,  
         调用cas方法,此时有可能其他线程的操作将该值改为了2,然后又改为了1,
         虽然最后值没有改变,但是却被其他线程调用过
          
          解决办法,是加版本号,只要做了任何的修改,版本值都要加1
public class myAtomic {

    AtomicInteger at = new AtomicInteger(0);//初始值
    public static void main(String[] args) {
        myAtomic my = new myAtomic();
        List<Thread> li = new ArrayList<>();
        for (int i = 0; i < 6; i++) {
            li.add(new Thread("thread" + i) {
                @Override
                public void run() {
                    my.a();
                }
            });
        }
      li.forEach((o)->{
          o.start();
      });

      li.forEach((o)->{
          try {
              o.join();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      });

        System.out.println(my.at);

    }

    void a() {
        for (int i = 0; i < 1000; i++)
            at.incrementAndGet();  相当于at++,但使用同步的方式加1,原子操作,
    }
}
最后输出6000

这也是CAS的一种

 AtomicLong  类
 
 想比于内置锁的方式,也就是比(synchronized)会更加高效
 原因是没有用到锁,原子性的操作

 还有一种方式比  比atomicLong效率还高,那就是longAdder 
 它内部使用的是分段锁,最后再相加,效率提高了许多
 分段锁又是CAS实现的。多段并行运行,在线程数比较多的情况下,效率比较高
 
   AtomicInteger a=new AtomicInteger(4);
        a.compareAndSet(4,6); 4期望的值,6修改的值,修改成功
        a.compareAndSet(4,7);此时,期望值不同,要加改为6了,不做任何的操作
        System.out.println(a.get());
        如,这一段,

底层我们发现有一个Unsafa类

java无法操作内存
Java可以操作c++  使用native
c++可以操作内存

我们可以通过这个类来操作内存,相当于时java的一个后门
unsafa是调用底层的,当我们使用AtomicInteger.getAndIncrement()去加1时,底层也是使用了比较和交换,还有自旋,就是一直比较,知道达到期望值
就是do..while一直在比较,也就是底层是自旋锁,有原子性
缺点
  自旋,循环耗时
  一次性只能保证一个共享变量的原子性


  ABA问题
    携带版本号

解决
  使用该类 AtomicStampedReference a=new AtomicStampedReference(4,1);
      该类可以携带版本号参数   ,4是存入的值,1是版本号
  a.compareAndSet(4,5,a.getStamp(),a.getStamp()+1);  设置值  ,Stamp是获取版本号,这个有返回值,成功则返回ture,否则false
   a.getReference()获取值

不需要版本信息的可以使用AtomicReference类

补充:所有的相同的包装类对象之间值得比较,使用equals,对于Integer var在-128 ~ 127之间的赋值
Integer是在IntegerCache.cache产生的,会复用对象,但是在这个区间外的所有数据都会在堆上产生,也就是不同的对象


还有很多的原子操作的实现
 例如
 LongAccumulator
 DoubleAdder
 DoubleAccumulator
  
`

     补充:ReentrantLock   
             有公平锁和非公平锁
             公平锁讲究先来后到
             非公平锁,先会尝试获取锁,而不是再队列中等待
             底层也是CAS,之前没讲、 、
             
             notify(),notifyall不释放锁因此唤醒线程,也不会立刻退出临界区,而是执行完临界区代码后释放锁
               
               

AQS
        
        Reentrantlock  .ReadWriteLock底层都是AQS实现的
         
         LockSupper
               之前我们需要阻塞和释放可以使用wait,或者await方法
               现在我们可以使用LockSupper类 ,该类个控制线程睡眠时,可以控制指定某个线程醒来
```java
public class mylocksupper {

    public static void main(String[] args) {
        Thread th=new Thread(()->{
            for (int i=0;i<10;i++){
                if (i==5)
                    LockSupport.park();
                System.out.println(i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        th.start();
        try {
            Thread.sleep(7000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LockSupport.unpark(th);  指定线程,且该方法可以在park()方法前执行
    }
}

注意,该LockSupport.park();是阻塞当前线程,   
      LockSupport.unpark(th);是指定线程不会阻塞,但是不会停止当前线程的执行,所以如果想让其他线程执行的话,需要加上park()方法阻塞



如果我们有10个消费者和2个生产者时,如何设置
 也就是说我们有12个线程,两个负责生产,10个负责消费,
 按照普通的设计,我们会在没有产品的时候,消费者等待,在产品生产满时,则生成等待,一般都是使用wait()notifyall()来控制
 但是有一个地方,就是,notifyall有可能会唤醒另一个生产者,使之加入竞争队列里,这是不好的
   
   解决,可以使用  Reentrantlock的newCondition()  我们创建两个这样的,来分别控制消费和生产 
     
     而Condition得本质就是不同的等待队列
  
 aqs是所有锁的底层
 AQS的底层是cas+volatile     
           state 是volatilr修饰的int类型     设置state的方法除了setState还有compareAndState
         那么这个值是什么呢,由子类来定,如Reentranlock, 线程得到了这把锁就变为1,释放了就变回0
         不同的锁的实现是不一样的
          
          aqs核心是state以及监控state的双向链表
          aqs内部维护了一个队列,队列里面每一个都是节点,队列里面有一个线程变量thread,所有又叫线程队列,
          里面还有prev 和next     所以节点可以指向前面后后面,而且都是volatile修饰的
          节点可以指向前面又可以指向后面,所以是双向链表
           
           所以哪个线程的得了锁,哪个线程等待都要进入这个队列
           抢不到锁的线程就进入队列中等待
           
           那么抢的时候是怎么抢的呢,
                 获取当前线程,然后再获取state 
                 判断state是否等于0,如果等于0,说明锁没有被占用,那么就开始使用cas来操作
                 判断  (期望值是0,改成1,如果改成了,)则将该线程设置为占有这把锁得线程 
                  
                  那么如果判断当前线程已经占有了这把锁呢,那么就将state继续加1,这种其实就是可重入
                   
                继承关系
                AQS           Sync     NonfairSync 
                 
                 如Reentranlock类    
                    该类就有两个内部类NonfairSync ,Sync ,该类继承了Sync类,而Sync类继承了AbstractQueuedSynchronizer,也就是AQS 
                    调用lock方法时,最终调用得是NonfairSync类得重载方法
                     final void lock() {
                           if (compareAndSetState(0, 1))
                        setExclusiveOwnerThread(Thread.currentThread());
                  else
                          acquire(1);
                    } 
                    这段操作属于cas操作,如果锁没有被占用,则占用这把锁,如果是被占用了,就调用  acquire(1);方法

                       public final void acquire(int arg) {
                              if (!tryAcquire(arg) &&
                                  acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                                 selfInterrupt();
                             }   
                             获取锁  ,自旋,加入等待队列中,加入到AQS的线程对列中等待
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值