java中锁的由来
为什么使用锁

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

现象。

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

public  staticvoidmain(String[] args) {
        final CountBean countBean=new CountBean();
        final CountDownLatch countDownLatch=new CountDownLatch(10000);
        for(int i=0;i<10000;i++){
            new Thread(new Runnable() {
@Override
                public void run() {
// TODO Auto-generated method stub
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() {
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);
                    return true;
                }
            }
            else  if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            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);
        final Map map = new HashMap();
        for (int i = 0; i < 100; i++){
            new Thread(new Runnable() {
@Override
                public void 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
                public void 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();
           
    }