当前位置:主页 > 查看内容

Java-----多线程【同步方法 与 不安全案例转换为安全案例】

发布时间:2021-05-21 00:00| 位朋友查看

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

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

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

同步方法

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法 和 synchronized块。

    同步方法:public synchronized void method(int args){}

    块:synchronized(this){}

  • synchronized方法控制对”对象“的访问,每个对象对应一把锁,每个synchronized方法都必须获得调该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获取这个锁,继续执行。

    缺陷:若将一个大的方法申明为synchronized将会影响效率

同步方法弊端

  • 方法里面需要修改的内容才需要锁,锁定太多,浪费资源

同步块

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

不安全案例修改为安全案例

案例一:

public class UnsafeBuyTicket {

    public static void main(String[] args) {
        BuyTicket b = new BuyTicket();

        new Thread(b,"老黄牛").start();
        new Thread(b,"网友").start();
        new Thread(b,"自己").start();
    }
}

class BuyTicket implements Runnable{
    // 票
    private int ticetNum = 10;

    // 外部停止方式
    boolean flag = true;

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

    // synchronized 同步方法,锁的是this
    private synchronized void buy(){
        // 判断是否有票
        if(ticetNum<=0){
            flag = false;
            return;
        }

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

        // 买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticetNum--);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mPvMWuv9-1617869030711)(D:\学习\tupian\多线程\线程同步\dui1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QkTkaj9U-1617869030715)(D:\学习\tupian\多线程\线程同步\dui1_s.png)]

就这么简单

案例二:

// 两个人去银行账户,取钱
public class UnsafeBank {
    public static void main(String[] args) {

        // 账户
        Account account = new Account("项目基金",1001);

        Drawing you = new Drawing(account,50,"组长");
        Drawing my = new Drawing(account,80,"组员");

        you.start();
        my.start();
    }
}

class Account{
     String name;
     int money;

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

// 银行:模拟取钱
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() {

        // synchronized默认锁的是this 就是它本身
        // synchronized块可以锁任何对象
        // 锁的对象就是变化的量,需要增删改的对象
        synchronized (account){
            // 判断有没有钱
            if (account.money-drawingMoney<0){
                System.out.println(this.getName()+"没钱了,取不了");
                return;
            }

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

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

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

            System.out.println(account.name+"余额为:"+account.money);
            System.out.println(this.getName()+"手里的钱"+nowMoney);
        }
    }
}

11

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4P8teuwq-1617869030721)(D:\学习\tupian\多线程\线程同步\dui2_s.png)]

为什么不把run方法锁了呢?那是因为synchronized默认锁的是this 就是本身,synchronized方法不能改变锁住的是谁,只能锁this。所以如果把run变成同步锁,就相当于把银行类锁了,你说人取钱你把银行锁了,那不白锁吗

案例三:

public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        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());
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VgfEwzMQ-1617869030726)(D:\学习\tupian\多线程\线程同步\dui3.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FnMn2ftY-1617869030729)(D:\学习\tupian\多线程\线程同步\dui3_s.png)]

总结:

很简单,实际上就是把方法变成了同步方法或用同步块套住你变化的量。虽然解决了问题,但是如果是一个很大的方法变成了同步方法会降低效率,但变得安全了,毕竟鱼和熊掌不能兼得。

;原文链接:https://blog.csdn.net/AMT_MUZI/article/details/115523112
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!
上一篇:网络安全基础——HLCA综合实验 下一篇:没有了

推荐图文


随机推荐