介绍
【线程的安全问题】
由于多个线程同时运行时,多个线程表现得不可控,即每个线程在某个实可运行到什么阶段是不可控的,所以,如果这些线程需要对同一个数据进行修改,则最终的值就不可控,即线程的安全问题。
使用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();
}
}
运行结果:程序会卡住,一直执行不完,这种嵌套的锁就会出现死锁。