线程不安全

并发

并发:同一个对象被多个线程同时操作

并发问题:买火车的票的案例 多个线程同时操作同一个对象

package test;

import com.sun.org.apache.bcel.internal.generic.NEW;

//多个线程同时操作同一个对象
//买火车的票的案例


//发现问题 :多个线程操作同一个资源的情况下,线程不安全,数据紊乱
public class ThreadTest2 implements Runnable {


    private int ticketNums = 10;

    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) {
                break;
            }
            //模拟延时 0.2秒
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticketNums-- + "票");
        }
    }

    public static void main(String[] args) {
        ThreadTest2 ticketThread = new ThreadTest2();

        new Thread(ticketThread, "小明").start();
        new Thread(ticketThread, "老师").start();
        new Thread(ticketThread, "黄牛党").start();

    }
}

多个线程操作同一个资源的情况下,线程不安全,数据紊乱

在这里插入图片描述

线程同步

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这个时候我们需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入到这个对象的等待池 形成队列,等待前面线程使用完毕,下一个线程再使用。类似生活中食堂排队打饭,排队,一个个来。

队列和锁(安全,损失性能)

由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入 锁机制 synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待使用后释放锁即可,存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引发性能问题;
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题。

三大不安全的案例

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

  1. 不安全的买票
package test;

//不安全的买票
//线程不安全,有负数
public class UnsafeTicketBuying {
    public static void main(String[] args) {

        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket,"抢票的我").start();
        new Thread(buyTicket,"抢票的某人").start();
        new Thread(buyTicket,"抢票黄牛党").start();

    }
}

class BuyTicket implements Runnable{

    //票
    private int ticketNum = 10;
    //外部停止方式
    boolean flag = true;

    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //买票
    private void  buy() throws InterruptedException {
        //判断是否有票
        if(ticketNum<=0){
            flag = false;
            return;
        }
        //模拟延时
        Thread.sleep(100);
        //买到票
        System.out.println(Thread.currentThread().getName()+"拿到第"+ticketNum-- +"张票");
    }
}

在这里插入图片描述

  1. 不安全的取钱
package test;

//不安全的取钱
//两个人去银行取钱,账号
public class UnsafeBank {

    public static void main(String[] args) {

        //账户
        Account account = new Account(100, "结婚基金");

        Drawing you = new Drawing(account, 50, "你");
        Drawing girlFriend = new Drawing(account, 100, "女朋友");

        you.start();
        girlFriend.start();

    }
}

class Account {

    //余额
    int money;

    //卡名
    String name;

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }

}


//银行:模拟取款
class Drawing extends Thread {

    Account account;//账户

    //取了多少钱
    int drawingMoney;

    //现在手里有多少钱
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //取钱
    @Override
    public void run() {
        //判断有没有钱
        if (account.money - drawingMoney < 0) {
            System.out.println(Thread.currentThread().getName() + "钱不够了,取不了钱了");
            return;
        }

        //sleep可以放大问题的发生性
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        //卡内余额 = 余额 - 你取的钱
        account.money = account.money - drawingMoney;

        //你手里的钱
        nowMoney = nowMoney + drawingMoney;

        System.out.println(account.name + "余额为:" + account.money);
        //Thread.currentThread().getName() = this.getName();

        System.out.println(this.getName() + "手里的钱:" + nowMoney);
    }
}

在这里插入图片描述

  1. 线程不安全的集合

​ 比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。

在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;

​ 而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。

​ 那好,我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

package test;

import java.util.ArrayList;
import java.util.List;

//线程不安全的集合
public class UnsafeList {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 5000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(list.size());

    }
}

在这里插入图片描述

posted @ 2020-05-10 22:05  我有满天星辰  阅读(3)  评论(0编辑  收藏  举报