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

一、线程锁

线程安全问题

p { margin-bottom: 0.25cm; direction: ltr; color: #000000; line-height: 120%; text-align: justify; widows: 0; orphans: 0 }
p.western { font-family: "Calibri", sans-serif; font-size: 10pt }
p.cjk { font-family: "宋体"; font-size: 10pt }
p.ctl { font-family: "Times New Roman", serif; font-size: 11pt }
a:link { color: #0000ff; text-decoration: none }

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

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

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

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--);
                }
            }
        }
    }
}

p { margin-bottom: 0.25cm; direction: ltr; color: #000000; line-height: 120%; text-align: justify; widows: 0; orphans: 0 }
p.western { font-family: "Calibri", sans-serif; font-size: 10pt }
p.cjk { font-family: "宋体"; font-size: 10pt }
p.ctl { font-family: "Times New Roman", serif; font-size: 11pt }
a:link { color: #0000ff; text-decoration: none }

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

2、同步方法

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

p { margin-bottom: 0.25cm; direction: ltr; color: #000000; line-height: 120%; text-align: justify; widows: 0; orphans: 0 }
p.western { font-family: "Calibri", sans-serif; font-size: 10pt }
p.cjk { font-family: "宋体"; font-size: 10pt }
p.ctl { font-family: "Times New Roman", serif; font-size: 11pt }
a:link { color: #0000ff; text-decoration: none }

public synchronized void method(){

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

}

p { margin-bottom: 0.25cm; direction: ltr; color: #000000; line-height: 120%; text-align: justify; widows: 0; orphans: 0 }
p.western { font-family: "Calibri", sans-serif; font-size: 10pt }
p.cjk { font-family: "宋体"; font-size: 10pt }
p.ctl { font-family: "Times New Roman", serif; font-size: 11pt }
a:link { color: #0000ff; text-decoration: none }

同步方法

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();
                }
            }
        }
    }
}

二、死锁

p { margin-bottom: 0.25cm; direction: ltr; color: #000000; line-height: 120%; text-align: justify; widows: 0; orphans: 0 }
p.western { font-family: "Calibri", sans-serif; font-size: 10pt }
p.cjk { font-family: "宋体"; font-size: 10pt }
p.ctl { font-family: "Times New Roman", serif; font-size: 11pt }
a:link { color: #0000ff; text-decoration: none }

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

三、等待唤醒机制

p { margin-bottom: 0.25cm; direction: ltr; color: #000000; line-height: 120%; text-align: justify; widows: 0; orphans: 0 }
p.western { font-family: "Calibri", sans-serif; font-size: 10pt }
p.cjk { font-family: "宋体"; font-size: 10pt }
p.ctl { font-family: "Times New Roman", serif; font-size: 11pt }
a:link { color: #0000ff; text-decoration: none }

线程之间的通信:

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

p { margin-bottom: 0.25cm; direction: ltr; color: #000000; line-height: 120%; text-align: justify; widows: 0; orphans: 0 }
p.western { font-family: "Calibri", sans-serif; font-size: 10pt }
p.cjk { font-family: "宋体"; font-size: 10pt }
p.ctl { font-family: "Times New Roman", serif; font-size: 11pt }
a:link { color: #0000ff; text-decoration: none }

等待唤醒机制

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

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

仔细查看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

p { margin-bottom: 0.25cm; direction: ltr; color: #000000; line-height: 120%; text-align: justify; widows: 0; orphans: 0 }
p.western { font-family: "Calibri", sans-serif; font-size: 10pt }
p.cjk { font-family: "宋体"; font-size: 10pt }
p.ctl { font-family: "Times New Roman", serif; font-size: 11pt }
a:link { color: #0000ff; text-decoration: none }
p { margin-bottom: 0.25cm; direction: ltr; color: #000000; line-height: 120%; text-align: justify; widows: 0; orphans: 0 }
p.western { font-family: "Calibri", sans-serif; font-size: 10pt }
p.cjk { font-family: "宋体"; font-size: 10pt }
p.ctl { font-family: "Times New Roman", serif; font-size: 11pt }
a:link { color: #0000ff; text-decoration: none }

原文地址:https://www.cnblogs.com/x54256/p/8445131.html

时间: 2024-10-07 12:08:03

Java——线程锁,死锁,等待唤醒机制的相关文章

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

线程间通信——等待唤醒机制,避免争夺同一资源: 锁对象可以是任意Object类的子类对象: 包子案例: 包子案例——生产者和消费者: 代码实现: 关键就是在于两个线程使用同一个锁对象! 这边是主程序调用这两个线程时候传入的同一个对象! 包子铺线程类——生产者: 其中,baozi类作为成员变量,并且重载了带参的构造方法: 锁对象为调用包子铺带参的构造方法传入的这个包子变量bz; 调用点wait方法必须是锁对象调用,锁对象调用wait方法之后当前线程就进入等待状态,另外一个线程此时应该是正在执行:

18、多线程 (线程安全、线程同步、等待唤醒机制、单例设计模式)

线程操作共享数据的安全问题 *A:线程操作共享数据的安全问题 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码. 程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的. 售票的案例 *A:售票的案例 /* - 多线程并发访问同一个数据资源 - 3个线程,对一个票资源,出售 */ public class ThreadDemo { public static void main(String[] args) { //创建Runnable接口实现

线程组、等待唤醒机制代码优化

线程组:ThreadGroup 把多个线程组合到一起,可以对一批线程进行分类处理,JAVA允许程序直接对线程进行控制 获取线程组:public final ThreadGroup getThreadGroup() 获取线程组的名称:public final String getName() 设置新的线程组:ThreadGroup(String name) 把线程弄到新线程组里:Thread(ThreadGroup group,Runnable target,String name) 等待唤醒机制

线程间通讯(等待唤醒机制)

锁.whit(); //将线程丢进线程池,暂停运行. 锁.notify(); //唤醒线程池中的最先存入那个同锁线程 锁.notifyAll(); //唤醒线程池中所有的同锁线程 都使用在同步中,因为要对持有监视器(锁)的线程操作.所以要使用在同步中,因为只有同步才具有锁. 为什么这些操作线程的方法要定义Object类中因为这些方法在操作同步中线程时,都需要标识它们所操作线程持有的锁只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒.不可以对不同锁中的线程进行唤醒. 也就是说,等待和唤

JAVA-初步认识-第十四章-线程间通信-等待唤醒机制-代码优化

一. 上一节中的代码写的并不是很眼镜,如下图中的属性,应该都是私有的.我们不应该直接访问资源中的属性,是因为它具备了不安全性. 瞎赋值怎么办呢?为了可控,意味着资源里面的属性需要被私有化,并对外提供方法访问.因此上节中的代码要进行改写. 首先对资源描述类进行修改,至于为什么set方法中写有两个形参,是因为name和sex同时要做赋值,因此直接将它们定义在一起. 而且类中提供了直接输出name和sex的方法,后面的程序中就不需要写那么长的输出语句了.这个输出问题比较简单 关键问题在哪儿呢?如果这么

多线程之Java中的等待唤醒机制

多线程的问题中的经典问题是生产者和消费者的问题,就是如何让线程有序的进行执行,获取CPU执行时间片的过程是随机的,如何能够让线程有序的进行,Java中提供了等待唤醒机制很好的解决了这个问题! 生产者消费者经典的线程中的问题其实是解决线程中的通讯问题,就是不同种类的线程针对同一资源的操作,这里其实有一张图很好的阐述了这其中的问题: 1 //代码中的实体类 2 public class Student { 3 String name; 4 int age; 5 boolean flag; // 默认

JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制

JAVA之旅(十四)--静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制 JAVA之旅,一路有你,加油! 一.静态同步函数的锁是class对象 我们在上节验证了同步函数的锁是this,但是对于静态同步函数,你又知道多少呢? 我们做一个这样的小实验,我们给show方法加上static关键字去修饰 private static synchronized void show() { if (tick > 0) { try { Thread

java多线程(死锁,lock接口,等待唤醒机制)

一.Lock接口 常用方法 Lock提供了一个更加面对对象的锁,在该锁中提供了更多的操作锁的功能. 使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,对电影院卖票案例中Ticket类进行如下代码修改: public class Ticket implements Runnable { //共100票 int ticket = 100; //创建Lock锁对象 Lock ck = new ReentrantLock(); @Override public void run

java之等待唤醒机制(线程之间的通信)

线程间通信 概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同.比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题. 为什么要处理线程间通信: 多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据. 如何保证线程间通信有效利