多线程基础总结一--synchronized(1)

最近写关于并发的小应用,才发现真的该好好的正视java的多线程了。之前没有深入的掌握,用起来也是那么的吃力。作为J2SE里面为数不多的重要难点之一,多线程应用一直是我以敬畏的心态去尽量避开的,只是通过一些实例掌握一些简单的应用。这段时间会多用点时间去掌握,有需要写下来的我也会通过这种方式既分享又加深理解。
首先这篇只涉及基础的知识整理,对于并发包java.util.concurrent内的线程池和锁我会看情况在之后的总结中写点东西。对于进程的概念我们都很熟悉,它是应用程序级的隔离,不同的应用程序之间的进程几乎不共享任何资源。而线程则可以说是应用程序内的隔离,一种相对低级别的隔离。一个进程可以有多个线程,它们之间隔离的内容大致包括:a.自身的堆栈,b.程序计数器,c.局部变量;共享应用的内容大致包括:a.内存,b.文件句柄,c.进程状态等。线程不是Java自身的概念,它是操作系统底层的概念。Java作为一种应用语言把线程的操作通过API提升到应用开发的支持,但是在并发性的支持上并不是那么美好。
Java在设计时,每个对象都有一个隐式的锁,这个锁的使用则是通过synchronized关键字来显式的使用。在JDK5.0以后引用了java.util.concurrent.ReentrantLock作为synchronized之外的选择,配和Condition可以以一种条件锁的机制来管理并发的线程,之后的总结再介绍。提到synchronized,多数的初学者都知道Object的wait(),notify(),notifyAll()是配和其使用的,但是为什么要在同步内才能用对象的这些方法呢(不然抛IllegalMonitorStateException)?
我想因为没有synchronized让对象的隐式锁发挥作用,那么方法或者方法块内的线程在同一时间可能存在多个,假设wait()可用,它会把这些线程统统的加到wait set中等待被唤醒,这样永远没有多余的线程去唤醒它们。每个对象管理调用其wait(),notify()的线程,使得别的对象即使想帮忙也帮不上忙。这样的结果就是多线程永远完成不了多任务,基于此Java在设计时使其必须与synchronized一起使用,这样获得隐式锁的线程同一时间只有一个,当此线程被对象的wait()扔到wait set中时,线程会释放这个对象的隐式锁等待被唤醒的机会,这样的设计会大大降低死锁。另外同一个对象隐式锁作用下的多个方法或者方法块在没有锁的限制下可以同时允许多个线程在不同的方法内wait和notify,严重的竞争条件使得死锁轻而易举。所以Java设计者试图通过Monitor Object模式解决这些问题,每个对象都是Monitor用于监视拥有其使用权的线程。
但是synchronized这种获得隐式锁的方式本身也是有隐患问题的:a.不能中断正在试图获得锁的线程,b.试图获得锁时不能设定超时,c.每个锁只有一个条件太少。对于最后一项的设计前面提到的JDK5的方案是可以弥补的,一个ReentrantLock可以有多个Condition,每个条件管理获得对象锁满足条件的线程,通过await(),signalAll()使只关于Condition自己放倒的线程继续运行,或者放倒一些线程,而不是全部唤醒等等。但对于前两者的极端情况会出现死锁。下面的这个例子:
class DeadLockSample{
public final Object lock1 = new Object();
public final Object lock2 = new Object();

public void methodOne(){
synchronized(lock1){
...
synchronized(lock2){...}
}
}

public void methodTwo(){
synchronized(lock2){
...
synchronized(lock1){...}
}
}
}

假设场景:线程A调用methodOne(),获得lock1的隐式锁后,在获得lock2的隐式锁之前线程B进入运行,调用methodTwo(),抢先获得了lock2的隐式锁,此时线程A等着线程B交出lock2,线程B等着lock1进入方法块,死锁就这样被创造出来了。
以上的例子不直观的话,再看一个实例顺便看看wait()的缺陷:
import java.util.LinkedList;
import java.util.List;

/**
* User: yanxuxin
* Date: Dec 9, 2009
* Time: 5:58:39 PM
*/
public class DeadLockSample {
public static void main(String[] args) {
final WaitAndNotify wan = new WaitAndNotify();

Thread t1 = new Thread(new Runnable(){
public void run() {
wan.pop();
}
});

Thread t2 = new Thread(new Runnable(){
public void run() {
wan.push("a");
}
});

t1.start();
t2.start();
}
}

class WaitAndNotify {

final List<String> list = new LinkedList<String>();

public synchronized void push(String x) {
synchronized(list) {
list.add(x);
notify();
}
}

public synchronized Object pop() {
synchronized(list) {
if(list.size() <= 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return list.size();
}
}

}

上面的这个例子也会出现死锁,为什么呢?首先看WaitAndNotify这个类,在push和pop方法上有synchronized关键字,方法内部也有synchronized,那么当WaitAndNotify实例化时会有两个对象的隐式锁,一个是WaitAndNotify对象自身的,作用在方法上;另一个就是方法内部同步用到的list的。主线程开启两个线程t1和t2,t1进入pop方法此时list为空,它先后获得了wan和list的隐式锁,接着就被wait扔进wait set等待去了。注意这个wait()方法是谁的?答案是wan的,所以它释放了wan的隐式锁,但是把list的死死的抓着不放。此时t2终于得到了wan的隐式锁进入push方法,但是不幸的是list的隐式锁它这辈子也得不到了。。。

就是由于wait的设计是针对对象管理线程的,而又没有其他的可以类似栈的方式层层释放锁,导致死锁的杯具了。关于synchronized和wait(),notify(),notifyAll()的故事,我想我能瞎编的暂时这么多了,不然滔滔江水就又杯具了,哈哈。下面简单的讲讲java.lang.Thread的特色小故事。(待续...)

PS:"多线程基础总结六"算是本文的补遗了,呵呵。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值