Java中多线程安全、同步、死锁、等待唤醒机制 您所在的位置:网站首页 等待star Java中多线程安全、同步、死锁、等待唤醒机制

Java中多线程安全、同步、死锁、等待唤醒机制

2024-05-30 10:55| 来源: 网络整理| 查看: 265

目录

一、线程安全问题

二、线程同步

1、同步代码块

2、同步方法

三、死锁

四、Lock 接口

五、等待和唤醒机制

一、线程安全问题

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码,程序每次运行结果和单线程运行的结果是一样的,而且程序中的变量值和和预期的一样,那么线程就是安全的,如果不是,则线程不安全。

下面通过售票的案例来理解一下线程安全问题

//模拟售票类 public class Ticket implements Runnable{ int T = 100; //定义100张票 @Override public void run() { while (true) { try { Thread.sleep(10); //加了休眠,让其他线程有机会执行 } catch (InterruptedException e) { e.printStackTrace(); } if(T > 0) { System.out.println(Thread.currentThread().getName() + T--); } } } } public static void main(String[] args) { //创建Runnable接口类实现对象 Ticket t = new Ticket(); //创建三个Thread对象,传递Runnable类实现对象 Thread T1 = new Thread(t,"窗口1:"); Thread T2 = new Thread(t,"窗口2:"); Thread T3 = new Thread(t,"窗口3:"); //开启线程 T1.start(); T2.start(); T3.start(); } 当我们运行代码时,会发现出现了重复的票,这和我们预期的结果不一样,这就出现了多线程安全问题多线程安全问题都是由全局变量及静态变量引起的,若每个线程中对全局变量、静态变量只有读操作没有写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,就可能有线程安全问题,一般采用线程同步来解决这个问题在Java中抢占式调度,当程序在售票类运行的时候,进入run方法的while循环里的if循环的时候,可能没有抢到CPU而发生阻塞,并且这三个线程都有可能阻塞在这个位置,所以会出现打印出负数的情况 二、线程同步

Java中提供了线程同步机制,有效的解决了线程安全问题,线程同步有以下两种方式:

方法一:同步代码块方法二:同步方法 1、同步代码块

格式:在代码块声明上,加上 synchronizedsynchronized (锁对象) {     可能会产生线程安全问题的代码块 }

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

对售票的案例进行改进:

public class Ticket implements Runnable{ int T = 10; //定义锁对象 Object lock = new Object(); @Override public void run() { while (true) { //同步代码块 synchronized (lock) { if(T > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + T--); } } } } }

对上述代码进行改进,增加了同步代码块,即同步锁,当线程进入同步代码块的时候,会判断有没有同步锁,如果有,则获取同步锁,进入同步中,去执行代码块,执行完毕后,出去了同步代码块,线程就将锁还回去,如果判断没有锁,就被阻挡在同步代码块外面不能执行,只能等待,这样,线程安全问题就解决了,但导致程序运行的速度下降了。总:没有锁的线程不能进入同步,在同步中的线程,不出去同步,就不会释放锁。

2、同步方法 (1) 普通方法同步

格式:在方法声明上加上 synchronizedpublic synchronized void method() {     可能会产生线程安全问题的代码 }

注:同步方法中的锁对象是 this

再对售票的案例进行改进:

public class Ticket implements Runnable{ int T = 10; //定义锁对象 Object lock = new Object(); @Override public void run() { while (true) { //同步方法 method(); } } private synchronized void method() { if(T > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + T--); } } }

 

 同步方法也能解决线程安全问题

(2) 静态同步方法

格式:在方法声明上加上 static synchronizedpublic static synchronized void method(){     可能会产生线程安全的代码 }

注:静态同步方法中的锁对象是 类名.class

三、死锁

在使用同步锁的时候,存在弊端:当线程任务中出现多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发程序的无限等到,这种现象称为死锁。

格式:synchronized(A锁){     synchronized(B锁){          } }

四、Lock 接口

Lock 接口实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作,Lock 接口中常用方法如下:

void lock():获得锁void unlock():释放锁

使用 Lock 接口继续对售票案例进行修改:

public class Ticket implements Runnable{ int T = 10; //创建Lock对象 Lock ck = new ReentrantLock(); @Override public void run() { while (true) { //调用lock方法获取锁 ck.lock(); if(T > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + T--); } //释放锁 ck.unlock(); } } } 五、等待和唤醒机制

等待唤醒机制是为了方便处理进程之间通信的手段,多个线程在处理同一个资源时,由于处理的动作(线程的任务)不行同,为了使各个线程能够有效的利用资源,便采取了等待唤醒机制。等待唤醒机制涉及到的方法:

wait():等待。将正在执行的线程释放其执行资格和执行权,并存储到线程池中notify():唤醒。唤醒线程池中被 wait() 的线程,一次唤醒一个,而且是任意的notifyAll():唤醒全部。可以将线程池中的所有 wati() 线程都唤醒

注:

这些方法都是在同步中才有效,在使用时必须注明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程因为这些方法在使用的时候要注明所属的锁,而锁又是任意对象,所以这些方法是定义在 Object 类中的

代码实例:

来看一个例子,现有Person类,存储了姓名和年龄,使用 inPut 线程对 Person 类输入信息,使用 outPut 线程对 Person 类获取打印信息

//模拟Person类 public class Person { String name; int age; boolean flag = false; } //输入线程任务inPut类 public class inPut implements Runnable { private Person p; int count = 0; public inPut(Person p) { this.p = p; } public void run() { while (true) { synchronized (p) { if(p.flag) { try { p.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } if(count % 2 == 0) { p.name = "儿童"; p.age = 3; } else { p.name = "老人"; p.age = 99; } p.notify(); p.flag = true; } count++; } } } //输出线程任务outPut类 public class outPut implements Runnable { private Person p; public outPut(Person p) { this.p = p; } public void run() { while (true) { synchronized (p) { if(!p.flag) { try { p.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(p.name + ":" + p.age + "岁"); p.notify(); p.flag = true; } } } } //在主线程中调用 public static void main(String[] args) { Person P = new Person(); inPut in = new inPut(P); outPut out = new outPut(P); Thread T1 = new Thread(in); Thread T2 = new Thread(out); T1.start(); T2.start(); }

分析:

输入 inPut 类:输入完成后,必须等待输出结果打印结束才能进行下一次赋值,赋值后,执行wait()方法永远等待,直到被唤醒,唤醒后重新对变量赋值,赋值后再唤醒输出线程 notify(),自己再wait()输出 outPut 类:输出完成后,必须等待输入的重新赋值后才能进行下一次输出,在输出等待前,唤醒输入的notify(),自己再 wait() 永远等待,直到被唤醒


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有