Java——线程锁,死锁,等待唤醒机制

Posted 想54256

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java——线程锁,死锁,等待唤醒机制相关的知识,希望对你有一定的参考价值。

一、线程锁

线程安全问题

其实,线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

由于线程休眠的特性,从哪休眠就从哪继续执行(一个线程的事情还没干完就被其他线程挤下去了),回来继续干就会导致操作的全局变量或静态变量出现问题。

为了解决这个问题,我们就需要让线程执行完毕(不能被其他线程挤下去),以下是几种解决办法。

1、同步代码块

保证代码块执行完毕,再切换线程。

公式:

synchronized(任意对象){

  线程要操作的共享数据

}

调用类

public class ThreadDemo {
    public static void main(String[] args) {
        Ticket tk = new Ticket();
        Thread t01 = new Thread(tk);
        Thread t02 = new Thread(tk);
        Thread t03 = new Thread(tk);

        t01.start();
        t02.start();
        t03.start();
    }
}

同步代码块

public class Ticket implements Runnable {
    //定义出售的票源
    private int ticket = 100;

    public void run() {
        while (true) {
            // 因为里面可以填任意对象,所以可以使用this(表示当前实例化的Ticket对象tk)
            synchronized (this) {
                //对票数判断,大于0,可以出售,变量--操作
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + " 出售第 " + ticket--);
                }
            }
        }
    }
}

同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。

2、同步方法

还可以将需要同步的代码块,抽出来一个方法,使用synchronized字段修饰。

public synchronized void method(){

  可能会产生线程安全问题的代码

}

同步方法

public class Ticket implements Runnable {
    //定义出售的票源
    private int ticket = 100;

    public void run() {
        while (true) {
            func();
        }
    }

    private synchronized void func() {
        //对票数判断,大于0,可以出售,变量--操作
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + " 出售第 " + ticket--);
        }
    }
}

同步方法中的锁对象是this,如果是静态同步方法的话同步锁是本类类名.class

3、Lock接口

public class Ticket implements Runnable{

    //定义出售的票源
    private int ticket = 100;
    //在类的成员位置,创建Lock接口的实现类对象
    private Lock lock = new ReentrantLock();

    public void run(){
        while(true){
            //调用Lock接口方法lock获取锁
            lock.lock();
            //对票数判断,大于0,可以出售,变量--操作
            if( ticket > 0){
                try{
                    //执行可能会引发线程安全问题的代码
                    System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
                }catch(Exception ex){

                }finally{
                    //释放锁,调用Lock接口方法unlock
                    lock.unlock();
                }
            }
        }
    }
}

二、死锁

同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。

三、等待唤醒机制

线程之间的通信:

多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即——等待唤醒机制。

等待唤醒机制

等待唤醒机制所涉及到的方法:

其实,所谓唤醒的意思就是让线程池中的线程具备执行资格。必须注意的是,这些方法都是在同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。

仔细查看JavaAPI之后,发现这些方法并不定义在Thread中,也没定义在Runnable接口中,却被定义在了Object类中,为什么这些操作线程的方法定义在Object类中?

因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。

 1 package cn.x5456.demo;
 2 
 3 public class ThreadDemo {
 4     public static void main(String[] args) {
 5         Resource r = new Resource();
 6 
 7         // 共享数据
 8         Input in = new Input(r);
 9         Output out = new Output(r);
10 
11         Thread tin = new Thread(in);
12         Thread tout = new Thread(out);
13 
14         tin.start();
15         tout.start();
16     }
17 }
ThreadDemo
 1 package cn.x5456.demo;
 2 
 3 public class Input implements Runnable{
 4     private Resource r;
 5     int i = 0;
 6 
 7     public Input(Resource r){
 8         this.r=r;
 9     }
10 
11 
12     public void run() {
13         while (true){
14             synchronized (r){   //要使用同一个对象来看着Input和Output两个同步方法(否则就各自走各自的了)
15                 if(r.flag){
16                     try {
17                         r.wait();   //使用同一个对象才能等待+启动
18                     } catch (InterruptedException e) {
19                         e.printStackTrace();
20                     }
21                 }
22                 if(i%2==0){
23                     r.name = "张三";
24                     r.sex = "男";
25                 }else{
26                     r.name = "lisi";
27                     r.sex = "nv";
28                 }
29                 i++;
30                 r.flag = true;
31                 r.notify();     //唤醒另一边
32             }
33         }
34     }
35 }
Input
 1 package cn.x5456.demo;
 2 
 3 public class Output implements Runnable{
 4     private Resource r;
 5 
 6     public Output(Resource r) {
 7         this.r = r;
 8     }
 9 
10 
11     @Override
12     public void run() {
13         while (true){
14             synchronized (r){
15                 if(!r.flag){
16                     try {
17                         r.wait();
18                     } catch (InterruptedException e) {
19                         e.printStackTrace();
20                     }
21                 }
22                 System.out.println(r.name+".."+r.sex);
23                 //标记改成false,唤醒对方线程
24                 r.flag = false;
25                 r.notify();
26             }
27         }
28     }
29 }
Output
1 package cn.x5456.demo;
2 
3 public class Resource {
4     String name;
5     String sex;
6     boolean flag = false;
7 }
Resource

 

以上是关于Java——线程锁,死锁,等待唤醒机制的主要内容,如果未能解决你的问题,请参考以下文章

死锁Lock锁等待唤醒机制线程组线程池定时器单例设计模式_DAY24

买卖包子案例——等待唤醒机制

多线程等待唤醒机制之生产消费者模式

多线程等待唤醒机制之生产消费者模式

线程间通信——等待唤醒机制

线程同步