当前位置 : 主页 > 编程语言 > java >

72 java多线程_4 _线程安全、线程同步与线程通信

来源:互联网 收集:自由互联 发布时间:2022-07-13
文章目录 ​​1.线程安全问题​​ ​​1.1多线程安全问题:​​ ​​2.线程安全​​ ​​2.1同步机制:同步方式1​​ ​​2.1.1同步代码块:​​ ​​2.1.2线程阻塞状态​​ ​​2.2同步机


文章目录

  • ​​1.线程安全问题​​
  • ​​1.1多线程安全问题:​​
  • ​​2.线程安全​​
  • ​​2.1同步机制:同步方式1​​
  • ​​2.1.1同步代码块:​​
  • ​​2.1.2线程阻塞状态​​
  • ​​2.2同步机制:同步方式2​​
  • ​​2.2.1同步方法​​
  • ​​2.3同步规则​​
  • ​​3.经典问题​​
  • ​​3.1死锁​​
  • ​​4.线程通信​​
  • ​​4.1常用方法​​
  • ​​4.2还是存取钱的例子(存一下,取一下):​​
  • ​​4.3多存多取问题与全部等待问题​​
  • ​​5.生产者消费者问题​​
  • ​​6.小结​​

1.线程安全问题

72 java多线程_4 _线程安全、线程同步与线程通信_java

  • 需求: A线程将“Hello"存入数组; B线程将“World"存入数组。
  • 出现的问题:A线程查找下标0,此时这个位置是空的,但是还没有存入数据,时间片到期,然后B线程开始执行,查找到下标0,发现里面没有数据,就把World存入到下标为0的位置,之后轮到A线程执行时,因为前面A执行时,发现下标0这个位置还没有数据,就把Hello存入,导致World被覆盖
package com.wlw.thread;
import java.util.Arrays;
/**
*多线程安全问题:
* 两个线程同时向一个数组存入数据
*/
public class ThreadSafe {
private static int index = 0;
public static void main(String[] args) throws Exception{
//创建数组,共享资源
String[] s = new String[5];

//创建两个操作
Runnable Arunnabe = new Runnable() {
@Override
public void run() {
s[index] = "Hello";
index++;
}
};
Runnable Brunnabe = new Runnable() {
@Override
public void run() {
s[index] = "World";
index++;
}
};

//创建线程
Thread a = new Thread(Arunnabe,"A");
Thread b = new Thread(Brunnabe,"B");

//启动线程
a.start();
b.start();

//加入线程,确保最后a,b线程执行完
a.join();
b.join();

System.out.println(Arrays.toString(s));
}
}
/*
多运行几次,你会发现结果并不都是我们设想的 -》[Hello, World, null, null, null]
,还会有-》[Hello, null, null, null, null] ,[World, null, null, null, null]
等.........
*/

1.1多线程安全问题:

  • 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一 致。
  • 临界资源:共享资源(同一对象),一次仅允许-一个线程使用,才可保证其正确性。
  • 原子操作:不可分割的多步操作,被视作- -个整体,其顺序和步骤不可打乱或缺省。

2.线程安全

  • 思考:在程序应用中,如何保证线程安全?
    同步机制:一个线程A在访问共享资源时,别的线程等待,只有A线程执行完毕,别的线程才能执行

2.1同步机制:同步方式1

2.1.1同步代码块:

synchronized(临界资源对象/共享资源对象){ //对临界资源对象加锁
//代码(原子操作)
}

注意:

  • 每个对象都有一个互斥锁标记,用来分配给线程的
  • 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块
  • 线程退出同步代码块,会释放相应的互斥锁标记。
  • 上面数组问题的优化,同步机制:

    package com.wlw.thread;
    import java.util.Arrays;
    /**
    *多线程安全问题:
    * 两个线程同时向一个数组存入数据
    */
    public class ThreadSafe {
    private static int index = 0;
    public static void main(String[] args) throws Exception{
    //创建数组,共享资源
    String[] s = new String[5];

    //创建两个操作
    Runnable Arunnabe = new Runnable() {
    @Override
    public void run() {
    //同步代码块
    synchronized (s){
    s[index] = "Hello";
    index++;
    }
    }
    };
    Runnable Brunnabe = new Runnable() {
    @Override
    public void run() {
    //同步代码块
    synchronized (s) {
    s[index] = "World";
    index++;
    }
    }
    };

    //创建线程
    Thread a = new Thread(Arunnabe,"A");
    Thread b = new Thread(Brunnabe,"B");

    //启动线程
    a.start();
    b.start();

    //加入线程,确保最后a,b线程执行完
    a.join();
    b.join();

    System.out.println(Arrays.toString(s));
    }
    }
    /*
    执行结果只有这两种:
    [Hello, World, null, null, null]
    [World, Hello, null, null, null]
    */

    优化之前的四个窗口共卖100张票的问题:

    package com.wlw.thread.caseDemo;
    /**
    * 票类(共享资源)
    */
    public class Ticket implements Runnable{

    int ticket = 100; //票总共100张

    //创建锁,引用对象都行
    //Object obj = new Object();

    //售票功能
    @Override
    public void run() {
    while (true){
    synchronized (this){ //this 代表当前对象 Ticket
    if(ticket <= 0){
    break;
    }
    System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
    ticket--;
    }

    }
    }
    }package com.wlw.thread.caseDemo;

    public class TestTicket {
    public static void main(String[] args) {

    // 创建实现类对象
    Ticket ticket = new Ticket();

    //创建线程对象
    Thread thread1 = new Thread(ticket,"窗口1");
    Thread thread2 = new Thread(ticket,"窗口2");
    Thread thread3 = new Thread(ticket,"窗口3");
    Thread thread4 = new Thread(ticket,"窗口4");

    //调用start()方法,启动线程
    thread1.start();
    thread2.start();
    thread3.start();
    thread4.start();

    /*
    这时就不会出现多个窗口卖了同一张票,这是一个线程安全问题,已用同步机制解决
    */
    }
    }

    优化之前存钱取钱的问题:

    package com.wlw.thread.caseDemo;
    /**
    * 银行卡类
    */
    public class BankCard {
    private Double money;

    public Double getMoney() {
    return money;
    }

    public void setMoney(Double money) {
    this.money = money;
    }
    }package com.wlw.thread.caseDemo;

    public class TestBankCard2 {
    public static void main(String[] args) {

    //1.创建银行卡
    BankCard bankCard = new BankCard();
    bankCard.setMoney(0.0);

    //2.创建两个操作,存取功能
    Runnable add = new Runnable() {
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    //同步机制
    synchronized (bankCard){
    bankCard.setMoney(bankCard.getMoney() + 1000);
    System.out.println(Thread.currentThread().getName()+ "存了1000块钱,余额是:" +bankCard.getMoney());
    }
    }
    }
    };
    Runnable sub = new Runnable() {
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    //同步机制
    synchronized (bankCard) {
    if (bankCard.getMoney() >= 1000) {
    bankCard.setMoney(bankCard.getMoney() - 1000);
    System.out.println(Thread.currentThread().getName() + "取了1000块钱,余额是:" + bankCard.getMoney());
    } else {
    System.out.println("余额不足,请充值");
    i--;
    }
    }
    }
    }
    };

    //3.创建线程对象,并启动
    new Thread(add,"小李").start();
    new Thread(sub,"小月").start();

    }
    }

    2.1.2线程阻塞状态

    72 java多线程_4 _线程安全、线程同步与线程通信_sed_02

    2.2同步机制:同步方式2

    2.2.1同步方法

    synchronized 返回值类型 方法名称(形参列表o){//对当前对象(this)加锁
    //代码(原子操作)
    }

    注意:

  • 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步方法中。
  • 线程退出同步方法,会释放相应的互斥锁标记。
  • 例子:四个窗口共卖100张票

    package com.wlw.thread.caseDemo;

    /**
    * 票类(共享资源)
    */
    public class Ticket2 implements Runnable{

    int ticket = 100; //票总共100张

    //售票功能
    @Override
    public void run() {
    while (true){
    if(!sale()){
    break;
    }
    }
    }

    public synchronized boolean sale(){ //此时锁为this,如果该方法变为static的,锁为Ticket2.Class
    if(ticket <= 0){
    return false;
    }
    System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
    ticket--;
    return true;
    }
    }package com.wlw.thread.caseDemo;

    public class TestTicket2 {
    public static void main(String[] args) {

    // 创建实现类对象
    Ticket2 ticket = new Ticket2();

    //创建线程对象
    Thread thread1 = new Thread(ticket,"窗口1");
    Thread thread2 = new Thread(ticket,"窗口2");
    Thread thread3 = new Thread(ticket,"窗口3");
    Thread thread4 = new Thread(ticket,"窗口4");

    //调用start()方法,启动线程
    thread1.start();
    thread2.start();
    thread3.start();
    thread4.start();

    }
    }

    2.3同步规则

  • 注意:
    • 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
    • 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
  • 已知JDK中线程安全的类:
    • StringBuffer
    • Vector
    • Hashtable
    • 以上类中的公开方法,均为synchonized修饰的同步方法。

    3.经典问题

    3.1死锁

  • 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
  • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
  • 实现死锁的代码(理解死锁就好):

    package com.wlw.thread.casedemo02;
    public class MyLock {
    //定义锁,两只筷子
    public static Object a = new Object();
    public static Object b = new Object();
    }package com.wlw.thread.casedemo02;
    public class Boy extends Thread{
    @Override
    public void run() {
    synchronized (MyLock.a){
    System.out.println("男孩拿到了a");
    synchronized (MyLock.b){
    System.out.println("男孩拿到了b");
    System.out.println("男孩可以吃饭了...........");
    }
    }
    }
    }package com.wlw.thread.casedemo02;
    public class Girl extends Thread{
    @Override
    public void run() {
    synchronized (MyLock.b){
    System.out.println("女孩拿到了b");
    synchronized (MyLock.a){
    System.out.println("女孩拿到了a");
    System.out.println("女孩可以吃饭了...........");
    }
    }
    }
    }package com.wlw.thread.casedemo02;
    public class TestDeadLock {
    public static void main(String[] args) {
    //创建线程对象
    Boy boy = new Boy();
    Girl girl = new Girl();

    //启动线程
    boy.start();
    /*try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }*/
    girl.start();
    }
    }
    /*
    运行发现,程序不会停止,男孩拿到了a,女孩拿到了b,就这样一值僵持下去
    如果启动其中一个线程之后,休眠一会,再启动另一个线程,就可以正确执行了
    */

    4.线程通信

    4.1常用方法

  • 等待: ( 锁.wait() )
    • public final void wait()
    • public final void wait(long timeout)
    • 必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait() 时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁,进入等待队列。
  • 通知:( 锁.notify() )
    • public final void notify() //唤醒等待线程
    • public final void notifyAll() //唤醒全部等待线程

    4.2还是存取钱的例子(存一下,取一下):

    package com.wlw.thread.casedemo03;

    /**
    * 银行卡类
    */
    public class BankCard {

    private double money;

    private boolean flag ; //true为有钱,false为没钱

    //存钱(没有钱才会存)
    public synchronized void add(double m){//锁为this
    if(flag){//有钱,不能存
    try {
    this.wait(); //锁.wait(),将线程放入等待队列,同时释放锁和cpu
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    money = money + m;
    System.out.println(Thread.currentThread().getName()+"存了1000块钱,余额为:"+money);
    flag = true;// 存完钱了,变为有钱了
    this.notify();//锁.notify(),唤醒等待队列中的线程
    }

    //取钱,有钱才能取
    public synchronized void sub(double m){
    if (!flag){ //没钱不能取
    try {
    this.wait();//锁.wait(),将线程放入等待队列,同时释放锁和cpu
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    money = money - m;
    System.out.println(Thread.currentThread().getName()+"取了1000块钱,余额为"+money);
    flag = false;//取完钱了,变没钱了
    this.notify();//锁.notify(), 唤醒等待队列中的线程

    }
    }package com.wlw.thread.casedemo03;

    public class AddMoney implements Runnable{

    private BankCard card;
    public AddMoney(BankCard card){
    this.card = card;
    }
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    card.add(1000.0);
    }
    }
    }package com.wlw.thread.casedemo03;

    public class SubMoney implements Runnable {

    private BankCard card;
    public SubMoney(BankCard card){
    this.card = card;
    }
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    card.sub(1000.0);
    }
    }
    }package com.wlw.thread.casedemo03;

    public class TestBankCard {
    public static void main(String[] args) {

    //创建一张卡
    BankCard card = new BankCard();

    //创建操作
    AddMoney addMoney = new AddMoney(card);
    SubMoney subMoney = new SubMoney(card);

    //创建线程
    Thread add = new Thread(addMoney,"小李");
    Thread sub = new Thread(subMoney,"小月");

    //启动线程
    add.start();
    sub.start();
    }
    }

    4.3多存多取问题与全部等待问题

  • 多存多取问题分析:两个人A、B存,两个人C、D取,有这样的情况,一开始C、D去取钱,因为没钱,所以C、D都进入等待队列;下一个A存钱(1000元),成功,唤醒了C;C抢到了cpu,取钱(0元),成功,唤醒了D;D抢到了cpu,取钱,D是可以成功的,因为D是从wait()语句,开始执行的,它不会再去判断标记,所以此时卡里的钱为(-1000元)
  • 解决:将同步方法里的 if 变为 while
  • 72 java多线程_4 _线程安全、线程同步与线程通信_ide_03

  • 全部等待问题分析:将同步方法里的 if 变为 while 之后,还会出现一个这样的情况:一开始,C去取钱,因为没钱不能取,进入等待队列,释放锁和cpu;D去取钱,因为没钱不能取,进入等待队列,释放锁和cpu;A去存钱(1000元),成功,唤醒了C;B去存钱,因为有钱不能存,进入等待队列,释放锁和cpu;A去存钱,因为有钱不能存,进入等待队列,释放锁和cpu;C去取钱,成功(0元),唤醒了D(问题就来了);此时只有C、D可以抢cpu,但是卡里已经没钱了,不管谁抢到,都会进入等待队列,最终全部都进入了等待队列。
  • 解决:将notify() 变为notifyAll() ,全部唤醒
  • 72 java多线程_4 _线程安全、线程同步与线程通信_多线程_04

    最终代码:

    package com.wlw.thread.casedemo03;

    /**
    * 银行卡类
    */
    public class BankCard {

    private double money;

    private boolean flag ; //true为有钱,false为没钱

    //存钱(没有钱才会存)
    public synchronized void add(double m){//锁为this
    while (flag){//有钱,不能存
    try {
    this.wait(); //锁.wait(),将线程放入等待队列,同时释放锁和cpu
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    money = money + m;
    System.out.println(Thread.currentThread().getName()+"存了1000块钱,余额为:"+money);
    flag = true;// 存完钱了,变为有钱了
    this.notifyAll();//锁.notify(),唤醒等待队列中的线程
    }

    //取钱,有钱才能取
    public synchronized void sub(double m){
    while (!flag){ //没钱不能取
    try {
    this.wait();//锁.wait(),将线程放入等待队列,同时释放锁和cpu
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    money = money - m;
    System.out.println(Thread.currentThread().getName()+"取了1000块钱,余额为"+money);
    flag = false;//取完钱了,变没钱了
    this.notifyAll();//锁.notify(), 唤醒等待队列中的线程

    }
    }package com.wlw.thread.casedemo03;

    public class AddMoney implements Runnable{

    private BankCard card;
    public AddMoney(BankCard card){
    this.card = card;
    }
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    card.add(1000.0);
    }
    }
    }package com.wlw.thread.casedemo03;

    public class SubMoney implements Runnable {

    private BankCard card;
    public SubMoney(BankCard card){
    this.card = card;
    }
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    card.sub(1000.0);
    }
    }
    }package com.wlw.thread.casedemo03;

    public class TestBankCard {
    public static void main(String[] args) {

    //创建一张卡
    BankCard card = new BankCard();

    //创建操作
    AddMoney addMoney = new AddMoney(card);
    SubMoney subMoney = new SubMoney(card);

    //创建线程
    Thread xiaoli = new Thread(addMoney,"小李");
    Thread xiaoyue = new Thread(subMoney,"小月");
    Thread mingming = new Thread(addMoney,"明明");
    Thread lili = new Thread(subMoney,"丽丽");

    //启动线程
    xiaoli.start();
    mingming.start();
    xiaoyue.start();
    lili.start();
    }
    }

    5.生产者消费者问题

    package com.wlw.thread.casedemo04;
    /**
    * 面包类
    */
    public class Bread {

    private int id;
    private String producterName;

    public Bread() {}

    public Bread(int id, String producterName) {
    this.id = id;
    this.producterName = producterName;
    }

    public int getId() { return id;}

    public void setId(int id) {this.id = id;}

    public String getProducterName() {return producterName;}

    public void setProducterName(String producterName) {
    this.producterName = producterName;
    }

    @Override
    public String toString() {
    return "Bread{" +
    "id=" + id +
    ", producterName='" + producterName + '\'' +
    '}';
    }
    }package com.wlw.thread.casedemo04;
    public class BreadCon {
    //装面包的容器,也是缓冲区
    private Bread[] cons = new Bread[6];
    //下标
    private int index = 0;

    //存面包
    public synchronized void input(Bread bread){//锁 this
    //判断容器满吗
    while(index >= 6){
    try {
    this.wait(); //加入等待队列,释放锁和cpu
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    cons[index] = bread;
    System.out.println(Thread.currentThread().getName()+"生产了第"+bread.getId()+"个面包");
    index++;

    this.notifyAll();//唤醒等待队列里的线程
    }

    //吃面包
    public synchronized void output(){
    //判断容器里还有面包吗
    while(index <= 0){
    try {
    this.wait();//加入等待队列,释放锁和cpu
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    index--;
    Bread bread = cons[index];
    System.out.println(Thread.currentThread().getName()+"消费了第"+bread.getId()+"个面包"+",生产者:"+bread.getProducterName());
    cons[index] = null;

    this.notifyAll();//唤醒等待队列里的线程
    }

    }package com.wlw.thread.casedemo04;
    public class Product implements Runnable{
    private BreadCon breadCon;
    public Product(BreadCon breadCon) {
    this.breadCon = breadCon;
    }

    //生产面包
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    breadCon.input(new Bread(i,Thread.currentThread().getName()));
    }
    }
    }package com.wlw.thread.casedemo04;
    public class Consume implements Runnable{
    private BreadCon breadCon;
    public Consume(BreadCon breadCon) {
    this.breadCon = breadCon;
    }

    //消费面包
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    breadCon.output();
    }
    }
    }package com.wlw.thread.casedemo04;
    public class Test {
    public static void main(String[] args) {
    //创建容器
    BreadCon breadCon = new BreadCon();

    //创建操作
    Product product = new Product(breadCon);
    Consume consume = new Consume(breadCon);

    //创建线程
    Thread xiaoli = new Thread(product, "小李");
    Thread xiaoyue = new Thread(consume, "小月");
    Thread mingming = new Thread(product, "明明");
    Thread lili = new Thread(consume, "丽丽");

    //启动线程
    xiaoli.start();
    xiaoyue.start();
    mingming.start();
    lili.start();
    }
    }

    6.小结

  • 线程的创建:
    • 方式1:继承Thread类
    • 方式2:实现Runnable接口 (-一个任务Task),传入给Thread对象并执行。
  • 线程安全:
    • 同步代码块:为方法中的局部代码 (原子操作) 加锁。
    • 同步方法:为方法中的所有代码 (原子操作) 加锁。
  • 线程间的通信:
    • wait() / wait(long timeout); // 等待
    • notify() / notifyAll(); // 通知


    上一篇:73 java多线程_5 _线程池
    下一篇:没有了
    网友评论