Java线程(初级)——synchronized、死锁、wait、notify详解

一、Java线程状态


start之后是进去就绪状态,等待CPU的调度,而不是立即进去运行状态。当run函数返回,线程就终止了。


二、Thread.sleep

谁调用Thread.sleep方法谁休息,自己进入阻塞状态。Thread.sleep方法属于线程自己控制自己的状态。如果线程在sleep之前拥有某个对象的锁,则它在sleep的时候还会抱着这把锁不放。


三、obj.interrupt

obj是线程对象,其它线程调用obj的interrupt方法,将会打断obj线程的睡眠,引发obj线程的异常。

obj.stop,其它线程调用obj的stop方法可以让obj线程直接退出,粗暴直接,不要轻易使用(可能会导致某些打开的资源来不及关闭线程就退出了)。


四、join合并线程

Thread t1=new Thread(Runnable r);

t1.start();

t1.join();      //当前线程阻塞在此处

正在执行的线程调用t1的join方法将会合并t1线程,即相当于当前线程在方法调用(当前线程阻塞),直到等到t1线程执行完毕,当前线程才会继续执行。

yield:当前在执行的线程让出一次CPU时间给其它线程执行,自己立即进入就绪状态,等待CPU的再次调用。

(Thread.yield()谁调用此方法谁就让出CPU时间,但并不能保证让出后其它线程可以抢占到CPU时间。)
threadObj.setDaemon()方法将该线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,JVM会退出(该方法必须在线程启动前调用)。


五、synchronized互斥锁

每个对象只有一把锁(每一个对象对应一把锁),多个线程获取同一把锁时才会产生互斥效果,多个线程获取不同的锁时不会产生互斥效果。synchronized互斥锁锁住的是对象而不是代码。

同步方法和同步代码块synchronized(this){  //……  }效果一样,锁住的都是类的实例,同一时刻对于每一个类实例,其所有声明为synchronized的同步方法或被synchronized(this){  //……  }锁定的代码块至多只有一个处于可执行状态(因为同一时刻至多只有一个线程可获得该实例对应的锁),所有没有锁定的方法或代码块均可被多个线程同时执行。
同步代码块synchronized(obj){  //……  }锁定obj对象时,同一时刻对于每一个锁定的obj的同步代码块至多只有一个处于可执行状态(obj对象的锁只有一把,同一时刻只有一个线程可以获得该对象的锁),所有没有处于synchronized(obj){  //……  }同步代码块的代码均可被多个线程同时执行。
注意:
(1)同步代码块的锁的对象可以是 任意的对象,包括this对象。
(2)同步方法锁的对象是 this对象
(3)静态同步方法锁定的对象是类的字节码文件对象(ClassName.class)。因为静态方法中没有类的实例(this),所以不可能是this对象。


六、死锁

所谓死锁: 是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称程序处于死锁状态或程序产生了死锁,这些永远在互相等待的线程称为死锁线程。 由于资源占用是互斥的,当某个线程锁住某个资源后,使得有关线程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象:死锁。

假设有两个线程a和b,线程a和b结束的条件是分别同时锁住obj1和obj2对象,如果a已经获取到了obj1的锁正在努力获取obj2的锁但是obj2的锁已经被b获取,同时b也正在努力获取obj1的锁,因为只有线程结束才能释放锁,而正常结束的条件是一个线程必须同时获取到两个锁,因此a和b都得不到另外一个锁都无法结束,陷入了永久的等待状态,此时程序产生死锁。

死锁程序例如:

import java.util.logging.Level; 
import java.util.logging.Logger;

public class DiedSynchronized {
    public static void main(String[] args) {
        Tellme m=new Tellme();
        Thread t1=new Thread(m,"t1");
        Thread t2=new Thread(m,"t2");
        System.out.println(Thread.currentThread().getName()+":t1和t2线程已经启动……");
        t1.start();
        t2.start();
        try {
            Thread.sleep((long)10000);
        } catch (InterruptedException ex) { }
        if(t1.isAlive() && t2.isAlive()) {
            System.out.println(Thread.currentThread().getName()+":10s内,t1和t2线程均没有正常结束,证明t1和t2线程死锁!");
            System.exit(0);
        }
        else
            System.out.println(Thread.currentThread().getName()+":t1和t2线程已经结束,证明没有死锁!");
    }
}
class Tellme implements Runnable {
    private Integer _number = new Integer(100);
    public boolean _flag = true;
    public void run() {
        if(_flag) {
            _flag=false;
            synchronized(this) {
                System.out.println(Thread.currentThread().getName()+":已经获取到this锁,正在获取Number锁……");
                try {
                    Thread.sleep((long)1000);
                } catch (InterruptedException ex) { }
                synchronized(_number) {
                    System.out.println(Thread.currentThread().getName()+":this锁和Number锁同时获取完毕!");
                }
            }
        }else {
            synchronized(_number) {
                System.out.println(Thread.currentThread().getName()+":已经获取到Number锁,正在获取this锁……");
                try {
                    Thread.sleep((long)1000);
                } catch (InterruptedException ex) { }
                synchronized(this) {
                    System.out.println(Thread.currentThread().getName()+":this锁和Number锁同时获取完毕!");
                }
            }
        }
    }
}


运行结果如下:


run:
main:t1和t2线程已经启动……
t2:已经获取到Number锁,正在获取this锁……
t1:已经获取到this锁,正在获取Number锁……
main:10s内,t1和t2线程均没有正常结束,证明t1和t2线程死锁!
成功构建 (总时间: 10 秒)


t1和t2线程分别拥有this锁和Number锁,同时努力获取对方的锁,造成最终谁也得不到,程序进入死锁状态。Thread.sleep((long)1000)只是起放大作用,即使没有睡眠1秒程序也有可能进入死锁状态。


七、wait、notify、notifyAll

线程可以在任意对象的监视器(锁)上阻塞(wait,前提是获取到该对象的锁),也可以在唤醒任意一个wait在某个对象的监视器上的线程(notify,前提是获取到该对象的锁)。“获取到某个对象的锁”,就像获取到某种资格一样,只有有了这种资格才能够让自己阻塞在该锁上面或者唤醒已经阻塞在该锁上的其它线程。由此可知,每个对象的监视器上面自愿wait和被notify的线程只和该对象有关。因为每个对象都具有锁,每个锁均不同,故wait和notify的方法调用要通过对象调用,所以wait和notify方法要在Object中声明。

由于线程在任意对象的监视器都可以wait自己和notify其它线程,因此用 Obj表示任意对象中的某个确定的对象,也包括this对象。线程调用Obj.wait(让自己wait在Obj的监视器上)和Obj.notify(唤醒任意一个已经wait在Obj的监视器上的线程)方法的前提是:已经获取到了Obj对象的锁,即 Obj.wait()和Obj.notify()应该放在synchronize(Obj){ }代码块的内部。如果没有获取到Obj对象的锁,就没有这种“资格”执行这样的操作。
方法介绍:
wait:让自己在Obj对象的监视器上(锁)等待。获得Obj锁的线程调用Obj.wait()方法将使本线程放弃Obj的锁进入等待阻塞状态(wait在Obj对象的监视器上),直到另一个获得Obj锁的线程调用notify()或者notifyAll()方法才能唤醒此线程,等到此线程再次获取到Obj锁方可从暂停处继续执行。
notify:获得Obj锁的线程调用Obj.notify()方法将随机唤醒一个wait在Obj监视器上的线程,本线程并不会立即释放锁而是等待同步代码块执行完毕后再释放Obj锁。
notifyAll:唤醒所有正在wait在Obj监视器上的线程。
sleep和wait的区别
sleep()是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,也就是说,在线程里面决定。好比如说,我要做的事情是 "点火->烧水->煮面",而当我点完火之后我不立即烧水,我要休息一段时间再烧。对于运行的主动权是由我的流程来控制。而wait(),这是由某个确定的对象来调用的,将这个对象理解成一个传话的人,当这个人在某个线程里面说"暂停!",也是 thisOBJ.wait(),这里的暂停是阻塞,还是"点火->烧水->煮饭",thisOBJ就好比一个监督我的人站在我旁边,本来该线程应该执行1后执行2,再执行3,而在2处被那个对象喊暂停,那么我就会一直等在这里而不执行3,但这个流程并没有结束,我一直想去煮饭,但还没被允许,直到那个对象在某个地方说"通知暂停的线程启动!",也就是thisOBJ.notify()的时候,那么我就可以煮饭了,这个被暂停的线程就会从暂停处继续执行。
生产者和消费者例子:
本例子使用的篮子对象的锁(在篮子内部为this锁),该锁为生产者和消费者共同竞争。

import java.util.Random; 
import java.util.logging.Level;
import java.util.logging.Logger;
/**
 * Producer_Customer Program for testing wait_notify_notifyAll
 *
 * @author 忘川
 */
public class Producer_Customer {
    public static void main(String[] args) {
        Basket basket = new Basket();
        Producer pro = new Producer(basket);
        Customer cus = new Customer(basket);
       
        Thread proth1 = new Thread(pro, "一");
        Thread proth2 = new Thread(pro, "二");
        Thread proth3 = new Thread(pro, "三");
        Thread proth4 = new Thread(pro, "四");
        Thread custh1 = new Thread(cus, "custh1");
        Thread custh2 = new Thread(cus, "custh2");
       
        proth1.start();
        proth2.start();
        proth3.start();
        proth4.start();
        custh1.start();
        custh2.start();
    }
}
class Producer implements Runnable {
    private Basket basket = null;
    public Producer(Basket basket) {
        this.basket = basket;
    }
    public void run() {
        Random r = new Random();
        for (int i = 0; i < 4; i++) {
            basket.put(new Apple(Thread.currentThread().getName() + "#" + i));
            try {
                Thread.sleep((long) r.nextDouble() * 1000);
            } catch (InterruptedException ex) { }
        }
    }
}
class Customer implements Runnable {
    private Basket basket = null;
    public Customer(Basket basket) {
        this.basket = basket;
    }
    public void run() {
        Random r = new Random();
        for (int i = 0; i < 8; i++) {
            basket.eat();
            try {
                Thread.sleep((long) r.nextDouble() * 1000);
            } catch (InterruptedException ex) { }
        }
    }
}
class Basket {
    private Apple[] basket = new Apple[3];
    private int index = 0;
    public synchronized void put(Apple a) {
        while (index == 2) {
            try {
                this.wait();
            } catch (InterruptedException ex) { }
        }
        this.notifyAll();
        basket[index++] = a;
        System.out.println("生产:" + a + " 篮子剩余=" + index);
    }
    public synchronized void eat() {
        while (index == 0) {
            try {
                this.wait();
            } catch (InterruptedException ex) { }
        }
        this.notifyAll();
        System.out.println("吃掉:" + basket[--index] + " 篮子剩余=" + index);
    }
}
class Apple {
    public String ID;
    public Apple(String id) {
        this.ID = id;
    }
    public String toString() {
        return ID;
    }
}


其中的一个运行结果如下:


run:
生产:三#0 篮子剩余=1
生产:四#0 篮子剩余=2
生产:一#0 篮子剩余=3
吃掉:一#0 篮子剩余=2
生产:二#0 篮子剩余=3
吃掉:二#0 篮子剩余=2
生产:三#1 篮子剩余=3
吃掉:三#1 篮子剩余=2
生产:一#1 篮子剩余=3
吃掉:一#1 篮子剩余=2
生产:一#2 篮子剩余=3
吃掉:一#2 篮子剩余=2
吃掉:四#0 篮子剩余=1
生产:二#1 篮子剩余=2
生产:三#2 篮子剩余=3
吃掉:三#2 篮子剩余=2
生产:二#2 篮子剩余=3
吃掉:二#2 篮子剩余=2
生产:二#3 篮子剩余=3
吃掉:二#3 篮子剩余=2
生产:三#3 篮子剩余=3
吃掉:三#3 篮子剩余=2
吃掉:二#1 篮子剩余=1
生产:四#1 篮子剩余=2
生产:一#3 篮子剩余=3
吃掉:一#3 篮子剩余=2
生产:四#2 篮子剩余=3
吃掉:四#2 篮子剩余=2
生产:四#3 篮子剩余=3
吃掉:四#3 篮子剩余=2
吃掉:四#1 篮子剩余=1
吃掉:三#0 篮子剩余=0
成功构建 (总时间: 0 秒)

从运行结果可以看出,每当篮子中苹果个数达到3个(篮子所能容纳的上线时),生产者线程就自愿wait在篮子的监视器上,转而让消费者线程先消费之后再唤醒生产者线程;每当篮子中的苹果个数为0时,消费者线程就会自愿wait在篮子的监视器上,转而让生产者线程先生产之后再唤醒消费者线程。篮子就像一个控制中心,生产过剩,生产线程就资源wait,转而让消费线程消费;反之亦然。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值