并发编程4

回顾

  • 对象头的组成

    • 前56bit—在无锁情况下里面存的是hashcode,但是前提是hashcode必须计算
    • 57bit—没有使用
    • 58-61bit — 分代年龄,从eden区到survivor区,进入老年代,然后+1,一直到15
    • 62bit – 是否偏向
    • 63、64bit — 锁的级别
  • // 观察对象的分代年龄
    // 设置jvm的大小 --- -Xmx200m
    package BingFaBianCheng.bingFaBianCheng4.test;
    
    import BingFaBianCheng.bingFaBianCheng4.entity.A;
    import lombok.SneakyThrows;
    import lombok.extern.slf4j.Slf4j;
    import org.openjdk.jol.info.ClassLayout;
    
    import java.util.ArrayList;
    import java.util.List;
    
    @Slf4j(topic = "enjoy")
    public class TestJol {
           
    
        static A l = new A();
    
        static Thread t1;
        static  Thread t2;
        static List<A> list = new ArrayList<A>();
    
        public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {
           
    
    
    //      // System.out.println(Integer.toHexString(l.hashCode()));
    //        log.debug("线程还未启动----无锁");
    //        //log.debug(ClassLayout.parseInstance(l).toPrintable());
    //
    //
            t1 = new Thread(){
           
                @SneakyThrows
                @Override
                public void run() {
           
                    while (true) {
           
                        Thread.sleep(200);
    
                        log.debug(ClassLayout.parseInstance(l).toPrintable());
                    }
                }
            };
    
            t1.start();
    
            for (int i = 0; i < 999999; i++) {
           
                //Thread.sleep(2);
                list.add(new A());
            }
    //
    //        t2 = new Thread(){
           
    //            @Override
    //            public void run() {
           
    //                testLock();
    //            }
    //        };
    //        t1.setName("t1");
    //
    //        t1.start();
    //        //等待t1执行完后再启动t2
    //        t1.join();
    //
    //        t2.setName("t2");
    //        t2.start();
    
    
        }
    
        /**
         * synchronized 如果是同一个线程加锁
         * 交替执行 轻量锁
         * 资源竞争----mutex
         *
         *
         */
    
        public static void testLock(){
           
            //偏向锁  首选判断是否可偏向  判断是否偏向了 拿到当前的id 通过cas 设置到对象头
            synchronized (l){
           //t1 locked  t2 ctlock
                log.debug("name:"+Thread.currentThread().getName());
                //有锁  是一把偏向锁
                log.debug(ClassLayout.parseInstance(l).toPrintable());
            }
    
        }
    }
    
    

cas

  • 没有进入内核态

  • 只是一个CPU指令,不会unpark线程

账户余额测试

  • 并发执行时,造成余额不准

  • Sysnchronized锁住方法,可以实现并发

  • Atomic+cas(无锁机制):将余额定义为AtomicInteger

    • package BingFaBianCheng.bingFaBianCheng4.cas;
      
      import java.util.concurrent.atomic.AtomicInteger;
      
      /**
       * 取钱操作
       * cas
       * result :0
       *
       */
      public class AccountCas implements Account {
               
          public AccountCas(int balance){
               
              this.balance=new AtomicInteger(balance);
          }
      
          private AtomicInteger balance;
          @Override
          public Integer query() {
               
              return this.balance.get();
          }
      
          @Override
          public void acquire(Integer i) {
               
                  while(true) {
               
                      //t1 prev=80
                      int prev = balance.get();
                      //t1 next 80   t2 80
                      int next = prev - i;
                      //t2  balance=80
                      if(balance.compareAndSet(prev, next)) {
               
                          break;
                      }
                      // 上面那一堆等价于下面一条指令
                      //balance.getAndAdd(-1*i);
      
              }
      
          }
      }
      
      
    • cas方法保证操作的原子性,atomicInteger是volatile类型的,可以保证可见性

Sync(aqs的子类)是否是公平锁?

测试synchronized

  • package BingFaBianCheng.bingFaBianCheng4.test;
    
    /**
     * sync是否为公平锁
     */
    public class TestSysn {
           
        //定义一把锁
        private static Object lock = new Object();
    
        public static void main(String[] args) throws InterruptedException {
           
            //线程的数量
            int N = 10;
            Thread[] threads = new Thread[N];
    
    
            for(int i = 0; i < N; ++i){
           
                threads[i] = new Thread(new Runnable(){
           
                    public void run() {
           
                        /**
                         * 如果这里打印的结果是无序的则表示 非公平锁
                         * 有序则公平锁 --- 是否按照请求锁的顺序来拿锁
                         * 倒叙 为什么几乎上不可能研究
                         * 因为他存在一个队列  C++
                         */
                        synchronized(lock){
           //t0  1.6 mutext---t0 t1....t9 到一个队列当中的去阻塞(因为锁被main线程持有了)
                            System.out.println(Thread.currentThread().getName() + " get synch lock!");
                            try {
           
                                Thread.sleep(200);
                            } catch (InterruptedException e) {
           
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        }
                    }
    
                });
            }
    
            //main 线程可以得到锁 持有了锁
            synchronized(lock){
           
                for(int i = 0; i < N; ++i){
           
                    //t0
                    threads[i].start();
                    Thread.sleep(200);
                }
            }
    //
    //        for(int i = 0; i < N; ++i)
    //            threads[i].join();
    
    
        }
    }
    
    
  • 倒序打印

测试lock

  • package BingFaBianCheng.bingFaBianCheng4.test;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class TestLock {
           
    
        private static Lock lock = new ReentrantLock();
    
        public static void main(String[] args) throws InterruptedException {
           
    
            int N = 10;
            Thread[] threads = new Thread[N];
            for(int i = 0; i < N; ++i){
           
                threads[i] = new Thread(new Runnable(){
           
                    public void run() {
           
                        //synchronized ()
                        /**
                         *
                         * 独占锁---顾名思义
                         * t1------t9全部在这里阻塞
                         * 非公平锁也是拿不到锁---阻塞---进入队列---
                         * 线程
                         */
                        lock.lock();
                        System.out.println(Thread.currentThread().getName() + " lock!");
                        try {
           
                            Thread.sleep(20);
                        } catch (InterruptedException e) {
           
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        lock.unlock();
                    }
    
                });
            }
            //相当于synchronized ()
            lock.lock();
            for(int i = 0; i < N; ++i){
           
                threads[i].start();
                Thread.sleep(200);
            }
            lock.unlock();
    
            for(int i = 0; i < N; ++i)
                threads[i].join();
        }
    }
    
    
  • 正序打印

  • jdk1.8是公平锁,打印出的线程是倒序输出的

  • jdk1.6之后是模拟mutex实现的

ReemtrantLock默认是非公平锁,但是打印获取锁的线程顺序发现是有序的—(自然醒来,不会抢锁),这是为什么?

  • 独占锁 — 只能被一个线程持有

  • 默认是非公平锁,公平锁需要传入参数true,表示获取公平锁,不传或者传false皆为非公平锁。

  • 1.所谓的公平锁和非公平锁他们首先会在加锁的时候去抢锁如果加锁失败。

    2.他进入队列(线程还没有睡眠),这个时候不死心还会进行自旋再去获取锁

    3.如果失败就睡眠

  • 第一次加锁的时候 ,他不会去尝试加锁,他回去看一下我前面有没有人排队,如果有人排队,我则进入队列(并不等于排队)(线程没有睡眠),然后还不死心,再次看一下我有没有拿锁的资格,如果有继续拿,拿不到则睡眠(排队)

  • 上面问题的答案是:一朝排队,永远排队,公平锁和非公平锁最大的区别就是一开始加锁的时候,非公平锁如果可以获取到锁,即使有其他线程在排队,也不会入队,而是直接获取锁成功,而公平锁不会直接获取锁,如果有其他线程在排队,会插入到队列的最末尾,等待被它的前置节点来唤醒

  • 所以是顺序打印的结果

java怎么实现队列

查看ReentrantLock的源码

非公平锁

  • 上来就cas直接抢锁,不管前面有没有人排队(公平与不公平的关键),因为此处有一定的概率抢到锁
  • 然后再去尝试获取锁,此时也不会去排队

公平锁

  • 获取锁的时候,就会执行hasQueuedPredecessors()方法,判断是否有人排队

  • public final boolean hasQueuedPredecessors() {
           
            // The correctness of this depends on head being initialized
            // before tail and on head.next being accurate if the current
            // thread is first in queue.
            Node t = tail; // Read fields in reverse initialization order
            Node h = head;
            Node s;
            return h != t &&
                ((s = h.next) == null || s.thread != Thread.currentThread());
        }
    
    • h!=t有两种情况,h和t都是null,h和t相等,但是不为null,此时只有一个节点
  • ReentrantLock第一次加锁,t1来加锁,队列都没有初始化,不需要排队,此时获取锁的效率最高

  • t2来加锁

    • 如果t1和t2是交替执行,那么队列永远没有初始化,锁的获取永远都是最高效的状态

    • 如果t1没有释放锁,发生了资源竞争,会初始化队列,队列中只有一个节点,所以也不需要排队,会去获取锁,如果获取锁失败,然后再入队,

  • t3来加锁,获取锁失败,直接进入addWaiter()方法,

    • private Node addWaiter(Node mode) {
               
          Node node = new Node(Thread.currentThread(), mode);
          // Try the fast path of enq; backup to full enq on failure
          Node pred = tail;
          if (pred != null) {
               
              node.prev = pred;
              if (compareAndSetTail(pred, node)) {
               
                  pred.next = node;
                  return node;
              }
          }
          enq(node);
          return node;
      }
      
    • 如果队列没有初始化的话,又会进入enq()方法

    • private Node enq(final Node node) {
               
          for (;;) {
               
              Node t = tail;
              if (t == null) {
                // Must initialize
                  if (compareAndSetHead(new Node()))
                      tail = head;
              } else {
               
                  node.prev = t;
                  if (compareAndSetTail(t, node)) {
               
                      t.next = node;
                      return t;
                  }
              }
          }
      }
      

一些遗留问题

  • aqs队列里面为什么要用一个空node?

你可能感兴趣的:(并发编程,多线程)