线程的安全问题
线程安全问题触发的几个条件:
- 要有多线程并发或并行操作
- 要有共享数据,即两个或多个线程处理一个数据
- 要有对共享数据的读写操作
问题1:设计一个售票系统,售票系统要求有一个固定的票数,且多个售票窗口同时进行
代码展示与结果展示
package com.qianfeng.day16;
public class TickWindow implements Runnable{
public static int ticks = 10;//总票数
public void Booking() {
Thread cu = Thread.currentThread();
while(true) {
if(TickWindow.ticks > 0) {
//输出售卖信息
System.out.println(cu.getName() + ":卖出第" + (11-TickWindow.ticks) + "张票!");
TickWindow.ticks--;
} else {
System.out.println(cu.getName() + "票已经售完");
break;
}
//售票后等待
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
@Override
public void run() {
// TODO Auto-generated method stub
this.Booking();
}
}
class WorkTest01 {
public static void main(String[] args) {
TickWindow tw = new TickWindow();
Thread t1 = new Thread(tw,"售票窗口一");
Thread t2 = new Thread(tw,"售票窗口二");
Thread t3 = new Thread(tw,"售票窗口三");
t1.start();
t2.start();
t3.start();
}
}
发现问题:
在此程序中存在共享数据ticks(票数)在此票数的基础上,共有三个线程对票数进行了操作,现三个线程分别为“窗口一”、“窗口二”、“窗口三”。而每个线程都会被CPU分配一定的时间片。
通过上述三个线程可能存在这种情况,在窗口一进入循环并符合判断条件的时候,发现突然时间片用完了,这时还没有对共享数据进行操作,那么这个时候此线程就要终止,那么当下一个线程进入判断条件的时候,之前的一个线程还没有对共享数据进行操作,这时进入判断条件的共享数据跟之前一样,会出现共享数据的重复操作。这个过程其实就是线程安全问题的实际体现。
解决问题:
使用synchronized关键字对共享数据操作的代码进行保护,synchronized也称为是“同步锁”
解决问题代码与截图
问题2:模拟在一个房间中存在两把锁,每把锁都能够保证对应的房间在某一时刻只有一个线程进入
代码展示与结果展示:
package com.qianfeng.Test02;
public class MyHome{
private Object objstudent = new Object();
private Object objsleep = new Object();
public void student() {
Thread cu = Thread.currentThread();
synchronized(objstudent) {
System.out.println(cu.getName() + "进入该房间学习" + "现在时间:" + System.currentTimeMillis());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(cu.getName() + "退出学习房间交还锁" + "现在时间:" + System.currentTimeMillis());
synchronized(objsleep) {
System.out.println("学习累了准备休息");
}
}
}
public void sleep() {
Thread cu = Thread.currentThread();
synchronized(objsleep) {
System.out.println(cu.getName() + "进入该房间睡觉" + "现在时间:" + System.currentTimeMillis());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(cu.getName() + "退出睡觉房间交还锁" + "现在时间:" + System.currentTimeMillis());
synchronized(objstudent) {
System.out.println("休息完成准备学习");
}
}
}
}
class Test01 {
public static void main(String[] args) throws InterruptedException {
MyHome mh1 = new MyHome();
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
mh1.student();
mh1.sleep();
}
},"张三");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
mh1.sleep();
mh1.student();
}
},"李四");
t1.start();
t2.start();
t1.join(6000);
t2.join(6000);
int i = 0;
while(true) {
if(i % 1000000000 == 0) {
System.out.println("现在时间:" + System.currentTimeMillis());
}
i++;
}
}
}
发现问题:
在此问题中可以发现需要进入学习房间和睡觉房间都需要一把“锁”才能进去,如果当一个人进入学习的房间之后需要学习3个小时,这时有一个人在睡觉房间需要睡觉5小时。那么当两个人都进入房间后都拥有一把不同的锁,当学习房间的人学习完了之后想要睡觉那么他就需要睡觉房间的锁,但这时睡觉的人还没有醒,当他醒后想要学习的时候,同时也需要另一把锁,这时就会发现两个人相互牵制,都想要对方的锁才能进行后续操作,但都不放开自己的锁,这时就会出现“死锁”,让程序停止不动。
此问题的本质就是因为锁太细分了。
解决问题
让两个房间公用同一把锁,虽说这时解决了问题,但是在开发的过程中锁的粒度越细,程序的并发性越高,所以设计的时候除了保证线程的安全之外也要考虑并发的性能,防止锁的粒度太细发生“死锁”现象。
线程安全的细节问题
- 临界区的概念:对共享数据的读写操作的那部分代码
- 所谓的同步指的是能够让多个线程依次排队进入到同步代码块中去执行,只要有一个线程没有完全执行完成里面的所有代码则不会释放锁,其他的线程就只能处于等待的状态
- 同步的过程也会带来效率的问题,在同步时该线程没有执行完其他线程就要浪费此时的时间片
- synchronized(锁) 要求锁对象必须是多个线程使用同一把,否则这个锁就无法在多个线程中实现互斥的要求
- synchronized不仅可以用来修饰代码块,还可以用来修饰整个方法,此时这样的方法称为同步方法,同步方法中锁即用this表示,表示当前对象,保证使用同一个对象使用同一把锁
- 任何对象都能当锁
- 同步方法同步是整个的方法体,使用的锁默认两种情况:1.如果是一个实例方法默认使用的是this关键字作为锁对象2.如果这个方法是一个静态方法默认锁的是当前类的字节码对象,字节码对象在内存中只有一份