Loading

Java多线程16-线程同步问题与演示案例

18、线程同步机制

多个线程操作同一个资源

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

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

线程同步

  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题
    • 一个线程持有锁会导致其他所有需要此锁的线程挂起
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题

不安全的买票

//不安全的买票
public class UnsafeBuyTicket{
    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 ticketNums=10;
    boolean flag=true;
    @Override
    public void run() {
        while (flag){
            buy();
        }
    }

    private boolean buy(){
        if (ticketNums <= 0) {
            flag=false;
        }

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
        return flag;

    }
}

结果

苦逼的我拿到9
牛逼的你们拿到8
可恶的黄牛党拿到10
可恶的黄牛党拿到7
牛逼的你们拿到6
苦逼的我拿到5
牛逼的你们拿到4
可恶的黄牛党拿到2
苦逼的我拿到3
苦逼的我拿到1
牛逼的你们拿到-1
可恶的黄牛党拿到0
牛逼的你们拿到-2

不安全的银行

public class UnsafeBank {
    public static void main(String[] args) {
        Account account=new Account(100,"结婚基金");
        Drawing drawing01=new Drawing(account,10,"妻子");
        Drawing drawing02=new Drawing(account,100,"丈夫");
        drawing01.start();
        drawing02.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;
        this.nowMoney=nowMoney;
    }

    //取钱
    @Override
    public void run() {
        if (account.money-drawingMoney < 0) {
            System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
            return;
        }
        //模拟延迟,方法问题发生性
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //卡内余额=余额-你取的钱
        account.money=account.money-drawingMoney;
        //你手里的钱+你取的钱=你现在手里的钱
        nowMoney=nowMoney+drawingMoney;
        System.out.println(account.name+"余额为"+account.money);
        System.out.println(this.getName()+"手里的钱:"+nowMoney);
        //this.getName()==Thread.currentThread().getName()
    }
}

结果

结婚基金余额为-10
结婚基金余额为-10
妻子手里的钱:10
丈夫手里的钱:100

线程不安全集合

public class UnsafeList {
    public static void main(String[] args) {
        List<String > list=new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
//        try {
//            Thread.sleep(30);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        System.out.println(list.size());
    }
}

结果

8654

synchronized默认锁方法用到的this对象synchronized(obj){}代码块锁的是使用自定义的对象,而锁的对象应当是变化的量

安全的买票

//安全的买票
public class UnsafeBuyTicket{
    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 ticketNums=10;
    private boolean flag=true;

    @Override
    public void run() {
        while (flag){
            buy();
        }
    }

    private synchronized boolean buy(){//这里所的是调用方法的对象即buyTicket
        if (ticketNums <= 0) {
            flag = false;
            return flag;
        }
        System.out.println(Thread.currentThread().getName()+"拿到"+(ticketNums--)+"张票");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return flag;
    }
}

结果

苦逼的我拿到10张票
牛逼的你们拿到9张票
可恶的黄牛党拿到8张票
牛逼的你们拿到7张票
苦逼的我拿到6张票
牛逼的你们拿到5张票
可恶的黄牛党拿到4张票
可恶的黄牛党拿到3张票
牛逼的你们拿到2张票
牛逼的你们拿到1张票

安全的银行

public class UnsafeBank {
    public static void main(String[] args) {
        Account account=new Account(100,"结婚基金");
        Drawing drawing01=new Drawing(account,10,"妻子");
        Drawing drawing02=new Drawing(account,100,"丈夫");
        drawing01.start();
        drawing02.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;
        this.nowMoney=nowMoney;
    }

    //取钱
    @Override
    public void run() {
        //account是主函数传来的,两个线程公用一个,synchronized锁住该对象实例,每次操作只允许一个线程
        synchronized (account){
            if (account.money-drawingMoney < 0) {
                System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                return;
            }
            //模拟延迟,方法问题发生性
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //卡内余额=余额-你取的钱
            account.money=account.money-drawingMoney;
            //你手里的钱+你取的钱=你现在手里的钱
            nowMoney=nowMoney+drawingMoney;
            System.out.println(account.name+"余额为"+account.money);
            System.out.println(this.getName()+"手里的钱:"+nowMoney);
            //this.getName()==Thread.currentThread().getName()
        }
    }
}

结果

结婚基金余额为90
妻子手里的钱:10
丈夫钱不够,取不了

安全的集合

public class UnsafeList {
    public static void main(String[] args) {
        List<String > list=new ArrayList<String>();
        //这样操作后加入减少休眠时间还是会出现无法输出10000的现象,
        // 但线程是安全的,因为线程在执行10000次插入操作时主线程已经打印了输出(运行太快了)
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }

            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

结果

10000
posted @ 2022-02-16 19:18  Cn_FallTime  阅读(95)  评论(0编辑  收藏  举报