多线程——锁(lock)_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > 多线程——锁(lock)

多线程——锁(lock)

 2011/12/14 10:07:59  ciding  http://ciding.iteye.com  我要评论(0)
  • 摘要:上一讲《多线程——同步(synchronized)下》多线程——锁(lock)上首先提出一个问题,synchronized与lock有哪些异同?是性能、适应范围、切入点?还是使用时的复杂度呢?先了解一下java.util.concurrent.locks.Lock接口的实现类:ReentrantLock与ReentrantReadWriteLock的内部类中的ReadLock与WriteLock;分别叫重入锁,读入锁,写入锁。而本节中主要讲ReentrantLock的使用与特性
  • 标签:多线程 线程

上一讲《线程——同步(synchronized)下

?

多线程——锁(lock)上

?

首先提出一个问题,synchronized与lock有哪些异同?是性能、适应范围、切入点?还是使用时的复杂度呢?

?

先了解一下java.util.concurrent.locks.Lock接口的实现类:ReentrantLock与ReentrantReadWriteLock的内部类中的ReadLock与WriteLock;分别叫重入锁,读入锁,写入锁。

?

而本节中主要讲ReentrantLock的使用与特性。

?

在ReentrantLock中有这样一段描述:

一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。 (后面的先略过,需要的自己查看API。)

?

功能更强大??编写思路:先通过一个例子看看实际效果,再分析ReentrantLock的lock()方法的实现方式,再比较synchronized的异同,最后得出两者的适用范围。

?

例子:

package thread_test; import java.util.concurrent.locks.ReentrantLock; public class LockThread { public static void main(String[] args) { Long t_start = System.currentTimeMillis(); User_lock u = new User_lock("张三", 100); Thread_lock t1 = new Thread_lock("线程A", u, 20); Thread_lock t2 = new Thread_lock("线程B", u, -60); Thread_lock t3 = new Thread_lock("线程C", u, -80); Thread_lock t4 = new Thread_lock("线程D", u, -30); Thread_lock t5 = new Thread_lock("线程E", u, 100); Thread_lock t6 = new Thread_lock("线程F", u, 50); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); /** * 以下代码用于计算时间,当然,它本身的运行也会需要一点点时间,但与分析运行效率无影响 */ boolean flag = true; while (flag) { if (Thread_lock.activeCount() == 1) { Long t_end = System.currentTimeMillis(); System.out.println("消靠时间:" + (t_end - t_start)); flag = false; } } } } class Thread_lock extends Thread { private User_lock u; private int y = 0; Thread_lock(String name, User_lock u, int y) { super(name); //线程的名称 this.u = u; this.y = y; } public void run() { u.oper(y); } } class User_lock { private String code; private Integer cash; private ReentrantLock myLock1 = new ReentrantLock(); private ReentrantLock myLock2 = new ReentrantLock(); User_lock(String code, int cash) { this.code = code; this.cash = cash; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } /** * 业务方法 * * @param x * 添加x万元 */ public synchronized void oper(int x) { try { Thread.sleep(1000);// 作用:增加运行时间 this.cash += x; System.out.println(Thread.currentThread().getName() + " 运行结束1,增加“" + x + "”,当前用户信息:" + toString()); } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep(1000);// 作用:增加运行时间 this.code = "张三(2)"; } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return "User{" + "code='" + code + '\'' + ", cash=" + cash + '}'; } }


?

运行结果: 线程A? 运行结束1,增加“20”,当前用户信息:User{code='张三', cash=120}
线程F? 运行结束1,增加“50”,当前用户信息:User{code='张三(2)', cash=170}
线程E? 运行结束1,增加“100”,当前用户信息:User{code='张三(2)', cash=270}
线程D? 运行结束1,增加“-30”,当前用户信息:User{code='张三(2)', cash=240}
线程C? 运行结束1,增加“-80”,当前用户信息:User{code='张三(2)', cash=160}
线程B? 运行结束1,增加“-60”,当前用户信息:User{code='张三(2)', cash=100}
消靠时间:12016

?

例2:

package thread_test; import java.util.concurrent.locks.ReentrantLock; public class LockThread { public static void main(String[] args) { Long t_start = System.currentTimeMillis(); User_lock u = new User_lock("张三", 100); Thread_lock t1 = new Thread_lock("线程A", u, 20); Thread_lock t2 = new Thread_lock("线程B", u, -60); Thread_lock t3 = new Thread_lock("线程C", u, -80); Thread_lock t4 = new Thread_lock("线程D", u, -30); Thread_lock t5 = new Thread_lock("线程E", u, 100); Thread_lock t6 = new Thread_lock("线程F", u, 50); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); /** * 以下代码用于计算时间,当然,它本身的运行也会需要一点点时间,但与分析运行效率无影响 */ boolean flag = true; while (flag) { if (Thread_lock.activeCount() == 1) { Long t_end = System.currentTimeMillis(); System.out.println("消靠时间:" + (t_end - t_start)); flag = false; } } } } class Thread_lock extends Thread { private User_lock u; private int y = 0; Thread_lock(String name, User_lock u, int y) { super(name); //线程的名称 this.u = u; this.y = y; } public void run() { u.oper(y); } } class User_lock { private String code; private Integer cash; private ReentrantLock myLock1 = new ReentrantLock(); private ReentrantLock myLock2 = new ReentrantLock(); User_lock(String code, int cash) { this.code = code; this.cash = cash; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } /** * 业务方法 * * @param x * 添加x万元 */ public void oper(int x) { synchronized (cash) { try { Thread.sleep(1000);// 作用:增加运行时间 this.cash += x; System.out.println(Thread.currentThread().getName() + " 运行结束1,增加“" + x + "”,当前用户信息:" + toString()); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (code) { try { Thread.sleep(1000);// 作用:增加运行时间 this.code = "张三(2)"; } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public String toString() { return "User{" + "code='" + code + '\'' + ", cash=" + cash + '}'; } }


?

运行结果: 写道 线程A? 运行结束1,增加“20”,当前用户信息:User{code='张三', cash=120}
线程F? 运行结束1,增加“50”,当前用户信息:User{code='张三', cash=170}
线程E? 运行结束1,增加“100”,当前用户信息:User{code='张三(2)', cash=270}
线程D? 运行结束1,增加“-30”,当前用户信息:User{code='张三(2)', cash=240}
线程C? 运行结束1,增加“-80”,当前用户信息:User{code='张三(2)', cash=160}
线程B? 运行结束1,增加“-60”,当前用户信息:User{code='张三(2)', cash=100}
消靠时间:7031

??

?例3:

?

package thread_test; import java.util.concurrent.locks.ReentrantLock; public class LockThread { public static void main(String[] args) { Long t_start = System.currentTimeMillis(); User_lock u = new User_lock("张三", 100); Thread_lock t1 = new Thread_lock("线程A", u, 20); Thread_lock t2 = new Thread_lock("线程B", u, -60); Thread_lock t3 = new Thread_lock("线程C", u, -80); Thread_lock t4 = new Thread_lock("线程D", u, -30); Thread_lock t5 = new Thread_lock("线程E", u, 100); Thread_lock t6 = new Thread_lock("线程F", u, 50); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); /** * 以下代码用于计算时间,当然,它本身的运行也会需要一点点时间,但与分析运行效率无影响 */ boolean flag = true; while (flag) { if (Thread_lock.activeCount() == 1) { Long t_end = System.currentTimeMillis(); System.out.println("当前时间:" + (t_end - t_start)); flag = false; } } } } class Thread_lock extends Thread { private User_lock u; private int y = 0; Thread_lock(String name, User_lock u, int y) { super(name); // 线程的名称 this.u = u; this.y = y; } public void run() { u.oper(y); } } class User_lock { private String code; private Integer cash; private ReentrantLock myLock1 = new ReentrantLock(); private ReentrantLock myLock2 = new ReentrantLock(); User_lock(String code, int cash) { this.code = code; this.cash = cash; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } /** * 业务方法 * * @param x * 添加x万元 */ public void oper(int x) { myLock1.lock(); try { Thread.sleep(10);// 作用:增加运行时间 this.cash += x; System.out.println(Thread.currentThread().getName() + " 运行结束1,增加“" + x + "”,当前用户信息:" + toString()); } catch (InterruptedException e) { e.printStackTrace(); } finally { myLock1.unlock(); } myLock2.lock(); try { Thread.sleep(10);// 作用:增加运行时间 this.code = "张三(2)"; } catch (InterruptedException e) { e.printStackTrace(); } finally { myLock2.unlock(); } } @Override public String toString() { return "User{" + "code='" + code + '\'' + ", cash=" + cash + '}'; } }


?

?

运行结果: 线程A 运行结束1,增加“20”,当前用户信息:User{code='张三', cash=120}
线程B 运行结束1,增加“-60”,当前用户信息:User{code='张三', cash=60}
线程C 运行结束1,增加“-80”,当前用户信息:User{code='张三(2)', cash=-20}
线程D 运行结束1,增加“-30”,当前用户信息:User{code='张三(2)', cash=-50}
线程E 运行结束1,增加“100”,当前用户信息:User{code='张三(2)', cash=50}
线程F 运行结束1,增加“50”,当前用户信息:User{code='张三(2)', cash=100}
当前时间:7203

?

?

看上面的时间可以了解一下synchronized与lock的使用在性能上的差别。

单纯的看上面的测试结果:synchronized块是最快的。其实这是因为测试的数据量比较少,线程运行的时间很短。

?

当运行的线程数以千计的时候,那时对于CPU的上下文切换和调度所消靠的时间就会表现的比较明显。基本上,使用lock会比synchronized块的运行会快一点点,比synchronized方法就快很多了。

?

按照最开始给的思路,现在应该看看lock()方法的实现了。

?

/** * Acquires the lock. * * <p>Acquires the lock if it is not held by another thread and returns * immediately, setting the lock hold count to one. * * <p>If the current thread already holds the lock then the hold * count is incremented by one and the method returns immediately. * * <p>If the lock is held by another thread then the * current thread becomes disabled for thread scheduling * purposes and lies dormant until the lock has been acquired, * at which time the lock hold count is set to one. */ public void lock() { sync.lock(); }


?直接是通过调用sync的lock()方法。转到sync,发现这个抽象类的lock()方法有两个实现类,分别是NonfairSync与FairSync,刚好对应的一个是非公平锁,一个是公平锁。

?

非公平锁的代码:加点中文注释

/** * Sync object for non-fair locks */ final static class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { //表示如果当前state=0,那么设置state=1,并返回true;否则返回false。由于未等待,所以线程不需加入到等待队列 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }


?

接下来代码所调用的我只写方法,

acquire(1)->acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 其中addWaiter负责增加节点

增加完成后selfInterrupt(),设置中断。->tryAcquire(arg)来判断->nonfairTryAcquire(arg)最后回到这个关键方法。

?

/** * Performs non-fair tryLock. tryAcquire is * implemented in subclasses, but both need nonfair * try for trylock method. */ final boolean nonfairTryAcquire(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; }


?

?(1)如果锁状态空闲(state=0),且通过原子的比较并设置操作,那么当前线程获得锁,并把当前线程设置为锁拥有者;
?(2)如果锁状态空闲,且原子的比较并设置操作失败,那么返回false,说明尝试获得锁失败;
?(3)否则,检查当前线程与锁拥有者线程是否相等(表示一个线程已经获得该锁,再次要求该锁,这种情况叫可重入锁),如果相等,维护锁状态,并返回true;
?(4)如果不是以上情况,说明锁已经被其他的线程持有,直接返回false;

到此,核心代码就分析完了。

引用一段别人的总结:

?

zhangxl 写道 ReentrantLock在采用非公平锁构造时,首先检查锁状态,如果锁可用,直接通过CAS设置成持有状态,且把当前线程设置为锁的拥有者。
如果当前锁已经被持有,那么接下来进行可重入检查,如果可重入,需要为锁状态加上请求数。如果不属于上面两种情况,那么说明锁是被其他线程持有,
当前线程应该放入等待队列。
在放入等待队列的过程中,首先要检查队列是否为空队列,如果为空队列,需要创建虚拟的头节点,然后把对当前线程封装的节点加入到队列尾部。由于设置尾部节点采用了CAS,为了保证尾节点能够设置成功,这里采用了无限循环的方式,直到设置成功为止。
在完成放入等待队列任务后,则需要维护节点的状态,以及及时清除处于Cancel状态的节点,以帮助垃圾收集器及时回收。如果当前节点之前的节点的等待状态小于1,说明当前节点之前的线程处于等待状态(挂起),那么当前节点的线程也应处于等待状态(挂起)。挂起的工作是由LockSupport类支持的,LockSupport通过JNI调用本地操作系统来完成挂起的任务(java中除了废弃的suspend等方法,没有其他的挂起操作)。
在当前等待的线程,被唤起后,检查中断状态,如果处于中断状态,那么需要中断当前线程。

?

?

先到这里,需要写的东西太多,突然有点下不了手的感觉

ReentrantLock对synchronized的性能改进:

ReentrantLock是通过实现一个接口Lock,把锁定做为了一java类来实现,而不是做为语言的特性;从而可能实现不同的调度算法、性能特性等。而这里,ReentrantLock拥有synchronized相同的并发性与内存语义,另外,又添加了类似锁投票,定时锁等候,可中断锁等候的一些特性。

ReentrantLock对synchronized的可伸缩改进:

在非公平锁中,CPU的处理时间中,只有很少的时间花在线程调度上,大多都用在实际工作上。

?

ReentrantLock与synchronized在条件变量上的比较:
Lock框架包含了对waitnotify的概括,这个概括叫作条件(Condition)Lock对象则充当绑定到这个锁的条件变量的工厂对象,与标准的waitnotify方法不同对于指定的Lock可以有不止一个条件变量与它关联。这样就简化了许多并发算法的开发。

?怕越写越深,到我都控制不了的时候,就不好了;所以,最后还是用一个“围城”的比喻来说明synchronized与lock的差异来做为总结。

当我们看lock处处都比synchronized好的时候,请看下面:

在使用lock方法的时候,应该注意到一点,就是finally后面要加上unlock()方法,这个就有点像Java与C++在内存回收上的区别,所以写代码的时候,就要特别小心;另外,因为Lock是一个接口,它的实现也就是一个普通的类而已,所以JVM在生成线程转储时能够包括锁定信息的时候,synchronized可以指定到代码,而Lock就不行了,这对于调试,你懂的。

?

围城效应就在这两者之间来回的跑,就想当年喜欢内存回收之后,又去恶补JVM的内存回收机制是一样的。?

?

?

?

?

Java多线程及线程池专题 汇总http://ciding.iteye.com/blog/1300110

发表评论
用户名: 匿名