保障接口安全的5种常见方式
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了保障接口安全的5种常见方式相关的知识,希望对你有一定的参考价值。
参考技术A 一般有五种方式:
1、Token授权认证,防止未授权用户获取数据;
2、时间戳超时机制;
3、URL签名,防止请求参数被篡改;
4、防重放,防止接口被第二次请求,防采集;
5、采用HTTPS通信协议,防止数据明文传输;
所有的安全措施都用上的话有时候难免太过复杂,在实际项目中需要根据自身情况作出取舍,比如可以只使用签名机制就可以保证信息不会被篡改,或者定向提供服务的时候只用Token机制就可以了,如何取舍,全看项目实际情况和对接口安全性的要求。
HTTP协议是无状态的,一次请求结束,连接断开,下次服务器再收到请求,它就不知道这个请求是哪个用户发过来的,但是对我们有权限访问限制的模块而言,它是需要有状态管理的,以便服务端能够准确的知道HTTP请求是哪个用户发起的,从而判断他是否有权限继续这个请求。
Token的设计方案是用户在客户端使用用户名和密码登录后,服务器会给客户端返回一个Token,并将Token以键值对的形式存放在缓存(一般是Redis)中,后续客户端对需要授权模块的所有操作都要带上这个Token,服务器端接收到请求后进行Token验证,如果Token存在,说明是授权的请求。
Token生成的设计要求:
1、应用内一定要唯一,否则会出现授权混乱,A用户看到了B用户的数据;
2、每次生成的Token一定要不一样,防止被记录,授权永久有效;
3、一般Token对应的是Redis的key,value存放的是这个用户相关缓存信息,比如:用户的id;
4、要设置Token的过期时间,过期后需要客户端重新登录,获取新的Token,如果Token有效期设置较短,会反复需要用户登录,体验比较差,我们一般采用Token过期后,客户端静默登录的方式,当客户端收到Token过期后,客户端用本地保存的用户名和密码在后台静默登录来获取新的Token,还有一种是单独出一个刷新Token的接口,但是一定要注意刷新机制和安全问题;
根据上面的设计方案要求,我们很容易得到Token=md5(用户ID+登录的时间戳+服务器端秘钥)这种方式来获得Token,因为用户ID是应用内唯一的,登录的时间戳保证每次登录的时候都不一样,服务器端秘钥是配置在服务器端参与加密的字符串(即:盐),目的是提高Token加密的破解难度,注意一定不要泄漏;
客户端每次请求接口都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如:1分钟),则认为该请求失效。时间戳超时机制是防御DOS攻击的有效手段。
写过支付宝或微信支付对接的同学肯定对URL签名不陌生,我们只需要将原本发送给server端的明文参数做一下签名,然后在server端用相同的算法再做一次签名,对比两次签名就可以确保对应明文的参数有没有被中间人篡改过。
签名算法:
1、首先对通信的参数按key进行字母排序放入数组中(一般请求的接口地址也要参与排序和签名,那么需要额外添加url= http://url/getInfo 这个参数);
2、对排序完的数组键值对用&进行连接,形成用于加密的参数字符串;
3、在加密的参数字符串前面或者后面加上私钥,然后用md5进行加密,得到sign,然后随着请求接口一起传给服务器。
注意: 对于客户端的私钥一定要妥善处理好,不能被非法者拿到,如果针对于H5的项目,H5保存私钥是个问题,目前没有更好的方法,也是一致困扰我的问题,如果大家有更好的方法可以留言一起探讨。
客户端第一次访问时,将签名sign存放到服务器的Redis中,超时时间设定为跟时间戳的超时时间一致,二者时间一致可以保证无论在timestamp限定时间内还是外 URL都只能访问一次,如果被非法者截获,使用同一个URL再次访问,如果发现缓存服务器中已经存在了本次签名,则拒绝服务。如果在缓存中的签名失效的情况下,有人使用同一个URL再次访问,则会被时间戳超时机制拦截,这就是为什么要求sign的超时时间要设定为跟时间戳的超时时间一致。拒绝重复调用机制确保URL被别人截获了也无法使用(如抓取数据)。
方案流程:
1、客户端通过用户名密码登录服务器并获取Token;
2、客户端生成时间戳timestamp,并将timestamp作为其中一个参数;
3、客户端将所有的参数,包括Token和timestamp按照自己的签名算法进行排序加密得到签名sign
4、将token、timestamp和sign作为请求时必须携带的参数加在每个请求的URL后边
5、服务端对token、timestamp和sign进行验证,只有在token有效、timestamp未超时、缓存服务器中不存在sign三种情况同时满足,本次请求才有效;
众所周知HTTP协议是以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了客户端和服务器之间的传输报文,就可以直接读懂其中的信息,因此HTTP协议不适合传输一些敏感信息,比如信用卡号、密码等。
为了解决HTTP协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议HTTPS,为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为客户端和服务器之间的通信加密。
HTTPS也不是绝对安全的,如下图所示为中间人劫持攻击,中间人可以获取到客户端与服务器之间所有的通信内容。
中间人截取客户端发送给服务器的请求,然后伪装成客户端与服务器进行通信;将服务器返回给客户端的内容发送给客户端,伪装成服务器与客户端进行通信。 通过这样的手段,便可以获取客户端和服务器之间通信的所有内容。 使用中间人攻击手段,必须要让客户端信任中间人的证书,如果客户端不信任,则这种攻击手段也无法发挥作用。
针对安全性要求一般的app,可采用通过校验域名,证书有效性、证书关键信息及证书链的方式。
以上说的更多是设计阶段的思路,如果API已经在运行的话,我们则需要通过其他方式,如API网关工具来保护我们的API,这里推荐的是Eolinker,对于上述的5个方面,都有对应的功能做到保护API,可以自己部署开源版本试用一下: www.eolinker.com
27多线程(多线程的三种实现方式Thread线程类的常见方法线程安全问题)
多线程
多线程实现方式一
Thread
Java虚拟机允许应用程序同时执行多个线程。
每个线程都有优先级,具有较高优先级的线程优先于优先级较低的线程执行。
当在某个线程中运行的代码创建一个新的Thread
对象时,新线程的优先级最初设置为创建线程的优先级。
每个线程都有一个用于识别的名称,多个线程可能具有相同的名称。如果在创建线程时未指定名称,则会为其生成一个新名称。
继承Thread类,重写Thread类的run方法
public class Thread implements Runnable {
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
//继承Thread,重写run方法的逻辑为:不再调用target.run()方法,而是调用子类自定义的[线程要执行的方法]
//所以Thread() / Thread(String name)的构造方式,target为null,执行的是子类MyThread的run()
//Thread类继承Runnable接口,实现run方法,target代表Runnable实例对象,即创建Thread对象时传递的Runnable类型的参数
//Thread(Runnable target)的构造方式,就不需要重写run方法,target.run()调用Runnable实现类的run逻辑
Thread类的常用方法
方法名 | 描述 |
---|---|
public final String getName()【final修饰方法,子类不允许重写】 | 获取当前线程的名称 |
public final int getPriority() | 获取当前线程的优先级 |
public final void setName(String name) | 设置当前线程的名称 |
public final void setPriority(int newPriority) | 设置当前线程优先级 |
public static native Thread currentThread(); | 获取当前执行的线程 |
public final static int MIN_PRIORITY = 1; | 最低优先级 |
public final static int NORM_PRIORITY = 5; | 默认优先级 |
public final static int MAX_PRIORITY = 10; | 最高优先级 |
package org.westos.demo;
/**
* @author lwj
* @date 2020/5/31 13:53
*/
public class MyTest {
public static void main(String[] args) {
System.out.println("主线程的main方法开始执行");
System.out.println(Thread.currentThread().getName());
//获取当前正在执行的线程对象Thread.currentThread()
//main
System.out.println(Thread.currentThread().getPriority());
//获取当前正在执行的线程的优先级
//5
//创建一个线程,1、继承Thread类,重写run方法
MyThread myThread = new MyThread();
myThread.setName("myThread-0");
//设置线程的名称
System.out.println(myThread.getName());
//获取线程的名称
//myThread-0
System.out.println(myThread.getPriority());
//获取线程的优先级
//5
myThread.setPriority(10);
//设置线程的优先级
System.out.println(myThread.getPriority());
//10
myThread.start();
//启动线程,同一线程不要重复启动
//创建另一个线程
MyThread myThread1 = new MyThread();
myThread1.setName("myThread-1");
System.out.println(myThread1.getName());
//myThread-1
System.out.println(myThread1.getPriority());
//5
myThread1.start();
/*
两个线程轮流被操作系统调度,cpu执行,具有随机性
*/
System.out.println("主线程的main方法执行完毕");
}
}
class MyThread extends Thread {
@Override
public void run() {
//run方法:该线程执行的代码
for (int i = 0; i < 100; i++) {
System.out.println("线程" + this.getName() + ":" + i);
}
}
}
线程并不一定立即执行,由操作系统进行调度。
package org.westos.demo;
/**
* @author lwj
* @date 2020/6/1 16:33
*/
public class MyTest {
public static void main(String[] args) {
MyThread myThread = new MyThread("myThread-0");
myThread.setPriority(Thread.MAX_PRIORITY);
myThread.start();
}
}
class MyThread extends Thread {
public MyThread() {}
public MyThread(String name) {
super(name);
//MyThread子类不能继承父类Thread的构造方法,但是可以使用super关键字调用父类的构造方法
}
@Override
public void run() {
System.out.println("线程" + this.getName() + this.getPriority());
//MyThread继承Thread,自动继承Thread类的getName()和getPriority()
//线程myThread-010
}
}
缺陷:单继承具有局限性。
多线程实现方式二
实现Runnable接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
package org.westos.demo2;
/**
* 实现Runnable接口
* @author lwj
* @date 2020/6/1 20:01
*/
public class MyTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable, "A线程");
//构造方法:Thread(Runnable runnableImpl)
//Thread(Runnable target, String name)
Thread thread1 = new Thread(myRunnable, "B线程");
//虽然是一个相同的任务(对象),但是两个线程在执行run方法时,都会有自己的内存空间来执行for循环,来存储变量i,所以两个变量i的内存地址不同,那么就是两次的0-19
thread.start();
//开启线程
thread1.start();
}
/*
B线程---0
B线程---1
B线程---2
B线程---3
B线程---4
A线程---0
A线程---1
A线程---2
B线程---5
B线程---6
B线程---7
B线程---8
B线程---9
A线程---3
A线程---4
A线程---5
A线程---6
A线程---7
A线程---8
A线程---9
每个线程都有自己的PC寄存器/程序计数器,还有自己的虚拟机栈内存,存储自己线程的方法调用
*/
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
优点:避免了单继承的局限性,方便同一个对象被多个线程使用。
多线程实现方式三
实现Callable接口
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
相较于实现Runnable接口的方式,方法有返回值,并且可以抛出异常。
这种方式需要 FutureTask 实现类的支持(包装Callable),用于接收运算结果。FutureTask 是Runnable接口的实现类,Thread(Runnable target)来接收FutureTask实例。
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
package org.westos.demo2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 实现Callable接口
* @author lwj
* @date 2020/6/1 20:18
*/
public class MyTest2 {
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable(100));
Thread thread = new Thread(futureTask);
//Thread(Runnable target)
thread.start();
try {
Integer integer = futureTask.get();
//FutureTask<V>用于接收运算结果
System.out.println(integer);
//5050
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int num;
public MyCallable(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= num; i++) {
sum += i;
}
return sum;
}
}
Runnable和Callable的区别
- Runnable,让线程来执行run方法,没有返回值,如果run方法内部有异常,那么不能抛出异常,因为父类接口方法声明中没有抛出,所以只能try-catch;
- Callable,让线程来执行call()方法,有返回值,而且call方法可以抛出异常。
Thread常见方法
线程休眠
Thread.sleep()是Thread类的一个静态方法,使当前线程休眠,进入阻塞状态(暂停执行),休眠结束后,由阻塞态进入就绪态;如果线程在睡眠状态被中断,将会抛出IterruptedException中断异常。
注意:在哪个线程里面调用sleep()方法就阻塞哪个线程。
package org.westos.demo;
/**
* 线程休眠Thread.sleep,让当前线程休眠
* @author lwj
* @date 2020/6/1 16:49
*/
public class MyTest2 {
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程开始执行了");
Thread.sleep(2000);
//主线程休眠
System.out.println("主线程休眠后的代码");
MyThread3 myThread = new MyThread3("A线程");
MyThread3 myThread1 = new MyThread3("B线程");
myThread.start();
myThread1.start();
}
}
class MyThread3 extends Thread {
public MyThread3() {}
public MyThread3(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000);
//实现Runnable接口的方式创建线程,在run方法中如果存在异常,那么不能向外抛出,只能在内部try-catch
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "---" + i);
}
}
}
join
在A线程中调用B线程的join()方法,那么A线程必须等待B线程执行完毕后,才可以继续执行。
package org.westos.demo;
/**
* join
* @author lwj
* @date 2020/6/1 18:52
*/
public class MyTest3 {
public static void main(String[] args) throws InterruptedException {
MyThread1 myThread = new MyThread1("刘备");
MyThread1 myThread1 = new MyThread1("关羽");
MyThread1 myThread2 = new MyThread1("张飞");
myThread.start();
myThread.join();
//此时进程中只有main线程和myThread线程,在main线程中调用myThread.join()方法,那么直到myThread线程执行完毕后,main线程才会继续执行
myThread1.start();
myThread1.join();
//此时只有main线程和myThread1线程,当myThread1线程执行完毕后,main线程才会继续执行
myThread2.start();
myThread2.join();
//同理,当myThread2线程执行完毕后,main线程才会继续执行
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
class MyThread1 extends Thread {
public MyThread1() {}
public MyThread1(String name) {
super(name);
}
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.println(name + "---" + i);
}
}
}
案例2
package org.westos.demo;
/**
* @author lwj
* @date 2020/6/4 15:53
*/
public class JoinTest implements Runnable{
public static int a = 0;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
a += 1;
}
}
public static void main(String[] args) {
Thread thread = new Thread(new JoinTest());
thread.start();
//线程启动,需要准备资源,等待CPU的调度,而且和main线程是处于并发执行的状态,所以存在一种情况,main线程执行完毕了,thread线程才开始执行
System.out.println(a);
//0
}
}
package org.westos.demo;
/**
* @author lwj
* @date 2020/6/4 15:53
*/
public class JoinTest implements Runnable{
public static int a = 0;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
a += 1;
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new JoinTest());
thread.start();
thread.join();
//在main线程中调用thread.join方法,那么main线程必须等待thread线程执行完毕后才可以继续执行
System.out.println(a);
//5
}
}
yield
线程让步
yield()的作用是让步。它能让当前线程由"运行状态"进入到"就绪状态",从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到"运行状态"继续运行!
package org.westos.demo;
/**
* 线程礼让yield
* @author lwj
* @date 2020/6/4 16:07
*/
public class MyTest5 {
public static void main(String[] args) {
MyThread4 thread = new MyThread4("A线程");
MyThread4 thread1 = new MyThread4("B线程");
thread.start();
thread1.start();
}
/*
...
A线程---0
B线程---3
A线程---1
B线程---4
A线程---2
B线程---5
A线程---3
B线程---6
A线程---4
B线程---7
A线程---5
B线程---8
A线程---6
B线程---9
A线程---7
B线程---10
A线程---8
B线程---11
A线程---9
B线程---12
A线程---10
B线程---13
A线程---11
B线程---14
A线程---12
B线程---15
A线程---13
B线程---16
A线程---14
B线程---17
A线程---15
B线程---18
A线程---16
B线程---19
*/
}
class MyThread4 extends Thread {
public MyThread4() {}
public MyThread4(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
Thread.yield();
}
}
}
守护线程
Java用户线程和守护线程
- 用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束;守护线程是用来辅助用户线程的,如公司的员工和保安,各司其职,当员工都离开后,保安自然下班了。
- 必须在线程启动之前调用对象的setDaemon(true)方法。
- 还有就是经典坦克游戏,当你要守护的老鹰4掉了,坦克(守护线程)也就没有存在的必要了。
package org.westos.demo;
/**
* 守护线程
* @author lwj
* @date 2020/6/1 19:14
*/
public class MyTest4 {
public static void main(String[] args) {
//更改主线程的name
Thread.currentThread().setName("刘备");
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
MyThread2 myThread = new MyThread2("关羽");
MyThread2 myThread1 = new MyThread2("张飞");
myThread.setDaemon(true);
myThread1.setDaemon(true);
//设置为守护线程
//当主线程刘备消亡后,守护线程也要消亡
myThread.start();
myThread1.start();
System.out.println(Thread.currentThread().getName() + "线程执行完毕");
/*
刘备---0
刘备---1
刘备---2
刘备---3
刘备---4
刘备---5
刘备---6
刘备---7
刘备---8
刘备---9
刘备线程执行完毕
张飞---0
关羽---0
关羽---1
关羽---2
关羽---3
关羽---4
关羽---5
*/
}
}
class MyThread2 extends Thread {
public MyThread2() {}
public MyThread2(String name) {
super(name);
}
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.println(name + "---" + i);
}
}
}
创建守护线程
- 在线程启动之前调用Thread对象的setDaemon(true),设置该线程为守护线程
- 在守护线程中创建的新线程也是守护线程
线程停止
package org.westos.demo;
/**
* stop
* @author lwj
* @date 2020/6/4 16:29
*/
public class MyTest6 {
public static void main(String[] args) throws InterruptedException {
MyThread5 thread = new MyThread5("A线程");
thread.start();
//让主线程休眠2ms,给thread线程一些执行时间
Thread.sleep(2);
thread.stop();
//中断线程,已过时的方法
}
/*
A线程---0
...
A线程---90左右
*/
}
class MyThread5 extends Thread {
public MyThread5() {}
public MyThread5(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
interrupt()
:清除阻塞,打断线程的阻塞状态,当线程调用wait()、sleep(long mills)方法后,由运行态变为阻塞态。
package org.westos.demo2;
/**
* interrupt
* @author lwj
* @date 2020/6/4 16:48
*/
public class MyTest3 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.setName("A线程");
myThread.start();
Thread.sleep(2);
//主线程休眠保证myThread已经启动
myThread.interrupt();
//清除myThread线程的阻塞状态
//触发了sleep方法的InterruptedException异常,进入catch块打印异常的堆栈信息
/*
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at org.westos.demo2.MyThread.run(MyTest3.java:26)
A线程---0
A线程---1
A线程---2
A线程---3
A线程---4
A线程---5
A线程---6
A线程---7
A线程---8
A线程---9
*/
}
}
class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//手动让线程由运行态变为阻塞状态
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
线程安全问题
继承Thread类实现
package org.westos.demo3;
/**
* 售票案例
* @author lwj
* @date 2020/6/4 17:16
*/
public class MyTest {
public static void main(String[] args) {
CellThread a = new CellThread("A");
CellThread b = new CellThread("B");
CellThread c = new CellThread("C");
a.start();
b.start();
c.start();
/*
在增加static修饰后,售票不是按照tickets--的顺序输出的,所以是需要在run方法里加同步,每个线程执行完run方法后再轮到下一个线程
*/
}
}
class CellThread extends Thread {
//private int tickets = 100;
private static int tickets = 100;
/*
B窗口出售了第100张票
C窗口出售了第100张票
C窗口出售了第99张票
C窗口出售了第98张票
C窗口出售了第97张票
C窗口出售了第96张票
A窗口出售了第100张票
第一次是由于在main方法中创建了三个线程,每个线程对象都拥有tickets属性为100,即三个窗口各售100张票
所以需要将tickets属性作为三个线程共享的变量,添加static修饰
*/
public CellThread() {}
public CellThread(String name) {
super(name);
}
@Override
public void run() {
while (true) {
if (tickets >= 1) {
//只有在余票大于等于1张时才可以卖票
System.out.println(Thread.currentThread().getName() + "窗口出售了第" + tickets-- + "张票");
} else {
break;
}
}
}
}
实现Runnable接口实现
package org.westos.demo3;
/**
* 售票案例
* @author lwj
* @date 2020/6/4 19:57
*/
public class MyTest2 {
public static void main(String[] args) {
/*Thread a = new Thread(new CellRunnable(), "a");
Thread b = new Thread(new CellRunnable(), "b");
Thread c = new Thread(new CellRunnable(), "c");*/
/*
同样的问题,三个线程,分别执行三个不同的任务Runnable对象,那么分别都拥有tickets=100的属性
*/
CellRunnable cellRunnable = new CellRunnable();
Thread a = new Thread(cellRunnable, "a");
Thread b = new Thread(cellRunnable, "b");
Thread c = new Thread(cellRunnable, "c");
a.start();
b.start();
c.start();
/*
三个线程执行一个Runnable任务之后,那么只有一个对象,所以任何一个线程对tickets属性的修改都会影响其他线程
而当继承Thread的方式时,是因为有三个对象,所以tickets变量必须设置为静态,多个对象共享数据
*/
}
}
class CellRunnable implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets >= 1) {
System.out.println(Thread.currentThread().getName() + "窗口出售了第" + tickets-- + "张票");
} else {
break;
}
}
}
}
线程安全问题
package org.westos.demo3;
/**
* 模拟实际售票场景
* @author lwj
* @date 2020/6/4 20:07
*/
public class MyTest3 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread a = new Thread(myRunnable, "a");
Thread b = new Thread(myRunnable, "b");
Thread c = new Thread(myRunnable, "c");
a.start();
b.start();
c.start();
}
}
class MyRunnable implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets >= 1) {
try {
Thread.sleep(50);
//判断有票之后
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "窗口出售了第" + tickets-- + "张票");
} else {
break;
}
}
}
}
- c窗口出售了第0张票
- 卖出同一张票
- b窗口出售了第1张票
- a窗口出售了第1张票
以上是关于保障接口安全的5种常见方式的主要内容,如果未能解决你的问题,请参考以下文章