您的位置:首页 > 其它

我眼中多线程的锁

2016-01-21 16:25 465 查看
做了快6年的java项目了,仅用过一次多线程,而且都是架构组封装好的,仅需要调用即可,现在想起来,真是惭愧....近期在项目中又遇到一个需要用多线程处理的问题,因此仔细研究了一下...

------------------------------------------------------------------------------------------------------------------------------------

首先,了解什么是锁,举个简单的例子,你出门的时候会把门锁上,这叫给门这个对象上锁,然后回到家过后肯定会打开锁推门进屋,这叫给门这个对象解锁。在给对象上锁后,若不解锁,则其他人就无法使用这个对象,(假若你带一个朋友回家,假若你不开锁,朋友就无法推开门,推开门就是你朋友在使用你们家门这个对象),在这儿假若你和你朋友是二个线程,你朋友在等你开门的过程中,就是在线程等待,你是正在执行的线程,若你发现钥匙丢了开不了门,就会造成你朋友等待,造成线程执行超时(本来平时开门就两分钟时间),假若你一直都解决不了开锁的问题,你朋友就会一直等待,从而造成死锁...

在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释放锁。

上面就讲完了,若有问题,请留言。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: