我眼中多线程的锁
2016-01-21 16:25
465 查看
做了快6年的java项目了,仅用过一次多线程,而且都是架构组封装好的,仅需要调用即可,现在想起来,真是惭愧....近期在项目中又遇到一个需要用多线程处理的问题,因此仔细研究了一下...
------------------------------------------------------------------------------------------------------------------------------------
首先,了解什么是锁,举个简单的例子,你出门的时候会把门锁上,这叫给门这个对象上锁,然后回到家过后肯定会打开锁推门进屋,这叫给门这个对象解锁。在给对象上锁后,若不解锁,则其他人就无法使用这个对象,(假若你带一个朋友回家,假若你不开锁,朋友就无法推开门,推开门就是你朋友在使用你们家门这个对象),在这儿假若你和你朋友是二个线程,你朋友在等你开门的过程中,就是在线程等待,你是正在执行的线程,若你发现钥匙丢了开不了门,就会造成你朋友等待,造成线程执行超时(本来平时开门就两分钟时间),假若你一直都解决不了开锁的问题,你朋友就会一直等待,从而造成死锁...
在java里面的锁,有两类锁,类锁和对象锁。
一、首先我们来说说类锁
实现类锁有两种方式:在方法前加static synchronized或在方法体内对部分或所有代码加synchronized(XXX.class)。
注意:我们要知道,一个类只有一个类锁,当某个线程A为了执行该类的某个已被标识为需要获得类锁才能执行的方法时,若该类的类锁正在被其它线程占用,则线程A会一直等待,直到被其它线程占用的类锁被释放。
对于类锁,方法的执行顺序,主要有四点:
第一,当几个线程同时访问某个类的不同方法时,若执行这些方法都需要获得该类的类锁时,则这几个线程串行执行。举个例子,有个ClassLock类,里面有a()和b()两个方法,执行这两个方法都需要获得该类的类锁(因为方法被标记了static synchronized),此时有两个线程,分别访问a()和b()方法,则这两个线程串行执行,代码示例如下:
第四,当几个线程同时访问某个类的不同方法时,若执行该类的这些方法都不需要获得该类的类锁,则几个线程并行执行,很简单,我这儿就不举例了。
上面例子中我们还可以通过synchronized (ClassLock.class) 这种方式获得类锁,以下这两种方式效果相同
关于类锁我们就讲完了,我们总结一下,其实最重要的一点:一个类只有一个类锁,当某个线程A为了执行该类的某个已被标识为需要获得类锁才能执行的方法时,若该类的类锁正在被其它线程占用,则线程A会一直等待,直到被其它线程占用的类锁被释放。
第二,我们来说下对象锁
首先我们要知道:一个对象只有一把锁,如果一个对象被某个标识为synchronized的方法或synchronized(XXX){//}的代码块引用了,而这个方法此时又正在被A线程执行,那么这个对象此时就被锁住了,要等到A线程执行完synchronized作用范围的代码,这个对象的锁才会被释放,才能被其它线程访问。
锁住对象的方式有两种,在方法前上synchronizd或在方法体内标识synchronizd(对象),在方法前加synchronizd,那这把锁的作用范围是整个方法体,而在方法内部用synchronizd(对象)标识,则作用范围要灵活得多,可以是整个方法体,也可以是部分语句块。在锁的作用范围内的所有对象,都将被锁住,只到当锁被释放后,才能被另外一个线程访问。
我们来举个例子,ObjectLockC类的main方法中有线程一、线程二、线程三,三个线程,在类中有a()方法,调用时需要传入d对象,a()方法中直接会调用d对象的打印方法执行打印操作,d对象打印时需要获得对象d的锁,线程二、线程三调用ObjectLockC类A()方法,代码如下:
...知道答案了吗,答案如下:
一、首先线程一、线程二、线程三并行执行,因为线程一、线程三在执行a()方法第一行代码时还没进入到synchronized范围内,因此d对象和ca对象不会被锁,因此三个线程首先并行执行,其次是线程一执行,线程一执行进入a()方法的synchronized的作用范围后,会锁定ca对象,因此线程一、线程三串行执行。
二、在执行线程一时,首先会锁住ca对象,因为a()方法是ca对象的,而a()方法内部又被synchronized标识了,因此当执行到a()方法中synchronized作用范围内的代码时,就会锁住ca对象,为什么要锁住d呢,因d在方法a()中synchronized的作用范围内。
这个就是对象的锁。
上面介绍完了两种锁,我们通过一个例子还看下他们两者的区别,代码如下:
上面就讲完了,若有问题,请留言。
------------------------------------------------------------------------------------------------------------------------------------
首先,了解什么是锁,举个简单的例子,你出门的时候会把门锁上,这叫给门这个对象上锁,然后回到家过后肯定会打开锁推门进屋,这叫给门这个对象解锁。在给对象上锁后,若不解锁,则其他人就无法使用这个对象,(假若你带一个朋友回家,假若你不开锁,朋友就无法推开门,推开门就是你朋友在使用你们家门这个对象),在这儿假若你和你朋友是二个线程,你朋友在等你开门的过程中,就是在线程等待,你是正在执行的线程,若你发现钥匙丢了开不了门,就会造成你朋友等待,造成线程执行超时(本来平时开门就两分钟时间),假若你一直都解决不了开锁的问题,你朋友就会一直等待,从而造成死锁...
在java里面的锁,有两类锁,类锁和对象锁。
一、首先我们来说说类锁
实现类锁有两种方式:在方法前加static synchronized或在方法体内对部分或所有代码加synchronized(XXX.class)。
注意:我们要知道,一个类只有一个类锁,当某个线程A为了执行该类的某个已被标识为需要获得类锁才能执行的方法时,若该类的类锁正在被其它线程占用,则线程A会一直等待,直到被其它线程占用的类锁被释放。
对于类锁,方法的执行顺序,主要有四点:
第一,当几个线程同时访问某个类的不同方法时,若执行这些方法都需要获得该类的类锁时,则这几个线程串行执行。举个例子,有个ClassLock类,里面有a()和b()两个方法,执行这两个方法都需要获得该类的类锁(因为方法被标记了static synchronized),此时有两个线程,分别访问a()和b()方法,则这两个线程串行执行,代码示例如下:
public class ClassLock { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { a(); } }, "线程一").start(); new Thread(new Runnable() { @Override public void run() { b(); } }, "线程二").start(); } public static synchronized void a() { System.out.println("线程" + Thread.currentThread().getName() + "正在执行"); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();获得类锁,时间:" + new Date().toLocaleString()); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();正在处理数据,时间" + new Date().toLocaleString()); try { Thread.sleep(5000); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();已处理完数据,释放类锁"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static synchronized void b() { System.out.println("线程" + Thread.currentThread().getName() + "正在执行"); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,b();获得类锁,时间:" + new Date().toLocaleString()); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,b();正在处理数据,时间" + new Date().toLocaleString()); try { Thread.sleep(5000); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,b();已处理完数据,释放类锁"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } 控制台输出如下: --------------------------------------------------------- 线程线程一正在执行 线程线程一正在执行,a();获得类锁,时间:2016-1-22 9:20:06 线程线程一正在执行,a();正在处理数据,时间2016-1-22 9:20:06 线程线程一正在执行,a();已处理完数据,释放类锁 线程线程二正在执行 线程线程二正在执行,b();获得类锁,时间:2016-1-22 9:20:11 线程线程二正在执行,b();正在处理数据,时间2016-1-22 9:20:11 线程线程二正在执行,b();已处理完数据,释放类锁第二,当几个线程同时访问某个类的相同方法时,若执行这个方法需要获得该类的类锁时,则这几个线程串行执行,代码示例如下:
public class ClassLock2 { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { a(); } }, "线程一").start(); new Thread(new Runnable() { @Override public void run() { a(); } }, "线程二").start(); } public static synchronized void a() { System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();获得类锁,时间:" + new Date().toLocaleString()); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();正在处理数据,时间" + new Date().toLocaleString()); try { Thread.sleep(5000); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();已处理完数据,释放类锁"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } 控制台输出如下: --------------------------------------------------------- 线程线程一正在执行,a();获得类锁,时间:2016-1-22 9:26:17 线程线程一正在执行,a();正在处理数据,时间2016-1-22 9:26:17 线程线程一正在执行,a();已处理完数据,释放类锁 线程线程二正在执行,a();获得类锁,时间:2016-1-22 9:26:22 线程线程二正在执行,a();正在处理数据,时间2016-1-22 9:26:22 线程线程二正在执行,a();已处理完数据,释放类锁第三、当几个线程同时访问某个类的不同方法时,若该类中仅其中一个方法在执行时需要获得类锁,则这几个线程并行执行,代码示例如下:
public class ClassLock3 { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { a(); } }, "线程一").start(); new Thread(new Runnable() { @Override public void run() { b(); } }, "线程二").start(); } public static synchronized void a() { System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();获得类锁,时间:" + new Date().toLocaleString()); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();正在处理数据,时间" + new Date().toLocaleString()); try { Thread.sleep(5000); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();已处理完数据,释放类锁"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void b() { System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();获得类锁,时间:" + new Date().toLocaleString()); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();正在处理数据,时间" + new Date().toLocaleString()); try { Thread.sleep(5000); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();已处理完数据,释放类锁"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } 控制台输出如下: --------------------------------------------------------- 线程线程一正在执行,a();获得类锁,时间:2016-1-22 9:29:04 线程线程二正在执行,a();获得类锁,时间:2016-1-22 9:29:04 线程线程一正在执行,a();正在处理数据,时间2016-1-22 9:29:04 线程线程二正在执行,a(); 4000 正在处理数据,时间2016-1-22 9:29:04 线程线程二正在执行,a();已处理完数据,释放类锁 线程线程一正在执行,a();已处理完数据,释放类锁
第四,当几个线程同时访问某个类的不同方法时,若执行该类的这些方法都不需要获得该类的类锁,则几个线程并行执行,很简单,我这儿就不举例了。
上面例子中我们还可以通过synchronized (ClassLock.class) 这种方式获得类锁,以下这两种方式效果相同
public static synchronized void methodName() { System.out.println("进入方法"); System.out.println("处理数据"); System.out.println("执行完成"); } public static void methodName() { synchronized (XXX.class) { System.out.println("进入方法"); System.out.println("处理数据"); System.out.println("执行完成"); } }synchronized(XXX.class)方式相对灵活一些,它可以针对方法体内的某些代码而不是所有代码(作用范围可以根据需要调整),若作用范围不是整个方法体,当多个线程同时访问该方法时,不需要获得类锁的代码同步执行,需要获得类锁的代码串行执行,例
public class ClassLock01 { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { a(); } }, "线程一").start(); new Thread(new Runnable() { @Override public void run() { a(); } }, "线程二").start(); } public static void a() { System.out.println("线程" + Thread.currentThread().getName() + "正在执行,时间" + new Date().toLocaleString()); synchronized (ClassLock01.class) { System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();获得类锁,时间:" + new Date().toLocaleString()); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();正在处理数据,时间" + new Date().toLocaleString()); try { Thread.sleep(5000); System.out.println("线程" + Thread.currentThread().getName() + "正在执行,a();已处理完数据,释放类锁"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } 控制台输出如下: --------------------------------------------------------- 线程线程二正在执行,时间2016-1-22 9:34:52 线程线程一正在执行,时间2016-1-22 9:34:52 线程线程二正在执行,a();获得类锁,时间:2016-1-22 9:34:52 线程线程二正在执行,a();正在处理数据,时间2016-1-22 9:34:52 线程线程二正在执行,a();已处理完数据,释放类锁 线程线程一正在执行,a();获得类锁,时间:2016-1-22 9:34:57 线程线程一正在执行,a();正在处理数据,时间2016-1-22 9:34:57 线程线程一正在执行,a();已处理完数据,释放类锁
关于类锁我们就讲完了,我们总结一下,其实最重要的一点:一个类只有一个类锁,当某个线程A为了执行该类的某个已被标识为需要获得类锁才能执行的方法时,若该类的类锁正在被其它线程占用,则线程A会一直等待,直到被其它线程占用的类锁被释放。
第二,我们来说下对象锁
首先我们要知道:一个对象只有一把锁,如果一个对象被某个标识为synchronized的方法或synchronized(XXX){//}的代码块引用了,而这个方法此时又正在被A线程执行,那么这个对象此时就被锁住了,要等到A线程执行完synchronized作用范围的代码,这个对象的锁才会被释放,才能被其它线程访问。
锁住对象的方式有两种,在方法前上synchronizd或在方法体内标识synchronizd(对象),在方法前加synchronizd,那这把锁的作用范围是整个方法体,而在方法内部用synchronizd(对象)标识,则作用范围要灵活得多,可以是整个方法体,也可以是部分语句块。在锁的作用范围内的所有对象,都将被锁住,只到当锁被释放后,才能被另外一个线程访问。
我们来举个例子,ObjectLockC类的main方法中有线程一、线程二、线程三,三个线程,在类中有a()方法,调用时需要传入d对象,a()方法中直接会调用d对象的打印方法执行打印操作,d对象打印时需要获得对象d的锁,线程二、线程三调用ObjectLockC类A()方法,代码如下:
public class ObjectLockC { public static void main(String[] args) { final ObjectLockC ca = new ObjectLockC(); final D d = new D(); new Thread(new Runnable() { @Override public void run() { ca.a(d); } }, "线程一").start(); new Thread(new Runnable() { @Override public void run() { d.print(); } }, "线程二").start(); new Thread(new Runnable() { @Override public void run() { ca.a(d); } }, "线程三").start(); } public void a(D d) { System.out.println(Thread.currentThread().getName() + ",正在执行a()方法,时间" + new Date().toLocaleString()); synchronized (d) { d.print(); System.out.println(Thread.currentThread().getName() + "正在执行,获得ca对象的锁执行a()方法,时间:" + new Date().toLocaleString()); System.out.println(Thread.currentThread().getName() + "正在执行,a();正在处理数据,时间" + new Date().toLocaleString()); try { Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + "执行结束,a();已处理完数据,释放对象ca和d的锁"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } static class D { public synchronized void print() { System.out.println(Thread.currentThread().getName() + ",正在执行,时间:" + new Date().toLocaleString()); try { Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + ",执行结束,时间:" + new Date().toLocaleString()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } 控制台输出如下: --------------------------------------------------------- 线程二,正在执行,时间:2016-1-22 16:58:51 线程三,正在执行a()方法,时间2016-1-22 16:58:51 线程一,正在执行a()方法,时间2016-1-22 16:58:51 线程二,执行结束,时间:2016-1-22 16:58:53 线程一,正在执行,时间:2016-1-22 16:58:53 线程一,执行结束,时间:2016-1-22 16:58:55 线程一正在执行,获得ca对象的锁执行a()方法,时间:2016-1-22 16:58:55 线程一正在执行,a();正在处理数据,时间2016-1-22 16:58:55 线程一执行结束,a();已处理完数据,释放对象ca和d的锁 线程三,正在执行,时间:2016-1-22 16:59:00 线程三,执行结束,时间:2016-1-22 16:59:02 线程三正在执行,获得ca对象的锁执行a()方法,时间:2016-1-22 16:59:02 线程三正在执行,a();正在处理数据,时间2016-1-22 16:59:02 线程三执行结束,a();已处理完数据,释放对象ca和d的锁首先,我问一个问题,在上例中,这三个线程是并行执行还是串行执行?在执行线程一时有哪些对象被锁住了?
...知道答案了吗,答案如下:
一、首先线程一、线程二、线程三并行执行,因为线程一、线程三在执行a()方法第一行代码时还没进入到synchronized范围内,因此d对象和ca对象不会被锁,因此三个线程首先并行执行,其次是线程一执行,线程一执行进入a()方法的synchronized的作用范围后,会锁定ca对象,因此线程一、线程三串行执行。
二、在执行线程一时,首先会锁住ca对象,因为a()方法是ca对象的,而a()方法内部又被synchronized标识了,因此当执行到a()方法中synchronized作用范围内的代码时,就会锁住ca对象,为什么要锁住d呢,因d在方法a()中synchronized的作用范围内。
这个就是对象的锁。
上面介绍完了两种锁,我们通过一个例子还看下他们两者的区别,代码如下:
public class DifLock { public static void main(String[] args) { test1(); try { Thread.currentThread().sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } test2(); } private static void test1() { final DifLock l1 = new DifLock(); final DifLock l2 = new DifLock(); new Thread(new Runnable() { @Override public void run() { l1.b(); } }, "线程一").start(); new Thread(new Runnable() { @Override public void run() { l2.b(); } }, "线程二").start(); } private static void test2() { final DifLock l1 = new DifLock(); final DifLock l2 = new DifLock(); new Thread(new Runnable() { @Override public void run() { l1.a(); } }, "线程一").start(); new Thread(new Runnable() { @Override public void run() { l2.a(); } }, "线程二").start(); } public static synchronized void a() { System.out.println(Thread.currentThread().getName() + "正在执行a(),时间" + System.currentTimeMillis() / 1000); try { Thread.currentThread().sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public synchronized void b() { System.out.println(Thread.currentThread().getName() + "正在执行b(),时间" + System.currentTimeMillis() / 1000); try { Thread.currentThread().sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } 控制台输出如下: --------------------------------------------------------- 线程一正在执行b(),时间1453454757 线程二正在执行b(),时间1453454757 线程一正在执行a(),时间1453454762 线程二正在执行a(),时间1453454763上面的代码中进行了两种测试,第一个测试,两个不同的实例通过两个线程分别调用类锁的方法,执行结果为两个实例串行执行,第二个测试,两个不同实例通过两个线程分别调用对象锁的方法,执行结果为两个实例并行执行,为什么会这样呢?因为第一个测试,DifLock类的类锁只有一个,线程1获得了,那么线程2就需要等待线程1释放类锁,第二个测试,实例1和实例2分别为不同的对象,也就是说对象锁是不一样的,因此线程2不需要等待线程1释放锁。
上面就讲完了,若有问题,请留言。
相关文章推荐
- Python join和split函数
- SAP快速找到定制配置
- 链表常见面试题四:解决链表相交问题
- 远程连接linux执行命并且返回执行结果
- sp<>,wp<>
- RabbitMQ 入门 Helloworld
- C#调用FFMPEG实现桌面录制(视频+音频+生成本地文件)【笔记】
- Java学习第9天(2):面向对象-多态-子父类类型变换
- unity3d 播放视频的方法总结
- 解决:dubbo找不到dubbo.xsd报错
- 利用Console来调试JS程序、Console用法总结
- 如何将文章列表用<li>分两列显示
- 多线程单利模式之双检锁必要性
- Quartz.net持久化与集群部署开发详解
- cocos2dx 3.9 创建自定义mesh进行渲染。
- AWS 之 S3篇<.NET(c#)批量上传文件>
- 安卓测试
- 跳台阶
- 虚函数的使用【C语言】
- 内存分析工具 MAT 的使用