文章目录

介绍

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

使用​​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();
}
}

再次运行程序:
【达内课程】线程的安全问题_安全问题_02
我们发现金额变成负数了,对金额加一个判断能不能解决这个问题呢?

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() + "取钱失败");
}
}
}

执行程序:
【达内课程】线程的安全问题_线程安全_03

【达内课程】线程的安全问题_安全问题_04
执行多次可能出错,为了增加出错几率,修改代码:

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() + "取钱失败");
}
}
}

运行程序:
【达内课程】线程的安全问题_线程安全_05
出现这种结果的原因由于是 2 个线程交替执行,执行过程如下图所示,可能 红色线程执行到判断​​​Bank.account>=4000​​​,判断通过了,就停下切换到了蓝色线程,而蓝色线程也执行到判断​​Bank.account>=4000​​​,由于余额并没有减少,所以判断也通过了,所以会减少 2 次。所以即使我们加了判断也是没有用的。
【达内课程】线程的安全问题_线程安全_06
而且我们增加了​​​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 一个,也可能程序运行过程中这个字符串会进行运算而改变值

运行程序:
【达内课程】线程的安全问题_线程安全_07

第二种写法

如果​​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();
}
}

运行程序:
【达内课程】线程的安全问题_thread_08
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();
}
}

运行结果:程序会卡住,一直执行不完,这种嵌套的锁就会出现死锁。
【达内课程】线程的安全问题_thread_09