多线程 同步与死锁问题

1.线程的同步
1)The code segments within a program that access the same object from separate, concurrent threads are called “critical sections”。这是临界区的概念。

2)同步的两种方式:同步块和同步方法。

3)每一个对象都有一个监视器,或者叫做锁。
当线程执行到synchronized的时候,检查传入的实参对象,并得到该对象的锁旗标。如果得不到,那么此线程就会被加入到一个与该对象的锁旗标相关联的等待线程池中,一直等到对象的锁旗标被归还,池中的等待线程就会得到该锁旗标,然后继续执行下去。当线程执行完成同步代码块,就会自动释放它占有的同步对象的锁旗标。一个用于synchronized语句中的对象称为监视器,当一个线程获得了synchronized(object)语句中的代码块的执行权,即意味着它锁定了监视器。

4)同步方法利用的是this所代表的对象的锁。
下面实例介绍代码块与方法间的同步。观察this的作用。
public class ThreadDemo6 {
public static void main(String[] args) {
ThreadTest t = new ThreadTest();
new Thread(t).start();
try {
Thread.sleep(1);//(1)
} catch (InterruptedException e) {
e.printStackTrace();
}
t.str=new String("method");
new Thread(t).start();
}
}

class ThreadTest implements Runnable {
private int tickets = 100;
private int flag=0;
String str = new String("");

public void run() {
if (str.equals("method")) {
while (flag==0) {
sale();
}
} else {
while (true) {
synchronized (this) {//synchronized (str)//(2)
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " is saling ticket " + tickets--);
} else return;
}
}
}
}

public synchronized void sale() {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("track in method sale.");
System.out.println(Thread.currentThread().getName()
+ " is saling ticket " + tickets--);
} else flag = 1;
}
}注1,如果不使主线程sleep,很可能两个新建线程都执行同步方法(sale)中的代码。因为,产生并启动第一个线程,这个线程不见得马上开始运行,CPU可能还在原来的main线程上运行,并将str变量设置为”method”,等到第一个线程真正开始运行时,此刻检查到str的值为”method”,所以它将运行sale方法。
注2,如果使用synchronized (str),则两个线程不会同步。

5)要时刻考虑CPU会随时切换线程的情况。

6)同步是以牺牲程序的性能为代价的。

1.1同步代码块
ThreadDemo2.java
class ThreadTest implements Runnable {
private int tickets = 100;
String str = new String("");

public void run() {
while (true) {
synchronized (str) {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " is saling ticket " + tickets--);
} else return;
}
}
}
}

public class ThreadDemo2 {
public static void main(String[] args){
ThreadTest t=new ThreadTest();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
1.2同步方法
ThreadDemo3.java
class ThreadTest implements Runnable {
private int tickets = 100;
private int flag=0;

public void run() {
while (flag==0) sale();
}

public synchronized void sale() {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " is saling ticket " + tickets--);
}else flag=1;
}
}
public class ThreadDemo3 {
public static void main(String[] args){
ThreadTest t=new ThreadTest();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}在同一个类中,使用synchronized关键字定义的若干方法,可以在多个线程之间同步,当有一个线程进入了synchronized修饰的方法(获得监视器),其他线程就不能进入同一个对象的所有使用了synchronized修饰的方法,直到第一个线程执行完它所进入的synchronized修饰的方法为止(离开监视器)。

死锁是一个经典的多线程问题,因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成。假设有两个线程,分别代表两个饥饿的人,他们必须共享刀叉并轮流吃饭。他们都需要获得两个锁:共享刀和共享叉的锁。假如线程 "A" 获得了刀,而线程 "B" 获得了叉。线程 A 就会进入阻塞状态来等待获得叉,而线程 B 则阻塞来等待 A 所拥有的刀。这只是人为设计的例子,但尽管在运行时很难探测到,这类情况却时常发生。虽然要探测或推敲各种情况是非常困难的,但只要按照下面几条规则去设计系统,就能够避免死锁问题:

  让所有的线程按照同样的顺序获得一组锁。这种方法消除了 X 和 Y 的拥有者分别等待对方的资源的问题。

  将多个锁组成一组并放到同一个锁下。前面死锁的例子中,可以创建一个银器对象的锁。于是在获得刀或叉之前都必须获得这个银器的锁。

  将那些不会阻塞的可获得资源用变量标志出来。当某个线程获得银器对象的锁时,就可以通过检查变量来判断是否整个银器集合中的对象锁都可获得。如果是,它就可以获得相关的锁,否则,就要释放掉银器这个锁并稍后再尝试。

  最重要的是,在编写代码前认真仔细地设计整个系统。多线程是困难的,在开始编程之前详细设计系统能够帮助你避免难以发现死锁的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值