java 锁的介绍

来源:互联网 发布:中国报学史读后感知乎 编辑:程序博客网 时间:2024/06/06 01:09

java中锁的由来

为什么使用锁

多线程对同一资源进行操作时会引发线程不安全,合理的使用锁可以避免线程不安全

现象。

如下代码就会引起线程不安全现象

public staticvoidmain(String[] args) {

        final CountBean countBean=new CountBean();

        final CountDownLatchcountDownLatch=new CountDownLatch(10000);

        for(int i=0;i<10000;i++){

            new Thread(new Runnable() {

                @Override

                publicvoid run() {

                    // TODO Auto-generated method stub

                    countBean.i++;

                    try {

                        countDownLatch.countDown();

                    }catch(Exception e) {

                        // TODO Auto-generated catch block

                        e.printStackTrace();

                    }

                }

            }).start();

        }

        try {

            countDownLatch.await();

        }catch(InterruptedException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

        System.out.println(countBean.i);

    }

   如上代码如果线程安全的话结果应该是10000

实际打印出9987(注这个值时不固定的只能说明多线程对 共享资源countBean的i进行操作时引发了不安全现象)

java中产生线程不安全的原因

   java 内存模型规定所有变量都存储在主内存中(main memory),每条线程还有自己的工作内存(working memory)

    主内存和工作内存具体的交互协议,java内存模型规定以下8中操作来完成

1.lock(锁定):作用与主内存变量,把一个变量标志为一条线程独占状态

2.unlock(解锁):作用与主内存变量,他把一个处于锁定状态的变量释放出来,释放后变量才可以被其他线程锁定

3.read(读取):作用与主内存变量,他把一个变量从主内存传输到工作线程中,以便随后的load动作使用

4.load(载入):作用于工作内存变量,它把read操作从主内存得到的变量值放入到工作内存的变量副本中

5.use(使用):作用于工作内存的变量,他把工作内存的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量值的字节码指令时,将会执行这个动作

6.assign(赋值):作用于工作内存的变量,他把一个从执行引擎接收到的值赋给工作内存变量,

每当虚拟机遇到一个给变量赋值的字节码指令时执行这个动作

7.store(存储):作用于工作内存的变量,他把工作内存中的一个变量的值传送到主内存中,

以便随后的write操作使用

8.write(写入):作用于主内存变量,它把store操作从工作内存中得到的变量值放入主内存变量中

   如上,因为java的线程是抢占式的,线程可以在任何地方失去cpu的执行权,

如果load动作之后write动作未完成,有其他线程read变量可能就不是最新的值引发线程不安全现象

                                                                   

synchronized 关键字介绍

java中每个对象都有一把锁,如果方法或者块获得了对象的锁后,其他的需要获得锁的对象只能等待直到获取锁的方法释放了锁。

synchronized 修饰非static普通方法时表示锁的是当前对象

synchronized 修饰static普通方法时表示锁的是类对象

synchronized 修饰方法块时需要指定锁的对象

java会在synchronized 块前后加上monitorenter 和monitorexit两条指令

ReentrantLock 介绍及源码分析

ReentrantLock是基于代码实现的锁,我们通过源码分析下实现原理

AQS(AbstractQueuedSynchronizer)框架介绍

 

主要结构如上(其实翻译过来就是同步抽象队列):

 

ReentrantLock源码分析

     ReentrantLock是用纯代码实现的锁,

     ReentrantLock实现了lock接口,使用组合方式注入NonfairSync对象,NonfairSync继承与AbstractQueuedSynchronizer

    ReentrantLock构造方法

    //初始化一个抽象同步队列

 public ReentrantLock(){

        sync = new NonfairSync();

    }

ReentrantLock的lock方法

public voidlock() {

    //直接调用NonfairSync的lock方法 

     sync.lock();

    }

//NonfairSync的lock方法

 finalvoidlock() {

          //利用cpu的cas原理(即cpu原语修改AbstractQueuedSynchronizer的线程状态变量,如果修改成功则表示获取锁)

            if (compareAndSetState(0, 1))

               //设置当前线层为锁的拥有者

                setExclusiveOwnerThread(Thread.currentThread());

            else

               //如果没有获取锁则执行acquire

                acquire(1);

       }

//看下acquire(1)的代码是在AbstractQueuedSynchronizer实现的

public finalvoidacquire(intarg) {

        //尝试获取锁

        if (!tryAcquire(arg) &&

        //获取锁失败则将获取锁的线程放入AbstractQueuedSynchronizer的队列里

            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

            selfInterrupt();

    }

   

  protectedfinalbooleantryAcquire(intacquires) {

            return nonfairTryAcquire(acquires);

       }

//如果状态为0则证明锁以被释放继续尝试获取锁,如果获取锁的是当前线程则状态+1,这个就是可重入锁的说明

  finalbooleannonfairTryAcquire(int acquires) {

            final Thread current = Thread.currentThread();

            int c = getState();

            if (c == 0) {

                if (compareAndSetState(0, acquires)) {

                   setExclusiveOwnerThread(current);

                    returntrue;

                }

            }

            else if (current == getExclusiveOwnerThread()) {

                int nextc = c + acquires;

                if (nextc < 0)// overflow

                    thrownew Error("Maximum lock count exceeded");

                setState(nextc);

                returntrue;

            }

            return false;

       }

// acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

//将获取锁的对象封装成node对象

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq onfailure
        Node pred = tail;

      //先快速插入队列
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }

      //如果快速插入队列失败则死循环的插入队列尾部
        enq(node);
        return node;
    }

 acquireQueued()方法,

//

 finalboolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p =node.predecessor();
                if (p == head&& tryAcquire(arg)) {
                   setHead(node);
                    p.next =null; // help GC
                    failed =false;
                    returninterrupted;
                }
                if(shouldParkAfterFailedAcquire(p, node) &&
                   parkAndCheckInterrupt())
                   interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

这是个死循环 如果p == head && tryAcquire(arg) 不满足则会调用parkAndCheckInterrupt阻塞线程

最终调用的是unsafe.park(false,0L),阻塞线程这是个native方法这里不解释了

 

 

最后我们看下unlock函数怎样释放锁的

 public void unlock() {
        sync.release(1);
    }

 publicfinal boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus!= 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

 privatevoid unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possiblyneeding signal) try
         * to clear in anticipation of signalling. It is OK if this
         * fails or if status is changed by waitingthread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);


        /*
         * Thread to unpark is held in successor,which is normally
         * just the next node.  But if cancelledor apparently null,
         * traverse backwards from tail to find theactual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null&& t != node; t = t.prev)
                if (t.waitStatus <=0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

主要是修改syn的状态变量队列的移除,唤醒其他线程

读写锁简介

     java读写锁适用于读多写少的场景,相对一把锁而言,读写锁在读多写少的情况下大幅度提高性能

注意场景:同一线程在持有读锁的情况下不能调用写锁的lock方法,会造成死锁。

持有写锁后可以调用读锁的lock方法,当调用写锁的unlock后写锁降级为读锁

以下是个简单的demo

package com.mq.test.readwritelock;

 

import java.util.HashMap;

import java.util.Map;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.locks.ReentrantReadWriteLock;

import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;

import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

 

public classReadwritelockTest {

    private static ReentrantReadWriteLock lock =newReentrantReadWriteLock();

    private static WriteLock writelock =lock.writeLock();

    private static ReadLock readlock =lock.readLock();

 

    public static void main(String[] args) {

        final CountDownLatchcountDownLatch =newCountDownLatch(100);

        finalMap map = new HashMap();

        for (int i = 0; i < 100; i++){

            new Thread(new Runnable() {

                @Override

                publicvoid run() {

                    try {

                        writelock.lock();

                        map.put("1",Thread.currentThread().getId());

                        System.out.println("我获取了写锁");

                        countDownLatch.countDown();

                    }catch(Exception e) {

                        // TODO: handle exception

                    }finally{

                        writelock.unlock();

                    }

 

                }

            }).start();

        }

        for (int i = 0; i < 100; i++){

            new Thread(new Runnable() {

                @Override

                publicvoid run() {

                    try {

                        readlock.lock();

                        map.put("1",Thread.currentThread().getId());

                        System.out.println("我获取了读锁");

                        countDownLatch.countDown();

                    }catch(Exception e) {

                        // TODO: handle exception

                    }finally{

                        readlock.unlock();

                    }

 

                }

            }).start();

        }

 

    }

 

}

自旋锁简介

   由于锁的阻塞,1.需要挂起当前线程(即保留当前线程的线程状态),2.回复执行线程的现场

这些操作都要转入内核状态中完成,给高并发系统带来很大的性能开销。同时虚拟机团队注意到在许多应用中,共享数据的锁定状态很短,为了这段时间挂起和恢复线程并不值得,如果物理机上是多核的,可以让线程忙循坏下(即不干任何事的死循环)就不用挂起和恢复线程了。这项技术称为自旋锁(jdk1.6以后默认开启),当然阻塞时间长了白白浪费了很多cpu也是不值得的,虚拟机默认自旋10(可以修改)如果还没有获得锁则转为阻塞状态。

 

偏向锁简介

  偏向锁的偏就是偏心的偏,偏袒的偏。偏向锁是为了消除同步操作(locking,unlock,mark word,update),

   当开启了偏向锁后,当锁对象第一次被线程获取的时候,虚拟机会把对象头中的标志位设置为“01",即偏向模式,利用cas将线程id记录到对象的mark  work中,如果cas成功,则持有偏向锁的线程每次进入同步快时就不用进行任何同步操作了

   当然如果期间有其他线程尝试获取锁,则偏向模式宣告结束

 

 

死锁简介

   死锁是假设有两个线程A,B,有两个对象obj1,obj2

如果A线程获取obj1的锁后,又去申请obj2的锁

而B线程获取obj2的锁后,又去申请obj1的锁

两边都在等待对方锁的释放,又都没释放出来

demo

public staticvoidmain(String[] args) {

           final Object obj1=new Object();

           final Object obj2=new Object();

           new Thread(new Runnable() {

            @Override

            public void run() {

                synchronized(obj1){

                    try {

                        System.out.println(Thread.currentThread().getName()+"我获得了obj1的锁");

                        Thread.sleep(100);

                        synchronized (obj2) {

                            System.out.println(Thread.currentThread().getName()+"我获得了obj2的锁");

                        }

                    }catch(Exception e) {

                        // TODO Auto-generated catch block

                        e.printStackTrace();

                    }

                   

                }

               

            }

        }).start();

           new Thread(new Runnable() {

           @Override

           public void run() {

               synchronized(obj2){

                   try {

                       System.out.println(Thread.currentThread().getName()+"我获得了obj2的锁");

                       Thread.sleep(100);

                       synchronized (obj1) {

                           System.out.println(Thread.currentThread().getName()+"我获得了obj1的锁");

                       }

                   } catch (Exception e) {

                       // TODO Auto-generated catch block

                       e.printStackTrace();

                   }

                  

               }

              

           }

       }).start();

          

    }

 

 

原创粉丝点击