16.Java面向对象的三个特征与含义
继承
(1)继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。
(2)对象的一个新类可以从现有的类中派生,这个过程称为类继承,新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。
(3)派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
封装
(1)封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。
多态性
(1)多态性是指允许不同类的对象对同一消息作出响应。
(2)多态性包括参数化多态性和包含多态性。
(3)多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
17.Override和Overload的含义去区别
1. 方法重载(overload)
概念:简单的说:方法重载就是类的同一种功能的多种实现方式,到底采用哪种方式,取决于调用者给出的参数。
注意事项:
(1) 方法名相同
(2) 方法的参数类型、个数、顺序不至少有一项不同
(3) 方法返回类型可以不同
(4) 方法的修饰符可以不同
如果只是返回类型不一样,不能够构成重载
如果只是控制访问修饰符号不一样,也是不能构成重载的
Overloaded的方法是可以改变返回值的类型。
2. 方法覆盖(override)
概念:简单的说:方法覆盖就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的那个方法。
注意事项:方法覆盖有很多条件,总的讲有两点一定要注意:
(1) 子类的方法的返回类型,参数,方法名称,要和父类方法的返回类型,参数,方法名称完全一样,否则编译出错。
(2) 子类方法不能缩小父类方法的访问权限(反过来是可以的)
例:
Overrride实例
class A{
public int getVal(){
return(5);
}
}
class B extends A{
public int getVal(){
return(10);
}
}
public class override {
public static void main(String[] args) {
B b = new B();
A a= (A)b;//把 b 强 制转换成A的类型
int x=a.getVal();
System.out.println(x);
}
}
Overload实例
package com.guonan;
//Demostrate method voerloading.
class OverloadDemo {
void test(){
System.out.println("NO parameters");
}
void test(int a){
System.out.println("a:"+a);
}//end of Overload test for one integer parameter.
void test(int a, int b){
System.out.println("a and b:"+a+" "+b);
}
double test(double a){
System.out.println("double a:"+a);
return a*a;
}
}
class Overload{
public static void main(String[] args) {
OverloadDemo ob = new OverloadDemo();
double result;
ob.test();
ob.test(10);
ob.test(10, 20);
result = ob.test(123.25);
System.out.println("Result of ob.test(123.25):"+result);
}
}
18. Interface与abstract类的区别
在Java语言中,abstract class和interface是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。
| Abstract class | Interface |
实例化 | 不能 | 不能 |
类 | 一种继承关系,一个类只能使用一次继承关系。可以通过继承多个接口实现多重继承 | 一个类可以实现多个interface |
数据成员 | 可有自己的 | 静态的不能被修改即必须是static final,一般不在此定义 |
方法 | 可以私有的,非abstract方法,必须实现 | 不可有私有的,默认是public,abstract 类型 |
变量 | 可有私有的,默认是friendly 型,其值可以在子类中重新定义,也可以重新赋值 | 不可有私有的,默认是public static final 型,且必须给其初值,实现类中不能重新定义,不能改变其值。 |
设计理念 | 表示的是“is-a”关系 | 表示的是“like-a”关系 |
实现 | 需要继承,要用extends | 要用implements |
abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?
声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类为。取而代之,在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。
接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的。多继承性可通过实现 这样的接口而获得。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的方法。 然后,它可以在实现了该接口的类的任何对象上调用接口的方法。由于有抽象类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到 接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象的类是否实现了接口。
接口可以继承接口。抽象类可以实现(implements)接口,抽象类是可以继承实体类,但前提是实体类必须有明确的构造函数。接口更关注“能实现什么功能”,而不管“怎么实现的”。
1.相同点
A. 两者都是抽象类,都不能实例化。
B. interface实现类及abstrct class的子类都必须要实现已经声明的抽象方法。
2. 不同点
A. interface需要实现,要用implements,而abstract class需要继承,要用extends。
B. 一个类可以实现多个interface,但一个类只能继承一个abstract class。
C. interface强调特定功能的实现,而abstract class强调所属关系。
D. 尽管interface实现类及abstrct class的子类都必须要实现相应的抽象方法,但实现的形式不同。interface中的每一个方法都是抽象方法,都只是声明的 (declaration, 没有方法体),实现类必须要实现。而abstract class的子类可以有选择地实现。
这个选择有两点含义:
一是Abastract class中并非所有的方法都是抽象的,只有那些冠有abstract的方法才是抽象的,子类必须实现。那些没有abstract的方法,在Abstrct class中必须定义方法体。
二是abstract class的子类在继承它时,对非抽象方法既可以直接继承,也可以覆盖;而对抽象方法,可以选择实现,也可以通过再次声明其方法为抽象的方式,无需实现,留给其子类来实现,但此类必须也声明为抽象类。既是抽象类,当然也不能实例化。
E. abstract class是interface与Class的中介。
interface是完全抽象的,只能声明方法,而且只能声明pulic的方法,不能声明private及protected的方法,不能定义方法体,也 不能声明实例变量。然而,interface却可以声明常量变量,并且在JDK中不难找出这种例子。但将常量变量放在interface中违背了其作为接 口的作用而存在的宗旨,也混淆了interface与类的不同价值。如果的确需要,可以将其放在相应的abstract class或Class中。
abstract class在interface及Class中起到了承上启下的作用。一方面,abstract class是抽象的,可以声明抽象方法,以规范子类必须实现的功能;另一方面,它又可以定义缺省的方法体,供子类直接使用或覆盖。另外,它还可以定义自己 的实例变量,以供子类通过继承来使用。
3. interface的应用场合
A. 类与类之前需要特定的接口进行协调,而不在乎其如何实现。
B. 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识。
C. 需要将一组类视为单一的类,而调用者只通过接口来与这组类发生联系。
D. 需要实现特定的多项功能,而这些功能之间可能完全没有任何联系。
4. abstract class的应用场合
一句话,在既需要统一的接口,又需要实例变量或缺省的方法的情况下,就可以使用它。最常见的有:
A. 定义了一组接口,但又不想强迫每个实现类都必须实现所有的接口。可以用abstract class定义一组方法体,甚至可以是空方法体,然后由子类选择自己所感兴趣的方法来覆盖。
B. 某些场合下,只靠纯粹的接口不能满足类与类之间的协调,还必需类中表示状态的变量来区别不同的关系。abstract的中介作用可以很好地满足这一点。
C. 规范了一组相互协调的方法,其中一些方法是共同的,与状态无关的,可以共享的,无需子类分别实现;而另一些方法却需要各个子类根据自己特定的状态来实现特定的功能。
19.
Static class
与
non static class
的区别。
其实static与non-static的区别。可以这样理解。
某个对象的特性分为类特性与实例特性。类特性是与类相关的。如
1
2
3
4
| class Student{
static int numberOfStudent;
........
}
|
这里numberOfStudent是学生数。是与Student类相关的一个数据。
而实例特性是每个对象本身自己的特性。如:
1
2
3
4
5
| class Student{
int number;
String name;
........
}
|
这里的number(学号),name(姓名)。是与每个对象自身相关的。
static与non-static是对立的。static应当(注意是应当)使用类名来引用。而non-static必须(是必须)使用对象实例名来引用。
static与non-static在引用数据成员方面的差别:因为static、non-static的数据相关性,static只能引用类的static数据成员;而non-static既可以引用类的static数据成员,也可以引用对象自身的数据。
static与non-static method在overload方面是一样的。
而static与non-static method在override方面则完全不同。static方法是与类相关的,不是通过this引用的,所以它不能被override。其引用在编译期就得确定。而non-static方法才有可能被override。
static与abstract,它们不能同时用于修饰一个方法。因为abstract的语义就是说这个方法是多态方法,需要subclass的实现。而static方法则是在本类中实现的,编译期绑定,不具有多态行为。
static与interface,interface中的method也不能是static的。理由同上。但其数据成员
are all static, no matter you mark it static or not
。
多态只限于方法,所以,无论static还是non-static的成员变量,引用的是哪个在编译期就已经确定。
内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。
内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。
根据Oracle官方的说法:
Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes. Non-static
nested classes are called inner classes.
从字面上看,一个被称为静态嵌套类,一个被称为内部类。
从字面的角度解释是这样的:
什么是嵌套?嵌套就是我跟你没关系,自己可以完全独立存在,但是我就想借你的壳用一下,来隐藏一下我自己(真TM猥琐)。
什么是内部?内部就是我是你的一部分,我了解你,我知道你的全部,没有你就没有我。(所以内部类对象是以外部类对象存在为前提的)
简单理解就是:如果把类比喻成鸡蛋,内部类为蛋黄,外部类是蛋壳。那么静态类相当于熟鸡蛋,就算蛋壳破碎(外部类没有实例化),蛋黄依然完好(内部类可
以实例化);而非静态类相当于生鸡蛋,蛋壳破碎(无实例化),蛋黄也会跟着xx(不能实例化)。
非静态内部类访问外部类成员 使用外部类名.this.属性的方式访问, 内部类访问自身的属性使用this.属性的方式访问
外部类访问非静态内部类的属性会报错,除非new出一个内部类的实例,通过实例去调用实例属性
不允许在外部类的静态成员里使用非静态成员
非静态内部类可以访问外部类private、public、protected、friendly修饰的成员属性或方法还有静态属性和方法,
非静态内部类不能有静态成员
非静态内部类不能有静态初始化块,静态属性,静态方法,但是可以有普通初始化块,普通初始化块的作用与顶层类初始化块的作用相同
静态内部类是类相关的,静态内部类不能访问外部类的实例属性,但可以访问外部类的静态属性,静态内部类可以有静态属性,
外部类依然不能直接访问静态内部类的属性,但可以通过静态内部类的类名.静态属性来进行访问,访问静态内部类的实例属性,则只能通过实例化静态内部类的实例,通过实例来调用实例属性
20.java多态的实现原理
靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法
既然多态是面向对象的三大本质特征之一(其它两个是数据抽象和继承),那么 C++为什么不将方法调用的默认方式设置为动态绑定,而要通过关键字virtual进行标记呢?Bruce Eckel在《Thinking in C++》中提到,这是由于历史原因造成的,C++是从C发展而来的,而C程序员最为关心的是性能问题,由于动态绑定比静态绑定多几条指令,性能有所下降, 如果将动态绑定设定为默认方法调用方式,那么很多C程序员可能不会接受,因此,C++就将动态绑定定位成可选的,并且作出保证:If you don't use it, you don't pay for it(Stroustrup)。
但是,Java作为一个全新的完全面向对象的语言,并不存在向下兼容的问题,同时,Java的设计者也认为多态作为面向面向对象的核心,面向对象语言应该提供内置的支持,因此,Java将动态绑定作为方法调用的默认方式。
下面,我们就详细地来了解一下Java是如何为多态提供支持的。 与C++一样,Java中也有一个存放实例方法地址的数据结构,在C++中,我们把它叫做VTable,而在java中方法表(Method Table),但是两者有很多相同之处:
1、它们的作用是相同的,同样用来辅助实现方法的动态绑定。
2、同样是类级别的数据结构,一个类的所有对象共享一个方法表。
3、都是通过偏移量在该数据结构中查找某一个方法。
4、同样保证所有派生类中继承于基类的方法在方法表中的偏移量跟该方法在基类方法表中的偏移量保持一致。
5、方法表中都只能存放多态方法(Java中的实例方法,C++中是vitual方法)。
但是归根结底,C++是一门编译型的语言,而Java更加偏向于解析型的,因此上述数据结构的生成和维护是有所不同的,表现在:
1、C++中VTable和vptr是在编译阶段由编译器自动生成的,也就是说,在C++程序载入内存以前,在.obj(.o)文件中已经有这些结构的信 息;Java中的方法表是由JVM生成的,因此,使用javac命令编译后生成的.class文件中并没有方法表的信息。只有等JVM把.class文件 载入到内存中时,才会为该.class文件动态生成一个与之关联的方法表,放置在JVM的方法区中。
2、C++中某个方法在VTable的索引号是在编译阶段已经明确知道的,并不需要在运行过程中动态获知;Java中的方法初始时都只是一个符号,并不是 一个明确的地址,只有等到该方法被第一次调用时,才会被解析成一个方法表中的偏移量,也就是说,只有在这个时候,实例方法才明确知道自己在方发表中的偏移 量了,在这之前必须经历一个解析的过程。
此外,Java中不支持多重继承,也就不会像C++那样在这个泥潭中纠缠不清了,但Java也引入了新的概念,那就是接口,Interface。使用Interface调用一个实例方法跟使用一个Class来调用的过程是不一样的:
public class Zoo
{
public static void main(String[] args)
{
Pet p1 = new Dog();
Pet p2 = new Dog();
p1.say(); //首先解析一次,得到偏移量,调用方法
p2.say(); //不用解析,直接使用上次的得到的偏移量,调用
Cute c1 = new Dog();
Cute c2 = new Dog();
c1.cute(); //这里使用接口来调用实例方法,首先同样会解析一次,得到偏移量,调用相应方法
c2.cute(); //这里虽然上次已经解析过了,但是还是得重新跟上次一样重新解析一次,得到偏移量,调用
}
}
interface Cute
{
public void cute();
}
class Pet
{
public void say(){ System.out.println("Pet say"); }
}
class Dog extends Pet implements Cute
{
public void cute(){ System.out.println("Dog cute"); }
public void say(){ System.out.println("Dog say"); }
}
为什么会有这样的区别呢?这是因为实现同一个接口的类并不能保证都是从同一个超类继承的,而且这个超类也同样实现相同的接口。因此,该接口声明的方法并不能都保证处于方法表中的同一个位置上。如,可以定义下面的类:
class Cat implements Cute
{
public void cute(){ System.out.println("Cat cute"); }
}
那么,Dog跟Cat同样都实现了接口Cute,因此都能够用Cute接口进行调用,但是方法cute在Dog方法表中的位置并不能保证该方法在Cat方法表中的位置是一样的。因此,对于接口调用方法,我们只好每次都重新解析一道,获得准确的偏移量,再进行调用了。这也导致了使用接口调用方法的效率要比使 用类调用实例方法低。当然,这仅仅是相对而言,JVM在实现上会予以优化,我们不能说因为接口效率低就不使用了,相反由于在面向对象作用中接口的强大作 用,java是提倡使用接口的,这一点我们是需要注意的。
还有一点,虽然java不支持类的多重继承,但是是可以实现多个接口的,那么,在Java中会不会要像C++的多重继承那样进行必要的转换呢?这个问题, 我们只需想一下两者调用的具体过程,就能知道,Java的接口方法每次调用前都是需要解析的,在这里才会取得真正的偏移量,这跟C++中编译期间取得偏移 量是不一样,因此,在Java中是不需要进行所谓的转换的。
21.实现多线程的两种方法:Thread与Runable。
一种是扩展java.lang.Thread类
另一种是实现java.lang.Runnable接口
区别就是:第一种是扩展,第二种是实现
好处就是:
在实际开发中通常以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类可以避免继承的局限,一个类可以继承多个接口,适合于资源的共享。
以卖票程序为例,通过Thread类完成:
package org.demo.dff;
class MyThread extends Thread{
private int ticket=10;
public void run(){
for(int i=0;i<20;i++){
if(this.ticket>0){
System.out.println("賣票:ticket"+this.ticket--);
}
}
}
};
下面通过三个线程对象,同时卖票:
package org.demo.dff;
public class ThreadTicket {
public static void main(String[] args) {
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();
MyThread mt3=new MyThread();
mt1.start();//每个线程都各卖了10张,共卖了30张票
mt2.start();//但实际只有10张票,每个线程都卖自己的票
mt3.start();//没有达到资源共享
}
}
如果用Runnable就可以实现资源共享,下面看例子:
package org.demo.runnable;
class MyThread implements Runnable{
private int ticket=10;
public void run(){
for(int i=0;i<20;i++){
if(this.ticket>0){
System.out.println("賣票:ticket"+this.ticket--);
}
}
}
}
package org.demo.runnable;
public class RunnableTicket {
public static void main(String[] args) {
MyThread mt=new MyThread();
new Thread(mt).start();//同一个mt,但是在Thread中就不可以,如果用同一
new Thread(mt).start();//个实例化对象mt,就会出现异常
new Thread(mt).start();
}
};
虽然现在程序中有三个线程,但是一共卖了10张票,也就是说使用Runnable实现多线程可以达到资源共享目的。
Runnable接口和Thread之间的联系:
public class Thread extends Object implements Runnable
1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。
(2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
22.线程同步的方法:sychronized、lock、reentrantLock等
最近在做一个监控系统,该系统主要包括对数据实时分析和存储两个部分,由于并发量比较高,所以不可避免的使用到了一些并发的知识。为了实现这些要求,后台使用一个队列作为缓存,对于请求只管往缓存里写数据。同时启动一个线程监听该队列,检测到数据,立即请求调度线程,对数据进行处理。 具体的使用方案就是使用同步保证数据的正常,使用线程池提高效率。
同步的实现当然是采用锁了,java中使用锁的两个基本工具是 synchronized 和 Lock。
一直很喜欢synchronized,因为使用它很方便。比如,需要对一个方法进行同步,那么只需在方法的签名添加一个synchronized关键字。
// 未同步的方法
public void test() {}
// 同步的方法
pubilc synchronized void test() {}
synchronized 也可以用在一个代码块上,看
public void test() {
synchronized(obj) {
System.out.println("===");
}
}
synchronized 用在方法和代码块上有什么区别呢?
synchronized 用在方法签名上(以test为例),当某个线程调用此方法时,会获取该实例的对象锁,方法未结束之前,其他线程只能去等待。当这个方法执行完时,才会释放对象锁。其他线程才有机会去抢占这把锁,去执行方法test,但是发生这一切的基础应当是所有线程使用的同一个对象实例,才能实现互斥的现象。否则synchronized关键字将失去意义。
(
但是如果该方法为类方法,即其修饰符为static,那么synchronized 意味着某个调用此方法的线程当前会拥有该类的锁,只要该线程持续在当前方法内运行,其他线程依然无法获得方法的使用权!)
synchronized 用在代码块的使用方式:synchronized(obj){//todo code here}
当线程运行到该代码块内,就会拥有obj对象的对象锁,如果多个线程共享同一个Object对象,那么此时就会形成互斥!特别的,当obj == this时,表示当前调用该方法的实例对象。即
public void test() {
...
synchronized(this) {
// todo your code
}
...
}
此时,其效果等同于
public synchronized void test() {
// todo your code
}
使用synchronized代码块,可以只对需要同步的代码进行同步,这样可以大大的提高效率。
小结:
使用synchronized 代码块相比方法有两点优势:
1、可以只对需要同步的使用
2、与wait()/notify()/nitifyAll()一起使用时,比较方便
----------------------------------------------------------------------------------------------------------------------------------------------------------
wait() 与notify()/notifyAll()
这三个方法都是Object的方法,并不是线程的方法!
wait():释放占有的对象锁,线程进入等待池,释放cpu,而其他正在等待的线程即可抢占此锁,获得锁的线程即可运行程序。而sleep()不同的是,线程调用此方法后,会休眠一段时间,休眠期间,会暂时释放cpu,但并不释放对象锁。也就是说,在休眠期间,其他线程依然无法进入此代码内部。休眠结束,线程重新获得cpu,执行代码。
wait()和sleep()最大的不同在于wait()会释放对象锁,而sleep()不会!
notify(): 该方法会唤醒因为调用对象的wait()而等待的线程,其实就是
对对象锁的唤醒,从而使得wait()的线程可以有机会获取对象锁。调用notify()后,并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,才会释放对象锁。JVM则会在等待的线程中调度一个线程去获得对象锁,执行代码。需要注意的是,
wait()和notify()必须在synchronized代码块中调用。
notifyAll()则是唤醒所有等待的线程。
为了说明这一点,举例如下:
两个线程依次打印"A""B",总共打印10次。
public
class
Consumer
implements
Runnable {
@Override
public
synchronized
void
run() {
//
TODO
Auto-generated method stub
int
count = 10;
while
(count > 0) {
synchronized
(Test.
obj
) {
System.
out
.print(
"B"
);
count --;
Test.
obj
.notify();
// 主动释放对象锁
try
{
Test.
obj
.wait();
}
catch
(InterruptedException e) {
//
TODO
Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public
class
Produce
implements
Runnable {
@Override
public
void
run() {
//
TODO
Auto-generated method stub
int
count = 10;
while
(count > 0) {
synchronized
(Test.
obj
) {
//System.out.print("count = " + count);
System.
out
.print(
"A"
);
count --;
Test.
obj
.notify();
try
{
Test.
obj
.wait();
}
catch
(InterruptedException e) {
//
TODO
Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
测试类如下:
public
class
Test {
public
static
final
Object
obj
=
new
Object();
public
static
void
main(String[] args) {
new
Thread(
new
Produce()).start();
new
Thread(
new
Consumer()).start();
}
}
这里使用static obj作为锁的对象,当线程Produce启动时(假如Produce首先获得锁,则Consumer会等待),打印“A”后,会先主动释放锁,然后阻塞自己。Consumer获得对象锁,打印“B”,然后释放锁,阻塞自己,那么Produce又会获得锁,然后...一直循环下去,直到count = 0.这样,使用Synchronized和wait()以及notify()就可以达到线程同步的目的。
----------------------------------------------------------------------------------------------------------------------------------------------------------
除了wait()和notify()协作完成线程同步之外,使用Lock也可以完成同样的目的。
ReentrantLock 与synchronized有相同的并发性和内存语义,还包含了中断锁等候和定时锁等候,意味着线程A如果先获得了对象obj的锁,那么线程B可以在等待指定时间内依然无法获取锁,那么就会自动放弃该锁。
但是由于synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否,而ReentrantLock使用代码实现的,系统无法自动释放锁,需要在代码中finally子句中显式释放锁lock.unlock();
同样的例子,使用lock 如何实现呢?
public
class
Consumer
implements
Runnable {
private
Lock
lock
;
public
Consumer(Lock lock) {
this
.
lock
= lock;
}
@Override
public
void
run() {
//
TODO
Auto-generated method stub
int
count = 10;
while
( count > 0 ) {
try
{
lock
.lock();
count --;
System.
out
.print(
"B"
);
}
finally
{
lock
.unlock(); //主动释放锁
try
{
Thread. sleep(91L);
}
catch
(InterruptedException e) {
//
TODO
Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public
class
Producer
implements
Runnable{
private
Lock
lock
;
public
Producer(Lock lock) {
this
.
lock
= lock;
}
@Override
public
void
run() {
//
TODO
Auto-generated method stub
int
count = 10;
while
(count > 0) {
try
{
lock
.lock();
count --;
System.
out
.print(
"A"
);
}
finally
{
lock
.unlock();
try
{
Thread. sleep(90L);
}
catch
(InterruptedException e) {
//
TODO
Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
调用代码:
public
class
Test {
public
static
void
main(String[] args) {
Lock lock =
new
ReentrantLock();
Consumer consumer =
new
Consumer(lock);
Producer producer =
new
Producer(lock);
new
Thread(consumer).start();
new
Thread( producer).start();
}
}
使用建议:
在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,其性能下降很严重,此时ReentrantLock是个不错的方案。
http://bbs.51cto.com/thread-1032719-1-1.html
1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
【synchronized同步】 JDK1.5之前,实现同步主要是使用synchronized。
synchronized相信很多熟悉J2SE的人都不会对这个关键字陌生,它用于实现多个线程之间的同步,一般有两种使用方式:
1、在方法上加synchronized关键字
复制内容到剪贴板
代码:
public synchronized void f() {
//do something
}
2、synchronized同步代码块
复制内容到剪贴板
代码:
synchronized (mutex) {
// do something
}
对于这两种方式又应该着重区分是否为“
static
”的,因为static的成员是属于类的,非staitc的是属于具体实例的,所以在使用synchronized时应该注意方法或选择的同步变量是否为static的,如下代码:
复制内容到剪贴板
代码:
public class SyncTest {
private Object mutex = new Object();
public synchronized void f1() {
synchronized (mutex) {
System.err.println("nonstatic method f1....");
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SycnThread thread1 = new SycnThread(new SyncTest());
SycnThread thread2 = new SycnThread(new SyncTest());
SycnThread thread3 = new SycnThread(new SyncTest());
thread1.start();
thread2.start();
thread3.start();
}
}
class SycnThread extends Thread {
private SyncTest st;
public SycnThread(SyncTest st) {
this.st = st;
}
@Override
public void run() {
while (true) {
st.f1();
}
}
}
在main方法,创建thread1、2、3三个线程,它们都调用SyncTest的f1()方法,而方法f1()使用mutex(非static)来做同步变量,如果你的意图是实现这3个线程对方法f1的同步,那么运行的结果会让你大失所望,因为这样根本就无法使得这3个线程同步,原因在于:mutex是一个非static的成员变量,也就是说每new SyncTest(),它们的mutex变量都是不相同的。这样,对于上面这个程序来说,意味着每一个线程都使用了一个mutex来做它们各自的不同变量,如果希望上面3个线程同步,可以把mutex改为static或在创建SycnThread时,传入的SyncTest都为同一个对象即可。
还有当使用String常量或全局变量时都应该引起注意。
【java.util.concurrent.locks下的锁实现同步】
自JDK1.5以为,Java提供了java.util.concurrent这个并发包,在它的子包locks中,提供了一系列关于锁的抽象的类。主要有两种锁ReentrantLockReentrantReadWriteLock,而其他的两个类,都是“辅助”类,如AbstractQueuedSynchronizer就是一个用于实现特殊规则锁的抽象类,ReentrantLock和ReentrantReadWriteLock内部都有一个继承了该抽象类的内部类,用于实现特定锁的功能。下文主要介绍:ReentrantLock和ReentrantReadWriteLock,ReentrantReadWriteLock与ReentrantLock基本一样,只是ReentrantReadWriteLock实现了特殊规则(读写锁),在ReentrantReadWriteLock中有两个内部类ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock(实际上不止两个内部类,还有实现AbstractQueuedSynchronizer的Sync等等),这两个类分别可以使用ReentrantReadWriteLock的readLock()和writeLock()返回,该读写锁的规则是:只要没有writer,读取锁定可以由多个reader 线程同时保持,而写入锁定是独占的。
先介绍
可重入的锁ReentrantLock:使用ReentrantLock锁最简单的一个例子:
复制内容到剪贴板
代码:
Lock lock = new ReentrantLock();
try {
lock.lcok();
// do something
} finally {
lock.unlock();
}
上面这段代码,首先创建了一个lock,然后调用它的lock()方法,开启锁定,在最后调用它的unlock()解除锁定。值得注意的时,一般在使用锁时,都应该按上面的风格书写代码,即lock.unlock()最好放在finally块,这样可以防止,执行do something时发生异常后,导致锁永远无法被释放。注意:lock与unlock一定要
配对。
到此,还没发现Lock与synchronized有什么不同,Lock与synchronized不同之处主要体现在Lock比synchronized更灵活得多,而这些灵活又主要体现在如下的几个方法
//lock()
tryLock()
tryLock(long timeout, TimeUnit timeUnit)
lockInterruptibly()
//unlock()
A、
trylock()方法
:如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
B、
tryLock(long timeout, TimeUnit timeUnit)方法
:如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
是不是比synchronized灵活就体现出来了,打个不是很恰当的比分:你现在正在忙于工作,突然感觉内急,于是你跑向洗手间,到门口发现一个“清洁中,暂停使用”的牌牌。没办法,工作又忙,所以你只好先放弃去洗手间回去忙工作,可能如此反复,终于你发现可以进了,于是......
像这样的场景用synchronized你怎么实现?没办法,如果synchronized,当你发现洗手间无法暂时无法进入时,就只能乖乖在门口干等了。而使用trylock()呢,首先你试着去洗手间,发现暂时无法进入(trylock返回false),于是你继续忙你的工作,如此反复,直到可以进入洗手间为止(trylock返回true)。甚至,你非常急,你可以尝试性的在门口等20秒,不行再去忙工作(trylock(20, TimeUnit.SECONDS);)。
C、
lockInterruptibly()方法
lockInterruptibly()方法的执行如下:
如果该锁定没有被另一个线程保持,则获取该锁定并立即返回,将锁定的保持计数设置为 1。
如果当前线程已经保持此锁定,则将保持计数加 1,并且该方法立即返回。
如果锁定被另一个线程保持,则出于线程调度目的,禁用当前线程,并且在发生以下两种情况之一以前,该线程将一直处于休眠状态:
a、锁定由当前线程获得;
b、或者其他某个线程中断当前线程。
如果当前线程获得该锁定,则将锁定保持计数设置为1。
如果当前线程:
a、在进入此方法时已经设置了该线程的中断状态;
b、或者在等待获取锁定的同时被中断。
则抛出 InterruptedException,并且清除当前线程的已中断状态。
即lockInterruptibly()方法允许在等待时由其它线程调用它的Thread.interrupt方法来中断等待而直接返回,这时不再获取锁,而会抛出一个InterruptedException。
可重入的读写锁ReentrantReadWriteLock
该锁的机制特性总结如下:
复制内容到剪贴板
代码:
(a).重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。
(b).WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持 有。反过来ReadLock想要升级为WriteLock则不可能,为什么?参看(a),呵呵.
(c).ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高 读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。
(d).不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。
(e).WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出 UnsupportedOperationException异常。
下面还是写个小例子说明部分内容:
复制内容到剪贴板
代码:
public class ReentrantReadWriteLockSample {
public static void main(String[] args) {
testReadLock();
// testWriteLock();
}
public static void testReadLock() {
final ReadWriteLockSampleSupport support = new ReadWriteLockSampleSupport();
support.initCache();
Runnable runnable = new Runnable() {
public void run() {
support.get("test");
}
};
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(new Runnable() {
public void run() {
support.put("test", "test");
}
}).start();
}
public static void testWriteLock() {
final ReadWriteLockSampleSupport support = new ReadWriteLockSampleSupport();
support.initCache();
new Thread(new Runnable() {
public void run() {
support.put("key1", "value1");
}
}).start();
new Thread(new Runnable() {
public void run() {
support.put("key2", "value2");
}
}).start();
new Thread(new Runnable() {
public void run() {
support.get("key1");
}
}).start();
}
}
class ReadWriteLockSampleSupport {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private volatile boolean completed;
private Map cache;
public void initCache() {
readLock.lock();
if(!completed) {
// Must release read lock before acquiring write lock
readLock.unlock(); // (1)
writeLock.lock(); // (2)
if(!completed) {
cache = new HashMap(32);
completed = true;
}
// Downgrade by acquiring read lock before releasing write lock
readLock.lock(); // (3)
writeLock.unlock(); // (4) Unlock write, still hold read
}
System.out.println("empty? " + cache.isEmpty());
readLock.unlock();
}
public String get(String key) {
readLock.lock();
System.out.println(Thread.currentThread().getName() + " read.");
startTheCountdown();
try{
return cache.get(key);
}
finally{
readLock.unlock();
}
}
public String put(String key, String value) {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + " write.");
startTheCountdown();
try{
return cache.put(key, value);
}
finally {
writeLock.unlock();
}
}
/**
* A simple countdown,it will stop after about 5s.
*/
public void startTheCountdown() {
long currentTime = System.currentTimeMillis();
for(;;) {
long diff = System.currentTimeMillis() - currentTime;
if(diff > 5000) {
break;
}
}
}
}
复制内容到剪贴板
代码:
这个例子改造自JDK的API提供的示例,其中ReadWriteLockSampleSupport辅助类负责维护一个Map,当然前提是这个Map大部分的多线程 下都是读取,只有很少的比例是多线程竞争修改Map的值。其中的initCache()简单的说明了特性(a),(b).在这个方法中如果把注释(1)和(2) 处的代码调换位置,就会发现轻而易举的死锁了,当然是因为特性(1)的作用了。而注释(3),(4)处的代码位置则再次证明了特性 (a),并 且有力的反映了特性(b)--WriteLock在cache初始化完毕之后,降级为ReadLock。另外get(),put()方法在线程获取锁之后会在方法中呆上近 5s的时间。
ReentrantReadWriteLockSample中的两个静态测试方法则分别测试了ReadLock和WriteLock的排斥性。testReadLock()中,开启三个线程 ,前两者试图获取ReadLock而后者去获取WriteLock。执行结果可以看到:ReadWriteLockSampleSupport的get()方法中的打印结果在前两个 线程中几乎同时显示,而put()中的打印结果则要等上近5s。这就说明了,ReadLock可以多线程持有并且排斥WriteLock的持有线程。 testWriteLock()中,也开启三个线程。前两个是去获取WriteLock,最后一个获取ReadLock。执行的结果是三个打印结果都有近5s的间隔时 间,这说明了WriteLock是独占的,比较独!
http://blog.youkuaiyun.com/smcwwh/article/details/7193666
关键字:synchronized、java.util.concurrent.locks.Lock、同步、并发、锁
一、【引言】
JDK1.5之前,实现同步主要是使用synchronized,而在JDK1.5中新增了java.util.concurrent包及其两个子包locks和atomic,其中子包locks中定义了系列关于锁的抽象的类。本文主要介绍java.util.concurrent.locks的使用及其与synchronized两种方式实现同步的异同。
二、【synchronized同步】
synchronized相信很多熟悉J2SE的人都不会对这个关键字陌生,它用于实现多个线程之间的同步,一般有两种使用方式:
1、在方法上加synchronized关键字
- public synchronized void f() {
-
- }
2、synchronized同步代码块
对于这两种方式又应该着重区分是否为“
static
”的,因为static的成员是属于类的,非staitc的是属于具体实例的,所以在使用synchronized时应该注意方法或选择的同步变量是否为static的,如下代码:
- package test.lock;
-
-
-
-
-
- public class SyncTest {
-
- private Object mutex = new Object();
-
- public synchronized void f1() {
- synchronized (mutex) {
- System.err.println("nonstatic method f1....");
- try {
- Thread.sleep(2 * 1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
-
- public static void main(String[] args) {
- SycnThread thread1 = new SycnThread(new SyncTest());
- SycnThread thread2 = new SycnThread(new SyncTest());
- SycnThread thread3 = new SycnThread(new SyncTest());
- thread1.start();
- thread2.start();
- thread3.start();
- }
-
- }
-
- class SycnThread extends Thread {
-
- private SyncTest st;
-
- public SycnThread(SyncTest st) {
- this.st = st;
- }
-
- @Override
- public void run() {
- while (true) {
- st.f1();
- }
- }
- }
在main方法,创建thread1、2、3三个线程,它们都调用SyncTest的f1()方法,而方法f1()使用mutex(非static)来做同步变量,如果你的意图是实现这3个线程对方法f1的同步,那么运行的结果会让你大失所望,因为这样根本就无法使得这3个线程同步,原因在于:mutex是一个非static的成员变量,也就是说每new SyncTest(),它们的mutex变量都是不相同的。这样,对于上面这个程序来说,意味着每一个线程都使用了一个mutex来做它们各自的不同变量,如果希望上面3个线程同步,可以把mutex改为static或在创建SycnThread时,传入的SyncTest都为同一个对象即可。
还有当使用String常量或全局变量时都应该引起注意,
Java线程同步小陷阱,你掉进去过吗?
。
三、【java.util.concurrent.locks下的锁实现同步】
自JDK1.5以为,Java提供了java.util.concurrent这个并发包,在它的子包locks中,提供了一系列关于锁的抽象的类。主要有两种锁ReentrantLockReentrantReadWriteLock,而其他的两个类,都是“辅助”类,如AbstractQueuedSynchronizer就是一个用于实现特殊规则锁的抽象类,ReentrantLock和ReentrantReadWriteLock内部都有一个继承了该抽象类的内部类,用于实现特定锁的功能。下文主要介绍:ReentrantLock和ReentrantReadWriteLock
1、可重入的锁ReentrantLock
使用ReentrantLock锁最简单的一个例子:
- Lock lock = new ReentrantLock();
- try {
- lock.lcok();
-
- } finally {
- lock.unlock();
- }
上面这段代码,首先创建了一个lock,然后调用它的lock()方法,开启锁定,在最后调用它的unlock()解除锁定。值得注意的时,一般在使用锁时,都应该按上面的风格书写代码,即lock.unlock()最好放在finally块,这样可以防止,执行do something时发生异常后,导致锁永远无法被释放。
到此,还没发现Lock与synchronized有什么不同,Lock与synchronized不同之处主要体现在Lock比synchronized更灵活得多,而这些灵活又主要体现在如下的几个方法:
//lock()
tryLock()
tryLock(long timeout, TimeUnit timeUnit)
lockInterruptibly()
//unlock()
A、
trylock()方法
:如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
B、
tryLock(long timeout, TimeUnit timeUnit)方法
:如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
是不是比synchronized灵活就体现出来了,打个不是很恰当的比分:你现在正在忙于工作,突然感觉内急,于是你跑向洗手间,到门口发现一个“清洁中,暂停使用”的牌牌。没办法,工作又忙,所以你只好先放弃去洗手间回去忙工作,可能如此反复,终于你发现可以进了,于是......
像这样的场景用synchronized你怎么实现?没办法,如果synchronized,当你发现洗手间无法暂时无法进入时,就只能乖乖在门口干等了。而使用trylock()呢,首先你试着去洗手间,发现暂时无法进入(trylock返回false),于是你继续忙你的工作,如此反复,直到可以进入洗手间为止(trylock返回true)。甚至,你非常急,你可以尝试性的在门口等20秒,不行再去忙工作(trylock(20, TimeUnit.SECONDS);)。
C、
lockInterruptibly()方法
lockInterruptibly()方法的执行如下:
如果该锁定没有被另一个线程保持,则获取该锁定并立即返回,将锁定的保持计数设置为 1。
如果当前线程已经保持此锁定,则将保持计数加 1,并且该方法立即返回。
如果锁定被另一个线程保持,则出于线程调度目的,禁用当前线程,并且在发生以下两种情况之一以前,该线程将一直处于休眠状态:
a、锁定由当前线程获得;
b、或者其他某个线程中断当前线程。
如果当前线程获得该锁定,则将锁定保持计数设置为1。
如果当前线程:
a、在进入此方法时已经设置了该线程的中断状态;
b、或者在等待获取锁定的同时被中断。
则抛出 InterruptedException,并且清除当前线程的已中断状态。
即lockInterruptibly()方法允许在等待时由其它线程调用它的Thread.interrupt方法来中断等待而直接返回,这时不再获取锁,而会抛出一个InterruptedException。
D、lockInterruptibly()方法源码介绍
- public void lockInterruptibly() throws InterruptedException {
- sync.acquireInterruptibly(1);
- }
a、首先lockInterruptibly调用了内部类sync的acquireInterruptibly(1)方法,这个sync就是前面提到的AbstractQueuedSynchronizer的子类
- abstract static class Sync extends AbstractQueuedSynchronizer {
-
- }
- public final void acquireInterruptibly(int arg)
- throws InterruptedException {
- if (Thread.interrupted())
- throw new InterruptedException();
- if (!tryAcquire(arg))
- doAcquireInterruptibly(arg);
- }
b、在sync的acquireInterruptibly方法中,首先检查当前现场是否已经中断,如果已经中断,抛出InterruptedException异常,否则调用调用sync的doAcquireInterruptibly方法。
- private void doAcquireInterruptibly(int arg)
- throws InterruptedException {
- final Node node = addWaiter(Node.EXCLUSIVE);
- boolean failed = true;
- try {
- for (;;) {
- final Node p = node.predecessor();
- if (p == head && tryAcquire(arg)) {
- setHead(node);
- p.next = null;
- failed = false;
- return;
- }
-
- if (shouldParkAfterFailedAcquire(p, node) &&
- parkAndCheckInterrupt())
- throw new InterruptedException();
- }
- } finally {
- if (failed)
- cancelAcquire(node);
- }
- }
c、在sync的方法doAcquireInterruptibly中,关键在于检测到中断则直接退出循环(不在等待获取锁),而是直接抛出InterruptedException异常,最后在finally里调用cancelAcquire取消获锁操作。
E、除了这些方法之外,ReentrantLock还提供了很多实用的方法,这里就不再一一讲述
对于Lock,还有一个
特别值得注意
的地方,请看下面的代码:
- Lock lock = new ReentrantLock();
-
- try {
- lock.lock();
- lock.lock();
-
- } finally {
- lock.unlock();
- }
-
可以看到上面,上面代码调用lock()方法和调用unlock()方法的次数不同,这样的一段代码执行完后,别的线程是否已经可以获取该lock锁呢?
- package test.mult;
-
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
-
-
-
-
-
-
- public class Test {
-
- private String name;
-
- public Test(String name) {
- this.name = name;
- }
-
- public static void main(String[] args) {
-
- Lock lock = new ReentrantLock(true);
- MyThread t1 = new MyThread(lock, new Test("test1"));
- MyThread t2 = new MyThread(lock, new Test("test2"));
- t1.start();
- t2.start();
- }
-
- private static class MyThread extends Thread {
- Lock lock = null;
- Test test = null;
-
- public MyThread(Lock lock, Test test) {
- this.lock = lock;
- this.test = test;
- }
-
- @Override
- public void run() {
- while (true) {
- try {
-
- lock.lock();
- lock.lock();
- System.err.println(test.name + " locked...");
- try {
- Thread.sleep(3 * 1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- } finally {
- lock.unlock();
- }
- }
- }
-
- }
- }
运行的结果:
- test1 locked...
- test1 locked...
- test1 locked...
- test1 locked...
- test1 locked...
- test1 locked...
永远都是持有test1这个类的线程才能获取锁,其实是第一个获取锁的线程,他永远都拿着锁不放。
所以在使用Lock的时候,lock与unlock一定要
配对
。
2、可重入的读写锁ReentrantReadWriteLock
该锁的用法与ReentrantLock基本一样,只是ReentrantReadWriteLock实现了特殊规则(读写锁),在ReentrantReadWriteLock中有两个内部类ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock(实际上不止两个内部类,还有实现AbstractQueuedSynchronizer的Sync等等),这两个类分别可以使用ReentrantReadWriteLock的readLock()和writeLock()返回,该读写锁的规则是:只要没有writer,读取锁定可以由多个reader 线程同时保持,而写入锁定是独占的。下面通过一个简单的例子来了解它:
- package test.mult;
-
- import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-
-
-
-
-
- public class ReadWriteLockTest {
-
- static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
-
- public static void main(String[] args) {
-
-
-
-
-
-
-
-
- MyThread t1 = new MyThread(0, "t1");
- MyThread t2 = new MyThread(0, "t2");
- MyThread t3 = new MyThread(1, "t3");
- t1.start();
- t2.start();
- t3.start();
- }
-
- private static class MyThread extends Thread {
-
- private int type;
-
- private String threadName;
-
- public MyThread(int type, String threadName) {
- this.threadName = threadName;
- this.type = type;
- }
-
- @Override
- public void run() {
- while (true) {
- if (type == 0) {
-
- ReentrantReadWriteLock.ReadLock readLock = null;
- try {
- readLock = lock.readLock();
- readLock.lock();
- System.err.println("to read...." + threadName);
- try {
- Thread.sleep(5 * 1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- } finally {
- readLock.unlock();
- }
- } else {
-
- ReentrantReadWriteLock.WriteLock writeLock = null;
- try {
- writeLock = lock.writeLock();
- writeLock.lock();
- System.err.println("to write...." + threadName);
- try {
- Thread.sleep(5 * 1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- } finally {
- writeLock.unlock();
- }
- }
- }
- }
- }
- }
3、AbstractQueuedSynchronizer:如果需要自己实现一些特殊规则的锁,可以通过拓展该类来实现。
23.锁的等级:方法锁(对象锁)、对象锁、类锁、私有锁
类锁和对象锁不会产生竞争,二者的加锁方法不会相互影响。
私有锁和对象锁也不会产生竞争,二者的加锁方法不会相互影响
synchronized直接加在方法上和synchronized(this)都是对当前对象加锁,二者的加锁方法够成了竞争关系,同一时刻只能有一个方法能执行
24.写出生产者消费者模式
1、首先写生产者消费者的类
package sunhuaili;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 生产者消费者例子,该程序模拟了一个房屋,房子只能容纳100人 如果房子中没有人,是不会有人从门口出来的
* 如果房子中满了100人,外面的人需要等待屋子里的人走出门口 才能进入 下面开始写代码
*
* @author sunhuaili
*
*/
public class Door {
private int persons = 0;// 房子中开始是没有人的
private Lock lock = new ReentrantLock();
private Condition empty = lock.newCondition();
private Condition full = lock.newCondition();
public void enter() throws InterruptedException {// 进入一个人
lock.lock();
try {
while (persons == 100) {
full.await();
}
persons++;
System.out.println("进入一个人,当前人数" + persons);
full.signalAll();
} finally {
lock.unlock();
}
}
public void out() throws InterruptedException {
lock.lock();
try {
while (persons == 0) {
empty.await();
}
persons--;
System.out.println("出去一个人,当前人数" + persons);
empty.signalAll();
} finally {
lock.unlock();
}
}
}
2、编写生产者消费者任务
package sunhuaili;
public class EnterTask implements Runnable {
private Door door ;
public EnterTask(Door door){
this.door = door;
}
@Override
public void run() {
try {
door.enter();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package sunhuaili;
public class OutterTask implements Runnable {
private Door door ;
public OutterTask(Door door){
this.door = door;
}
@Override
public void run() {
try {
door.out();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3、写测试类
package sunhuaili;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DoorTest {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(200);
Door door = new Door();
for(int i=0;i<200;i++){
service.execute(new EnterTask(door));
service.execute(new OutterTask(door));
}
service.shutdown();
}
}
4、运行结果
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
进入一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
出去一个人,当前人数2
进入一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
出去一个人,当前人数2
进入一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
进入一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
进入一个人,当前人数7
出去一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
出去一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
出去一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
进入一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
出去一个人,当前人数2
出去一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
进入一个人,当前人数3
进入一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
出去一个人,当前人数3
出去一个人,当前人数2
出去一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
进入一个人,当前人数3
出去一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
进入一个人,当前人数3
进入一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
出去一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
进入一个人,当前人数3
进入一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
进入一个人,当前人数6
进入一个人,当前人数7
进入一个人,当前人数8
进入一个人,当前人数9
进入一个人,当前人数10
出去一个人,当前人数9
出去一个人,当前人数8
出去一个人,当前人数7
出去一个人,当前人数6
出去一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
出去一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
出去一个人,当前人数2
出去一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
进入一个人,当前人数3
出去一个人,当前人数2
进入一个人,当前人数3
出去一个人,当前人数2
出去一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
出去一个人,当前人数0
进入一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
进入一个人,当前人数3
出去一个人,当前人数2
进入一个人,当前人数3
出去一个人,当前人数2
进入一个人,当前人数3
出去一个人,当前人数2
出去一个人,当前人数1
进入一个人,当前人数2
进入一个人,当前人数3
出去一个人,当前人数2
进入一个人,当前人数3
进入一个人,当前人数4
出去一个人,当前人数3
进入一个人,当前人数4
进入一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
出去一个人,当前人数4
进入一个人,当前人数5
进入一个人,当前人数6
出去一个人,当前人数5
出去一个人,当前人数4
出去一个人,当前人数3
出去一个人,当前人数2
出去一个人,当前人数1
出去一个人,当前人数0
生产者/消费者问题的多种Java实现方式
实质上,很多后台服务程序并发控制的基本原理都可以归纳为生产者/消费者模式,而这是恰恰是在本科操作系统课堂上老师反复讲解,而我们却视而不见不以为然的。在博文《一种面向作业流(工作流)的轻量级可复用的异步流水开发框架的设计与实现》中将介绍一种生产者/消费者模式的具体应用。
生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品。解决生产者/消费者问题的方法可分为两类:(1)采用某种机制保护生产者和消费者之间的同步;(2)在生产者和消费者之间建立一个管道。第一种方式有较高的效率,并且易于实现,代码的可控制性较好,属于常用的模式。第二种管道缓冲区不易控制,被传输数据对象不易于封装等,实用性不强。因此本文只介绍同步机制实现的生产者/消费者问题。
同步问题核心在于:如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用信号或加锁机制,保证资源在任意时刻至多被一个线程访问。Java语言在多线程编程上实现了完全对象化,提供了对同步机制的良好支持。在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。
(1)wait() / notify()方法
(2)await() / signal()方法
(3)BlockingQueue阻塞队列方法
(4)PipedInputStream / PipedOutputStream
本文只介绍最常用的前三种,第四种暂不做讨论,有兴趣的读者可以自己去网上找答案。
一、wait() / notify()方法
wait() / nofity()方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。
wait()方法:当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等等状态,让其他线程执行。
notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。
光看文字可能不太好理解,咱来段代码就明白了:
看完上述代码,对wait() / notify()方法实现的同步有了了解。你可能会对Storage类中为什么要定义public void produce(int num);和public void consume(int num);方法感到不解,为什么不直接在生产者类Producer和消费者类Consumer中实现这两个方法,却要调用Storage类中的实现呢?淡定,后文会有解释。我们先往下走。
二、await() / signal()方法
在JDK5.0之后,Java提供了更加健壮的线程处理机制,包括同步、锁定、线程池等,它们可以实现更细粒度的线程控制。await()和signal()就是其中用来做同步的两种方法,它们的功能基本上和wait() / nofity()相同,完全可以取代它们,但是它们和新引入的锁定机制Lock直接挂钩,具有更大的灵活性。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全。下面来看代码:
只需要更新仓库类Storage的代码即可,生产者Producer、消费者Consumer、测试类Test的代码均不需要进行任何更改。这样我们就知道为神马我要在Storage类中定义public void produce(int num);和public void consume(int num);方法,并在生产者类Producer和消费者类Consumer中调用Storage类中的实现了吧。将可能发生的变化集中到一个类中,不影响原有的构架设计,同时无需修改其他业务层代码。无意之中,我们好像使用了某种设计模式,具体是啥我忘记了,啊哈哈,等我想起来再告诉大家~
三、BlockingQueue阻塞队列方法
BlockingQueue是JDK5.0的新增内容,它是一个已经在内部实现了同步的队列,实现方式采用的是我们第2种await() / signal()方法。它可以在生成对象时指定容量大小。它用于阻塞操作的是put()和take()方法。
put()方法:类似于我们上面的生产者线程,容量达到最大时,自动阻塞。
take()方法:类似于我们上面的消费者线程,容量为0时,自动阻塞。
关于BlockingQueue的内容网上有很多,大家可以自己搜,我在这不多介绍。下面直接看代码,跟以往一样,我们只需要更改仓库类Storage的代码即可:
当然,你会发现这时对于public void produce(int num);和public void consume(int num);方法业务逻辑上的实现跟前面两个例子不太一样,没关系,这个例子只是为了说明BlockingQueue阻塞队列的使用。
有时使用BlockingQueue可能会出现put()和System.out.println()输出不匹配的情况,这是由于它们之间没有同步造成的。当缓冲区已满,生产者在put()操作时,put()内部调用了await()方法,放弃了线程的执行,然后消费者线程执行,调用take()方法,take()内部调用了signal()方法,通知生产者线程可以执行,致使在消费者的println()还没运行的情况下生产者的println()先被执行,所以有了输出不匹配的情况。
对于BlockingQueue大家可以放心使用,这可不是它的问题,只是在它和别的对象之间的同步有问题。
对于Java实现生产者/消费者问题的方法先总结到这里面吧,过几天实现一下C++版本的,接下来要马上着手于基于生产者/消费者模式的《异步工作流服务框架的设计与实现》,请持续关注本博客。