多线程-安全性问题的解决
Posted TGB-Earnest
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程-安全性问题的解决相关的知识,希望对你有一定的参考价值。
多线程的安全性问题解析
我们在使用多线程的时候,可以进行异步操作提高我们程序执行的效率,比如,我们一个页面需要3个接口,后端返回的时候将这个三个接口统一成一个接口执行,这三个接口在这个大接口中,如果按串行的方式执行,那么时间就是这三个接口所需时间之和,如果我们采用多线程的方式,那么返回的时间取决于这三个接口中所用时间最大的接口。
我们再来看一下他的问题。
package com.broky.multiThread.safeThread;
/**
* @author zxd
* @date 2022-04-26 20:39
*/
public class SafeTicketsWindow
public static void main(String[] args)
WindowThread ticketsThread02 = new WindowThread();
Thread t1 = new Thread(ticketsThread02);
Thread t2 = new Thread(ticketsThread02);
Thread t3 = new Thread(ticketsThread02);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
class WindowThread implements Runnable
private int tiketsNum = 100;
public void run()
while (true)
if (tiketsNum > 0)
try
//手动让线程进入阻塞,增大错票概率
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + ":\\t票号:" + tiketsNum);
/*try
//手动让线程进入阻塞,增大重票的概率
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
*/
tiketsNum--;
else
break;
打印的结果
窗口3: 票号:10
Thread-2: 票号:10
Thread-1: 票号:8
Thread-2: 票号:7
Thread-1: 票号:7
窗口3: 票号:7
Thread-2: 票号:4
窗口3: 票号:4
Thread-1: 票号:4
Thread-1: 票号:1
窗口3: 票号:1
Thread-2: 票号:1
我们发现有重复消费的现象,一个票销售给了两个人
分析:
我们发现执行结果中有重票的情况,
如果t1在输出票号7和票数进行减一的操作之前被阻塞,这就导致这时候t1卖出了7好票,但是总票数没有减少,在t1被阻塞期间,如果t2运行到输出票号时,
那么t2也会输出和t1相同的票号7
通过以上两种情况可以看出,线程的安全性问题时因为多线程正在执行代码的过程中,并且尚未完成的时候,其他线程参与进来执行代码所导致的。
当然还有可能输出的是负值的情况。
多线程安全性问题的解决
原理:
当一个线程在操作共享数据的时候,其他线程不能参与进来。直到这个线程操作完共享数据的时候,其他线程才可以从操作,即使当这个线程操作共享数据的时候发生了阻塞,依旧无法改变这种情况。
在Java中,我们通过同步机制,来解决线程的安全问题
多线程安全问题的解决方式一: 同步代码块
synchronized(同步监视器)需要被同步的代码
说明:
1、操作共享数据(多个线程共同操作的变量)的代码,即为需要被同步的代码。 不能多包涵代码(效率低,如果包到while前面就变成了单线程了),也不能少包含代码
2、共享数据:多个线程共同操作的变量。
3、同步监视器:俗称,锁。任何一个类的对象都可以充当锁。但是所有的线程都必须共用一把锁,共用一个对象。
锁的选择:
1、自行创建,共用对象,如下面demo中的Object对象。
2、使用this表示当前类的对象继承Thread的方法中的锁不能使用this代替,因为继承thread实现多线程时,会创建多个子类对象来代表多个线程,这个时候this指的时当前这个类的多个对象,不唯一,无法当作锁。实现Runnable接口的方式中,this可以当作锁,因为这种方式只需要创建一个实现类的对象,将实现类的对象传递给多个Thread类对象来当作多个线程,this就是这个一个实现类的对象,是唯一的,被所有线程所共用的对象。
3、使用类当做锁,以下面demo为例,其中锁可以写为WindowThread.class ,从这里可以得出结论,类也是一个对象
package com.broky.multiThread.safeThread;
/**
* @author zhaoxiaodong
* @date 2022-04-26 20:39
*/
public class SafeTicketsWindow
public static void main(String[] args)
WindowThread ticketsThread02 = new WindowThread();
Thread t1 = new Thread(ticketsThread02);
Thread t2 = new Thread(ticketsThread02);
Thread t3 = new Thread(ticketsThread02);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
class WindowThread implements Runnable
private int tiketsNum = 100;
//由于,Runnable实现多线程,所有线程共用一个实现类的对象,所以三个线程都共用实现类中的这个Object类的对象。
Object obj = new Object();
//如果时继承Thread类实现多线程,那么需要使用到static Object obj = new Object();
public void run()
//Object obj = new Object();
//如果Object对象在run()方法中创建,那么每个线程运行都会生成自己的Object类的对象,并不是三个线程的共享对象,所以并没有给加上锁。
while (true)
synchronized (obj)
if (tiketsNum > 0)
try
//手动让线程进入阻塞,增大安全性发生的概率
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + ":\\t票号:" + tiketsNum + "\\t剩余票数:" + --tiketsNum);
else
break;
打印的结果:
Thread-2: 票号:15
Thread-1: 票号:14
Thread-1: 票号:13
Thread-2: 票号:12
Thread-2: 票号:11
Thread-2: 票号:10
Thread-2: 票号:9
Thread-2: 票号:8
Thread-2: 票号:7
Thread-2: 票号:6
Thread-2: 票号:5
窗口3: 票号:4
窗口3: 票号:3
窗口3: 票号:2
窗口3: 票号:1
多线程安全问题的解决方式二:同步方法
将所要同步的代码放到一个方法中,将方法声明为synchronized同步方法。之后可以在run()方法中调用同步方法。
要点:
1、同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
2、非静态的同步方法,同步监视器是:this。
3、静态的同步方法,同步监视器是:当前类本身。
package com.broky.multiThread.safeThread;
/**
* @author zhaoxiaodong
* @date 2022-04-26 22:39
*/
public class Window02
public static void main(String[] args)
Window02Thread ticketsThread02 = new Window02Thread();
Thread t1 = new Thread(ticketsThread02);
Thread t2 = new Thread(ticketsThread02);
Thread t3 = new Thread(ticketsThread02);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
class Window02Thread implements Runnable
private int tiketsNum = 100;
@Override
public void run()
while (tiketsNum > 0)
show();
private synchronized void show() //同步监视器:this
if (tiketsNum > 0)
try
//手动让线程进入阻塞,增大安全性发生的概率
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + ":\\t票号:" + tiketsNum + "\\t剩余票数:" + --tiketsNum);
package com.broky.multiThread.safeThread;
/**
* @author zhaoxiaodong
* @date 2022-04-26 9:36
*/
public class Window03
public static void main(String[] args)
Window03Thread t1 = new Window03Thread();
Window03Thread t2 = new Window03Thread();
Window03Thread t3 = new Window03Thread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.setPriority(Thread.MIN_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
t3.start();
class Window03Thread extends Thread
public static int tiketsNum = 100;
@Override
public void run()
while (tiketsNum > 0)
show();
public static synchronized void show() //同步监视器:Winddoe03Thread.class 不加static话同步监视器为t1 t2 t3所以错误
if (tiketsNum > 0)
try
//手动让线程进入阻塞,增大安全性发生的概率
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + ":\\t票号:" + tiketsNum + "\\t剩余票数:" + --tiketsNum);
使用同步解决懒汉模式的线程安全问题
package com.broky.multiThread.safeThread;
/**
* @author zhaoxiaodong
* @date 2022-04-26 9:36
*/
public class BankTest
class Bank
private Bank()
private static Bank instance = null;
public static Bank getInstance()
//方式一:效率性差,每个等待线程都会进入同步代码块
// synchronized (Bank.class)
// if (instance == null)
// instance = new Bank();
//
//
//方式二:在同步代码块外层在判断一次,就防止所有线程进入同步代码块。
if (instance == null)
synchronized (Bank.class)
if (instance == null)
instance = new Bank();
return instance;
多线程安全问题的解决方式二:Lock 锁 -JDK5.0新特性
JDK5.0之后,可以通过实例化ReentrantLock对象,在所需要同步的语句前,调用ReentrantLock对象的lock()方法,实现同步锁,在同步语句结束时,调用unlock()方法结束同步锁
建议使用顺序:Lock—》同步代码块(已经进入了方法体,分配了相应的资源)—》同步方法(在方法体之外)
package com.broky.multiThread.safeThread;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author zhaoxiaodong
* @date 2022-04-26 9:36
*/
public class SafeLock
public static void main(String[] args)
SafeLockThread safeLockThread = new SafeLockThread();
Thread t1 = new Thread(safeLockThread);
Thread t2 = new Thread(safeLockThread);
Thread t3 = new Thread(safeLockThread);
t1.start();
t2.start();
t3.start();
class SafeLockThread implements Runnable
private int tickets = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run()
while (tickets>0)
try
//在这里锁住,有点类似同步监视器
lock.lock();
if (tickets > 0)
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + ":\\t票号:" + tickets + "\\t剩余票数:" + --tickets);
catch (InterruptedException e)
e.printStackTrace();
finally
//操作完成共享数据后在这里解锁
lock.unlock();
持续更新中…
以上是关于多线程-安全性问题的解决的主要内容,如果未能解决你的问题,请参考以下文章