【达内课程】线程的安全问题

介绍

【线程的安全问题】
由于多个线程同时运行时,多个线程表现得不可控,即每个线程在某个实可运行到什么阶段是不可控的,所以,如果这些线程需要对同一个数据进行修改,则最终的值就不可控,即线程的安全问题。

使用synchronized互斥锁可以解决线程的安全问题。

【使用synchronized】
1、使用 synchronized(对象) {代码} 的语法将需要锁定的代码进行锁定,锁定的对象必须是相对于多个线程而言的,访问到的是同一个对象。
2、使用synchronized修饰方法,表示锁定该方法中的所有代码,当锁定方法时,锁定的对象就是 this,所以,如果某代码不能够 synchronized(this)实现互斥锁,则使用 synchronized修饰方法是无效的。

【小结】
当出现线程安全问题时,使用synchronized("hello")所著所有代码即可

线程的安全问题描述

我们用线程模拟一个取钱过程

Bank类

public class Bank {
	public static int count = 5000;
}

Person类

public class Person extends Thread {
    public Person(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(getName() + "开始取钱,当前余额:" + Bank.count);
        Bank.count -= 4000;
        System.out.println(getName() + "取钱结束,当前余额:" + Bank.count);
    }
}

Main

public class Main {
    public static void main(String[] args) {
        Person p = new Person("我");
        p.start();
    }
}

运行程序:
在这里插入图片描述
刚才是正常情况下的取钱,如果 2 个人同时取钱会怎样呢,我们来模拟下:

Test

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("我");
        Person p2 = new Person("妈妈");
        p1.start();
        p2.start();
    }
}

再次运行程序:
在这里插入图片描述
我们发现金额变成负数了,对金额加一个判断能不能解决这个问题呢?

Person

public class Person extends Thread {
    public Person(String name) {
        super(name);
    }

    @Override
    public void run() {
        if (Bank.count >= 4000) {
            Bank.count -= 4000;
            System.out.println(getName() + "取钱结束,当前余额:" + Bank.count);
        } else {
            System.out.println(getName() + "取钱失败");
        }
    }
}

执行程序:
在这里插入图片描述

在这里插入图片描述
执行多次可能出错,为了增加出错几率,修改代码:

Person

public class Person extends Thread {
    public Person(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(getName() + "开始取钱,当前余额:" + Bank.count);
        if (Bank.count >= 4000) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Bank.count -= 4000;
            System.out.println(getName() + "取钱结束,当前余额:" + Bank.count);
        } else {
            System.out.println(getName() + "取钱失败");
        }
    }
}

运行程序:
在这里插入图片描述
出现这种结果的原因由于是 2 个线程交替执行,执行过程如下图所示,可能 红色线程执行到判断Bank.account>=4000,判断通过了,就停下切换到了蓝色线程,而蓝色线程也执行到判断Bank.account>=4000,由于余额并没有减少,所以判断也通过了,所以会减少 2 次。所以即使我们加了判断也是没有用的。
在这里插入图片描述
而且我们增加了Thread.sleep(1);程序的错误就必然出现,红色线程执行到Bank.account>=4000,就 sleep,换蓝色线程执行Bank.account>=4000,2次判断都通过了,程序必然出错。

synchronized

第一种写法

想解决这个问题我们可以让if(){}这段语句能整体一次性执行,使用synchronized互斥锁可以解决线程的安全问题,代码如下:

public class Person extends Thread {
    public Person(String name) {
        super(name);
    }

    public static Object lock = new Object();

    @Override
    public void run() {
        System.out.println(getName() + "开始取钱,当前余额:" + Bank.count);
        synchronized (lock) {
            if (Bank.count >= 4000) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Bank.count -= 4000;
                System.out.println(getName() + "取钱结束,当前余额:" + Bank.count);
            } else {
                System.out.println(getName() + "取钱失败");
            }
        }
    }
}

绝对靠谱的锁可以在里边放一个字符串

 synchronized("hello_world");

或者保证变量前有static

public static Object lock = new Object();

不可以用一个变量存一个字符串,因为每 new 一个新对象,字符串变量也会 new 一个,也可能程序运行过程中这个字符串会进行运算而改变值

运行程序:
在这里插入图片描述

第二种写法

如果implements Runnable接口,那么也可以这样写:

synchronized(this)

我们改变 Person 代码
Person

public class Person implements Runnable {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public void run() {
        System.out.println(getName() + "开始取钱,当前余额:" + Bank.count);
        synchronized (this) {
            if (Bank.count >= 4000) {
                Bank.count -= 4000;
                System.out.println(getName() + "取钱结束,当前余额:" + Bank.count);
            } else {
                System.out.println(getName() + "取钱失败");
            }
        }
    }
}

Main

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("我");
        Person p2 = new Person("妈妈");
        Thread t1 = new Thread(p1);
        Thread t2 = new Thread(p2);
        t1.start();
        t2.start();
    }
}

运行程序:
在这里插入图片描述
Person 中的代码也可以这样写:

@Override
    public synchronized void run() {
        System.out.println(getName() + "开始取钱,当前余额:" + Bank.count);

        if (Bank.count >= 4000) {
            Bank.count -= 4000;
            System.out.println(getName() + "取钱结束,当前余额:" + Bank.count);
        } else {
            System.out.println(getName() + "取钱失败");
        }
    }

线程的死锁

【线程的死锁】
当存在 2 个或以上的线程时,每个线程都锁定某个对象的同时,还尝试去锁定被其他线程已经锁定的对象,就会出现死锁。

我们来模拟一个死锁

新建 DeadLockThread

public class DeadLockThread extends Thread {
    public int flag;
    public static Object lock1 = new Object();
    public static Object lock2 = new Object();

    public DeadLockThread(int flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag == 1) {
            synchronized (lock1) {
                System.out.println("锁住了lock1");

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

                synchronized (lock2) {
                    System.out.println("锁住了lock2");
                }
            }
        } else {
            synchronized (lock2) {
                System.out.println("锁住了lock2");

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

                synchronized (lock1) {
                    System.out.println("锁住了lock1");
                }
            }
        }
    }
}

Main

public class Main {
    public static void main(String[] args) {
        DeadLockThread t1 = new DeadLockThread(1);
        DeadLockThread t2 = new DeadLockThread(2);

        t1.start();
        t2.start();
    }
}

运行结果:程序会卡住,一直执行不完,这种嵌套的锁就会出现死锁。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值