线程安全问题
2021-04-22 18:27
标签:线程 queue 情况 oca 释放 sys while src java 多个线程同时运行同一个实现了Runnable接口的类,程序每次运行结果和单线程运行结果是一样的,其他变量的值和预期的一样,就称之为线程安全的,反之则是不安全的 如下模拟一个抢票系统: 定义一个Ticket线程类 主程序里我们同时让三个线程调用同一个Ticket对象来进行抢票 预期结果应该是三个窗口各自抢票,剩余票数要实时的进行减少,而以上代码的实际效果如下: 可以看出,结果和我们预期的完全不同,分析可知,当多个线程一起对执行一个Runnable接口对象时,会出现以上情况,多个线程结果相同,不符合逻辑,致错原因如下所示: 三个线程同时进入if方法,并发情况下先后打印了结果,其他线程还没来得及count--就打印了,所以下次三个线程打印了一样的结果,count--执行了三次,下次循环还是一样的问题,所以出现了以上结果 总结问题: 定义一个锁对象,作为进入代码块的钥匙 将需要保证线程安全的代码加入到synchronized下的代码块中,起到安全作用 只有获取到obj的线程才能执行代码块,其余的线程必须等该线程运行完释放锁,再获取资源 同步方法与同步代码块类似,使用的是synchronized关键字,不过他是基于方法层面的,关键字在方法上 synchronized加在线程要运行的方法上,java会自动给该方法加上一个锁对象,类似同步代码块中的obj 只有线程拥有该锁对象时,才能运行方法,没有锁对象的方法需要在方法外等待 java.util.Concurrent.locks.Lock 机制提供了比synchronized关键字更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,lock有更强大的功能,更体现面向对象 同步锁的方法 锁有多种,后面会有详细专题,这里先暂时使用以下重入锁(释放后还可以被调用的锁),以下为使用方式 实验结果发现:当重入锁ReentrantLock的参数为true,即公平锁时,每个线程是公平的获得执行方法的权力,结果是非常有规律的1,2,3,1,2,3;而当定义为独占锁时,才有随机的效果,每个线程谁先获得锁,就可以执行。 线程安全问题 标签:线程 queue 情况 oca 释放 sys while src java 原文地址:https://www.cnblogs.com/JIATCODE/p/13276327.html线程安全
1.概念
2.问题演示
public class Ticket implements Runnable{
private int Count = 100;//100张票在售
public void run() {
while (true){
//有剩余票数
if (Count>=0){
//睡眠100毫秒模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出模拟抢票结果
String name = Thread.currentThread().getName();
System.out.println(name+"执行;剩余票数:"+ Count);
Count--;
}
}
}
public class TicketSafe {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread thread = new Thread(ticket,"窗口一");
Thread thread2 = new Thread(ticket,"窗口二");
Thread thread3 = new Thread(ticket,"窗口三");
thread.start();
thread2.start();
thread3.start();
}
}
窗口二执行;剩余票数:100
窗口一执行;剩余票数:100
窗口三执行;剩余票数:100
窗口一执行;剩余票数:97
窗口二执行;剩余票数:97
窗口三执行;剩余票数:97
窗口二执行;剩余票数:94
窗口一执行;剩余票数:94
窗口三执行;剩余票数:94
窗口二执行;剩余票数:91
窗口三执行;剩余票数:90
窗口一执行;剩余票数:90
窗口一执行;剩余票数:88
窗口二执行;剩余票数:88
................
3.实现线程安全
1.思路
2.7种线程同步机制
3.同步代码块(synchronized)
//创建锁对象:相当于打开代码块的钥匙
private Object obj = new Object();
public void run() {
while (true){
//有剩余票数
//同步代码块,线程到这里的时候,都会去请求一个obj资源,只有一个线程可以拿到
//拿到obj的线程才能进入代码块,其他请求的线程只能继续等待,等待obj锁对象被释放
synchronized (obj){
.....................
}
}
}
4.同步方法
//对于非static方法,调用该方法的Runnable实现类对象实例就是锁对象即this,注意对于多个线程来说,他们的this得是同一个实例对象,不然达不到互斥作用,相当于synchronized(new Ticket())
//对于static方法,当前方法所在类的字节码对象就是锁对象,相当于synchronized(Ticket.class)
private synchronized void threadSafe(){
if (Count>=0){
//睡眠100毫秒模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出模拟抢票结果
String name = Thread.currentThread().getName();
System.out.println(name+"执行;剩余票数:"+ Count);
Count--;
}
}
public void run() {
while (true) {
threadSafe();
}
}
5.同步锁(Lock)
public void lock(); //加同步锁
public void unlock(); //释放同步锁
//创建一个lock对象,重入锁实例
//参数fair:
// true---公平锁:所有线程都能公平的得到机会
// false(默认)---独占锁:只有第一个得到的线程可以使用,除非它主动放弃或者释放
Lock lock = new ReentrantLock(false);
public void run() {
while (true) {
lock.lock();//加上锁,只要有这个方法就一定要在某处有unlock,否则会导致死锁
//为保证unlock一定被执行,使用try finally来实现
try{
if (Count>=0){
//睡眠100毫秒模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出模拟抢票结果
String name = Thread.currentThread().getName();
System.out.println(name+"执行;剩余票数:"+ Count);
Count--;
}
}finally {
//保证释放锁
lock.unlock();
}
}
}
关于锁的小结