多线程学习笔记---(线程不安全问题)

145 阅读3分钟

线程不安全问题

取钱问题
  • 账户余额是100,两个人都要取100,两个人在取之前都获取到余额是100,可以继续往下取,第一个人取完了,余额变为0,第二个人继续取,余额变成负数;或者两个人都获取到100,在取的时候也是100,使得两个人都取过之后最新的余额为0
public class BuyTicket {
    public static void main(String[] args) {
        People lily = new People("lily", 0);
        People benjen = new People("benjen", 0);
        Account wedding = new Account("wedding",10 );
        new bank(lily,wedding).start();
        new bank(benjen,wedding).start();
    }
}
class Account{
    String account;
    int balance;

    public Account(String account, int balance) {
        this.account = account;
        this.balance = balance;
    }
}
class People{
    String name;
    int money;

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

class bank extends Thread{
    People people;
    Account account;

    public bank(People people, Account account) {
        this.people = people;
        this.account = account;
    }

    public void draw(int i){
        if (account.balance<i){
            System.out.println("balance is not enough");
            return;
        }
        account.balance=account.balance-i;
        people.money=i+people.money;
        System.out.println(people.name+" now has "+people.money);
        System.out.println(account.account+ "now has "+account.balance);
        System.out.println("________________");
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            draw(1);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

买票问题
  • 多个线程同时卖票,同时获取到票为10张,还可以卖,第一个线程卖出去了,第二个线程再卖一张就导致了多卖票
public class SellTickets {
    public static void main(String[] args) {
        Station station = new Station();
        Thread t1 = new Thread(station);
        Thread t2 = new Thread(station);
        Thread t3 = new Thread(station);
        t1.start();
        t2.start();
        t3.start();
    }
}
class Station implements Runnable{
    int num=0;
    @Override
    public void run() {
        while (num<11){
            System.out.println("sell "+(++num)+" tickets");
        }
    }
}
  1. ArrayList的默认大小是10,当大小为9时,两个线程同时添加元素,第一个添加完后,数组已经满了,第二个再添加导致越界
  2. 两个数组同时在同一个位置添加元素,然后依次对size加1,导致一个元素被覆盖,后一个元素为null
  3. 连个数组同时在同一个位置添加元素,获取到相同的size并加1,使得size最终只加了一个1,使得数组大小小于预期
  4. CopyOnWriteArrayList不存在这个问题

public class ListUnSafe {
    public static void main(String[] args) {
        ArrayList<String> strings = new ArrayList<String>();
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    strings.add(Thread.currentThread().getName());
                    try {
                        Thread.sleep(900);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

        }

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

        System.out.println(strings.size());
    }
}

synchronized关键字
  • 多个线程访问甚至修改同一个对象,这种现象叫并发;这种情况需要线程同步,即让同时访问的线程进入等待池等待资源被正在访问的线程释放。
  • 可以加入synchronized关键字来对资源进行加锁,下面这篇笔记讲得比较清楚

www.cnblogs.com/mengdd/arch… 1 修饰一个普通方法,是对象锁,同一个对象中所有被synchronized修饰的普通方法使用的均是同一把对象锁,等同于synchronized(this){代码块};当使用thread来创建线程时,由于this不是同一个对象,方法锁会无效 2 修饰一个代码块,也是对象锁。括号内可以填任意对象。若两个代码块括号内的对象相同,则上的是同一把对象锁,无法被同一个对象访问;若括号内的对象不同,可以被同一个对象使用。(若括号内的对象是string类型,并且这个string对象会在访问过程中被重新赋值,说明这个对象一直在变,无法保证一直是同一把锁,这里涉及到string的内存问题,可以参考:isudox.com/2016/06/22/… 3 修饰一个静态方法,使用的是类锁,所有的对象都无法同时访问上了类锁的方法。类锁和对象锁不冲突,即使是同一个对象,也可以在访问类锁方法的时候访问对象锁。

ReentrantLock
  • 好处是可以主动解锁
  • 但是只能对代码块加锁
生产者消费者问题
  • 这篇文章写得比较清楚
  • notify和signal都只会随机唤醒一个线程,可能唤醒的不是目标线程,被唤醒的目标线程又重新wait,使得所有线程wait,导致死锁。
  • 可以通过notifyAll和signalAll简单粗暴地唤醒所有的线程。
  • 更好的方法是建立针对性的condition 来针对性地唤醒线程 www.jianshu.com/p/d3214bd34…
死锁

两个线程都占用一部分资源,并且都在等待对方释放资源,产生死锁

public class DeadLock {
    public static void main(String[] args) {
        eat eat = new eat();
        drink drink = new drink();
        new table(0,eat,drink).start();
        new table(1,eat,drink).start();
    }
}

class eat{}
class drink{}

class table extends Thread{
    int choice;
    eat eat;
    drink drink;

    public table(int choice, com.citi.threadtest.eat eat, com.citi.threadtest.drink drink) {
        this.choice = choice;
        this.eat = eat;
        this.drink = drink;
    }

    @Override
    public void run() {
        if (choice==0){
            synchronized (eat){
                System.out.println("0-eat");
                synchronized (drink){
                    System.out.println("0-drink");
                }
            }
        }else if (choice==1){
            synchronized (drink){
                System.out.println("1-drink");
                synchronized (eat){
                    System.out.println("1-eat");
                }
            }
        }
    }
}