保障接口安全的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种常见方式的主要内容,如果未能解决你的问题,请参考以下文章

如何保障OA办公系统安全性

四种策略切实保障用户身份安全-行云管家堡垒机!

《CISP》信息安全保障

SQLServer数据库权限设置--保障数据库安全

灵活多样认证授权,零开发投入保障 IoT 安全

你的电子邮箱安全吗?个人邮箱安全3大技巧请收藏