synchronize是java最原始的同步关键字,经过对方法或者代码块进行加锁实现对临界区域的保护.线程每次进去同步方法或者代码块都须要申请锁,若是锁被占用则会等待锁的释放,值得注意的是,等待锁的线程不会响应中断.synchronize的锁分为对象所和类锁,当synchronize修饰静态方法或者synchronize(Object.class)这样写时是类锁,当synchronize修饰普通方法或者synchronize(this)这样写时是对象锁(this能够替换成其余对象的引用).synchronize是官方推荐使用的同步工具,synchronize主要是在JVM层面实现的同步,官方已经对synchronize的性能进行了屡次优化,有兴趣能够自行百度.html
1 package main; 2 3 public class Service implements Runnable { 4 5 @Override 6 public void run() { 7 8 synchronized (this) { 9 System.out.println(Thread.currentThread().getName() + "进入了代码块"); 10 try { 11 Thread.sleep(1000); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 System.out.println(Thread.currentThread().getName() + "准备退出代码块"); 16 } 17 } 18 19 public static void main(String[] args) { 20 Service service=new Service(); 21 new Thread(service).start(); 22 new Thread(service).start(); 23 } 24 }
输出结果:java
Thread-0进入了代码块 Thread-0准备退出代码块 Thread-1进入了代码块 Thread-1准备退出代码块
synchronize的基本使用如上,其在使用和理解上很是容易理解这里不作多余解释.咱们在使用synchronize要注意同步代码范围不能太大,而且耗时操做最好不要在同步中进行,这样会极大程度的影响程序效率.node
Lock一样是实现同步的工具,可是他的实现与synchronize有本质上的差异,synchronize基于JVM的同步,Lock是一个接口,他是基于AQS的实现,底层是使用CAS和volatile变量结合实现.一样在进入临界区以前须要申请锁,退出临界区域须要手动释放锁,Lock主要实现类是ReetrantLock.ide
1 public class Service implements Runnable { 2 3 private Lock lock=new ReentrantLock(); 4 5 @Override 6 public void run() { 7 lock.lock(); 8 System.out.println("进入临界资源"); 9 try { 10 Thread.sleep(1000); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 System.out.println("准备提出临界资源"); 15 lock.unlock(); 16 } 17 18 public static void main(String[] args) { 19 Service service=new Service(); 20 new Thread(service).start(); 21 new Thread(service).start(); 22 } 23 }
输出结果函数
进入临界资源
准备提出临界资源
进入临界资源
准备提出临界资源
以上代码使用的是ReetrantLock,他默认是使用非公平锁,要使用公平锁就给构造函数传一个true.上面的代码先调用lock方法获取锁,若是获取到锁则进入到临界资源区没有获取到则阻塞,操做完后调用unlock方法释放锁并唤醒在锁上等待的线程.工具
Lock中获取锁的主要方法有lock(),lockInterruptibly(),tryLock().性能
lock()方法不会响应中断,lockInterruptibly()会响应中断,tryLock()方法是尝试获取锁,若是没获取到则返回false,不然true.优化
在ReentrantLock类中有一个Sync内部类,他继承自AbstractQueuedSynchronizer(即AQS,介绍看这里).Sync子类就是公平锁(FairSync)和非公平锁(NonfairSync),这两个类也是ReentrantLock的内部类.ui
首先来看非公平锁,非公平锁是指后来线程具备极大的几率得到锁,来看看他的代码实现.this
1 static final class NonfairSync extends Sync { 2 private static final long serialVersionUID = 7316153563782823691L; 3 4 final void lock() { 5 if (compareAndSetState(0, 1)) 6 setExclusiveOwnerThread(Thread.currentThread());//将当前线程设置为锁的拥有者 7 else 8 acquire(1); 9 } 10 11 protected final boolean tryAcquire(int acquires) { 12 return nonfairTryAcquire(acquires); 13 } 14 }
lock()就是ReentrantLock类中的lock()方法具体调用的方法,compareAndSetState方法是一个CAS操做,它是AQS中的方法,它的功能是判断锁的状态是否为0,若是为0则为1返回true,不然返回false.
acquire()方法是AQS的方法,他的功能是尝试获取锁,下面是该方法的代码
1 public final void acquire(int arg) { 2 if (!tryAcquire(arg) && //尝试获取锁 3 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //将当前线程放入等待队列 4 selfInterrupt(); //中断本身 5 }
该方法首先会调用tryAcquire()方法,这个方法就是NonfairSync类中的tryAcquire().下面是nonfairTryAcquire方法代码
1 final boolean nonfairTryAcquire(int acquires) { 2 final Thread current = Thread.currentThread(); 3 int c = getState(); 4 if (c == 0) { //若是锁未被占用 5 if (compareAndSetState(0, acquires)) { //CAS操做获取锁 6 setExclusiveOwnerThread(current); 7 return true; 8 } 9 } 10 else if (current == getExclusiveOwnerThread()) { //若是锁被占用且申请锁的是锁的拥有线程 11 int nextc = c + acquires; 12 if (nextc < 0) // overflow 13 throw new Error("Maximum lock count exceeded"); 14 setState(nextc);//改变锁状态值 15 return true; 16 } 17 return false; 18 }
从上面代码逻辑来看,非公平锁是具备可重入性,若是获取锁失败就返回false,不然返回true.咱们在返回来看acquire()方法中的acquireQueued(addWaiter(Node.EXCLUSIVE), arg)这一句,addWaiter(Node.EXCLUSIVE)方法是将当前线程放入等待队列中,acquireQueued()方法再次尝试获取锁,若是再次获取失败,则将当前线程阻塞,代码以下
final boolean 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; return interrupted;//返回中断标记 } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node);//取消当前线程 } }
其中shouldParkAfterFailedAcquire(p, node)这个方法,是在再次获取锁失败以后的处理,因为等待队列中可能会有线程被取消,因此当前线程要去寻找本身前面的节点,直到找到一个没有被取消的线程为止,这样可以保证本身可以被唤醒.
parkAndCheckInterrupt()是将当前线程阻塞的方法,他调用了Unsafe类的本地方法.
以上就是非公平锁获取锁的过程,值得注意的是,在线程阻塞阶段,是不会响应中断的,代码中的响应中断是线程被唤醒以后才响应,响应手段是经过执行selfInterrupt()方法,该方法就是调用了Thread类的interrupt()方法.也就是说,只有会响应中断的方法才会被中断,以第4节的代码为例,线程被中断的话,依然会输出两句话,只是线程不会睡眠而已.
接下来是锁资源的释放,AQS中已经实现好了锁资源释放方法release(),可是tryRelease()方法没有实现,一下是ReetrantLock类的实现
1 protected final boolean tryRelease(int releases) { 2 int c = getState() - releases; 3 if (Thread.currentThread() != getExclusiveOwnerThread()) //是否为当前线程 4 throw new IllegalMonitorStateException(); 5 boolean free = false; 6 if (c == 0) { //若是状态变为0即锁变为未被拥有 7 free = true; 8 setExclusiveOwnerThread(null); 9 } 10 setState(c); 11 return free; 12 }
接下来是release()方法
1 public final boolean release(int arg) { 2 if (tryRelease(arg)) {//尝试释放资源 3 Node h = head; 4 if (h != null && h.waitStatus != 0) 5 unparkSuccessor(h);//唤醒线程 6 return true; 7 } 8 return false; 9 }
unparkSuccessor()代码以下
private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) //若是线程状态为小于0,表示有效状态,大于0表示取消状态 compareAndSetWaitStatus(node, ws, 0); 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);//唤醒线程 }
这里会唤醒后继节点,若是后继节点为null或者被取消,则从队尾开始向前回溯,为何从队尾开始?博主也没法理解.
以上即是非公平锁的实现原理,非公平锁在获取锁时若是有新线程进来,那么新线程有很大可能性会获取到锁资源,由于等待队列中的线程被唤醒到从新请求锁会消耗至关大的时间,公平锁就可以解决这个问题.
公平锁与非公平锁的惟一区别在于,公平锁的tryAcquire()方法与非公平锁不一样,看代码
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && //惟一不一样点 compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
hasQueuedPredecessors()方法是用于判断等待队列是否存在,若是等待队列中有节点,那么等待队列确定存在,那么线程就不能直接获取锁资源,必须去排队,如下是源码
1 public final boolean hasQueuedPredecessors() { 2 3 Node t = tail; // Read fields in reverse initialization order 4 Node h = head; 5 Node s; 6 return h != t && 7 ((s = h.next) == null || s.thread != Thread.currentThread()); 8 }
以上是公平锁与非公平锁的实现.
咱们如今有个问题是:一个线程要进行下去,就必须一个条件知足.咱们这里有两种有两种实现.
1:无限循环,一直循环访问条件,这样显然是极大程度浪费CPU资源
2:使用wait()方法,当条件知足时被其余线程唤醒,这个方法是经常使用方法,可是这种方法依赖于synchronize
condition就是用来解决上面这个问题的.看代码
1 public class ServiceCondition implements Runnable { 2 3 private static Lock lock = new ReentrantLock(); 4 private static boolean flag = false; 5 private static Condition condition = lock.newCondition(); 6 7 @Override 8 public void run() { 9 try { 10 lock.lock(); 11 while (!flag) { 12 System.out.println("条件为假,等待"); 13 condition.await(); 14 } 15 System.out.println("条件为真,执行"); 16 lock.unlock(); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 } 21 22 public static void main(String[] args) { 23 ServiceCondition serviceCondition=new ServiceCondition(); 24 new Thread(serviceCondition).start(); 25 try { 26 Thread.sleep(1000); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 lock.lock(); 31 flag=true; 32 condition.signal(); 33 lock.unlock(); 34 } 35 }
condition.await()至关于wait()方法,singal()至关于notify()方法.必须获取到锁才能调用这两个方法,缘由是调用await()方法时,会释放锁资源,要释放必须先要得到;调用signal()方法时会判断锁的拥有者是不是当前线程,若是是才会容许调用,这两个方法在未获取到锁时调用会抛出IllegalMonitorStateException异常.
synchronize具备可重入性,当一个线程获取到锁时,锁会将当前线程设置为拥有线程,而且状态值加1表示该锁被获取了1次,当该线程再次获取同一个锁对象时,锁会判断线程是否为拥有线程,若是是则容许获取,而且状态加1,不然拒绝获取,释放时必须一层一层释放资源,直到状态值为0,表示该锁被彻底释放.
Lock与synchronize同理,咱们从上面的代码就能够看出来,Lock在获取锁资源时都会判断是否为锁拥有线程.