`

多线程——锁(lock)

    博客分类:
  • java
阅读更多

上一讲《多线程——同步(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;

/**
 * lock锁
 * 
 * @author ciding 
 * @createTime Dec 14, 2011 9:54:03 AM
 *
 */
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;

/**
 * lock锁
 * 
 * @author ciding 
 * @createTime Dec 14, 2011 9:54:03 AM
 *
 */
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;

/**
 * lock锁
 * 
 * @author ciding 
 * @createTime Dec 14, 2011 9:54:03 AM
 *
 */
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(1000);// 作用:增加运行时间
			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(1000);// 作用:增加运行时间
			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的处理时间中,只有很少的时间花在线程调度上,大多都用在实际工作上。

 

 

 

 

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

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

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

 

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

 

 

 

 

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

7
8
分享到:
评论
8 楼 ciding 2011-12-29  
javaeyehoney 写道
写的什么玩意  肤浅


只是讲讲使用而已,还有很多不足之处,还请指明方向,比起大师来说,还是比较肤浅,期待大师之作,一定去品读。
7 楼 javaeyehoney 2011-12-29  
写的什么玩意  肤浅
6 楼 ciding 2011-12-14  
xmmcnn 写道
我想知道这个运行结果为什么不是按ABCDEF顺序来的???不是加了synchronized么???

可以看看上面两节的内容,你就知道了,锁定的对象是什么。
ABCDEF是不同的线程,线程的执行顺序是不一定的。

最好再看上两节的内容
5 楼 xmmcnn 2011-12-14  
我想知道这个运行结果为什么不是按ABCDEF顺序来的???不是加了synchronized么???
4 楼 ciding 2011-12-14  
修改了上面的,下面的又变了,懒得加了,在这里加上吧

1处
void lock() { sync.lock(); }

2处
/**
     * 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);
        }
    }



3处
/**
         * 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;
        }

3 楼 ciding 2011-12-14  
Grandline 写道
这排版 囧。。。。。。

本来好好的,结果一编辑就这样了,又得重新放入;
这个iteye的编辑老是容易出问题
2 楼 Grandline 2011-12-14  
这排版 囧。。。。。。
1 楼 zone8089653 2011-12-13  

相关推荐

    Java多线程——线程八锁案例分析.docx

    在Java多线程编程中,线程安全是一个关键概念,特别是在并发访问共享资源时。"线程八锁案例分析"文档中的示例着重展示了`synchronized`关键字如何在控制线程同步方面发挥作用。以下是对这些案例的详细分析: 案例1...

    C# 多线程——可执行源代码

    【C# 多线程】是C#编程中一种重要的技术,它允许程序在同一时间执行多个任务,提高程序的响应速度和效率。多线程在现代软件开发中扮演着核心角色,尤其是在用户界面(UI)应用程序中,可以实现异步操作,避免用户...

    多线程编程——线程的同步

    在“多线程编程之四——线程的同步”这个文件中,可能包含了上述各种同步机制的具体实现示例和详细说明,这对于初学者来说是一份非常宝贵的参考资料。通过学习和理解这些例子,开发者可以更好地掌握如何在实际项目中...

    java多线程案例——未完成

    Java多线程是Java编程中的一个重要领域,它允许程序同时执行多个任务,从而提高系统效率和资源利用率。在这个未完成的案例中,我们可能正在探讨如何在Java中创建和管理线程,以及处理多线程环境下的并发问题。下面是...

    多线程测试(wxWidgets)

    在C++编程中,多线程是一个核心概念,它允许程序同时执行多个任务,从而提高系统效率和响应性。在现代计算机系统中,多核处理器的普及使得多线程技术成为充分利用硬件资源的关键。本教程将重点介绍如何在C++环境中,...

    JAVA项目——多线程下载代码

    本项目以"JAVA项目——多线程下载代码"为主题,使用Eclipse集成开发环境进行实现,适合于Java初学者或毕业设计实践。下面我们将深入探讨相关的Java多线程下载知识点。 1. **线程基础**:在Java中,线程是程序执行的...

    Java多线程的小例子——吃包子

    这个名为"Java多线程的小例子——吃包子"的示例,旨在帮助开发者直观地理解多线程的工作原理。下面我们将深入探讨该示例所涉及的核心知识点。 首先,多线程通常涉及到以下几个关键概念: 1. **线程(Thread)**:...

    多线程编程之三——线程间通讯

    标题与描述均提到了“多线程编程之三——线程间通讯”,这明确指出了文章的核心主题:在多线程编程环境下,不同线程之间的通信机制。在现代软件开发中,尤其是涉及到高性能计算、并发处理以及分布式系统设计时,线程...

    并发服务器II——多线程

    POSIX线程库,简称pthreads,是Unix和类Unix系统中实现多线程编程的标准接口。这个库提供了一系列函数,使得程序员可以在同一进程中创建、管理、同步和通信多个线程。多线程服务器模型是现代高性能网络服务的常用...

    Linux多线程编程知识点总结(C语言)(csdn)————程序.pdf

    Linux多线程编程是开发高效率、高性能应用的重要技术之一,尤其在C语言环境中,它提供了直接和底层的控制。本文主要围绕Linux下C语言实现的多线程编程进行知识点总结,涉及线程与进程的区别、多线程的优势、Pthreads...

    Java练手小项目——多线程聊天室.zip

    【Java练手小项目——多线程聊天室】 在Java编程世界中,多线程是不可或缺的一部分,尤其在开发实时性、交互性强的应用时,如我们的主题“多线程聊天室”。这个实战项目旨在帮助开发者深入理解Java多线程的概念,并...

    java多线程设计模式详解(PDF及源码)

    重点回顾 练习问题 Introduction 2 多线程程序的评量标准 多线程程序的评量标准 安全性——不损坏对象 生存性——进行必要的处理 复用性——可再利用类 性能——能快速、大量进行处理 评量标准的总结 重点回顾 练习...

    多线程编程之四——线程的同步

    在多线程编程中,线程的同步是一个关键的概念,主要目的是确保多个线程在访问共享资源时能够有序地执行,防止数据不一致和竞态条件等问题。在MFC(Microsoft Foundation Classes)库中,提供了多种同步对象来帮助...

    面试题解惑系列(十)——话说多线程

    【标题】:“面试题解惑系列(十)——话说多线程” 【描述】:本篇文章主要探讨的是Java中的多线程概念及其在面试中常见的问题解析。 【标签】:“面试题解惑系列(十)——话说多线程” 【部分内容】:在Java中,多...

    第13章龟兔赛跑——多线程.ppt

    总结来说,通过学习"第13章龟兔赛跑——多线程",我们可以掌握Java中多线程的基本概念、创建和管理,以及如何利用多线程提高程序效率。理解并熟练应用这些知识对于开发高效、响应迅速的软件至关重要。

    多线程面试题

    在Java编程领域,多线程是面试中常见且重要的知识点,尤其对于系统设计和高并发处理的岗位至关重要。本文将围绕“多线程面试题”这一主题,深入探讨相关概念、技术及其应用。 1. **线程的概念**:线程是程序执行的...

    c++多线程编程之四——线程的同步

    【线程同步】是多线程编程中一个关键的概念,主要目的是解决多个线程并发访问共享资源时可能出现的问题,如数据不一致、竞态条件等。在C++中,MFC(Microsoft Foundation Classes)框架提供了多种同步对象来帮助...

    java多线程学习笔记02(csdn)————程序.pdf

    总之,Java的多线程同步机制提供了多种选择,包括volatile、synchronized和Lock接口。开发者可以根据具体需求选择最适合的工具,以实现高效、安全的并发编程。在设计多线程程序时,应充分考虑线程安全、性能和可维护...

Global site tag (gtag.js) - Google Analytics