美文网首页
线程同步机制

线程同步机制

作者: i小雨 | 来源:发表于2020-11-13 09:37 被阅读0次

    线程同步:

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

    同步方法:

    • synchronized关键字,它包括两种用法:synchronized方法synchronized块
      同步方法:public synchronized void methed(int args){}
    • synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
      缺陷:若将一个大的方法申明为synchronized将会影响效率

    同步块

    • 同步块:synchronized(Obj){}
    • Obj称之为同步监视器
      • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
      • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
    • 同步监视器的执行过程:
      1、第一个线程访问,锁定同步监视器,执行其中代码,
      2、第二个线程访问,发现同步监视器被锁定,无法访问,
      3、第一个线程访问完毕,解锁同步监视器,
      4、第二个线程访问,发现同步监视器没有锁,然后锁定并访问。

    1、抢票实例改进:

    package com.company.ThreadTest;
    //模拟多个线程同时操作一个对象
    //买火车票的例子
    public class TestThread1 implements Runnable{
    
        //票数
        private int ticketNum = 10;
        //添加标志,用于外部停止线程
        boolean flag = true;
    
        @Override
        public void run() {
    
            while (flag){
                buy();
            }
        }
        //synchronized锁的是this对象
        private synchronized void buy(){
            //票数小于0时票抢完
            if (ticketNum<=0){
                flag = false;
                return;
            }
    
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketNum--+"张票");
        }
    
        public static void main(String[] args) {
    
            TestThread1 ticket = new TestThread1();
    
            new Thread(ticket,"张三").start();
            new Thread(ticket,"赵四").start();
            new Thread(ticket,"王五").start();
        }
    }
    
    

    结果:

    张三抢到了第10张票
    张三抢到了第9张票
    王五抢到了第8张票
    赵四抢到了第7张票
    王五抢到了第6张票
    王五抢到了第5张票
    张三抢到了第4张票
    王五抢到了第3张票
    赵四抢到了第2张票
    王五抢到了第1张票
    

    2、银行取钱改进

    package com.company.ThreadTest;
    
    //不安全的取钱
    //模拟两个人去银行取钱
    public class TestUnsafeBank {
        public static void main(String[] args) {
            //账户
            Account account = new Account(100,"账户1");
    
            Drawing you = new Drawing(account,50,"你");
            Drawing yourFriend = new Drawing(account,100,"你的朋友");
            you.start();
            yourFriend.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() {
            //account为同步监视器
            //如果synchronized放在run()方法上,则锁定的是this.
            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);
            }
        }
    }
    

    结果:

    账户1余额为:50
    你手里的钱:50
    你的朋友钱不够,取不了。
    

    结果分析: 该示例的synchronized不能直接加到run()方法上,这样锁定的将是this,这样会导致锁不了;锁定的应该是账户对象

    ArrayList添加元素改进:

    package com.company.ThreadTest;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class TestArrayList {
    
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            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
    

    结果分析:list为同步监视器,每次添加元素前,先锁定list,这样就不会导致覆盖添加。

    相关文章

      网友评论

          本文标题:线程同步机制

          本文链接:https://www.haomeiwen.com/subject/elwubktx.html