当前位置: 首页 > news >正文

Java入门级教程14——同步安全机制明锁

1.Java多线程同步安全机制

  • 基础同步机制:synchronized(内置锁)
  • 显式锁:Lock接口(ReentrantLock为代表)
  • 原子操作类:java.util.concurrent.atomic
  • 线程封闭(避免共享)
  • 不可变对象(天然线程安全)
  • volatile关键字(轻量级同步)
  • 并发容器(高级封装同步)

2.Java多线程同步安全机制——明锁

2.1 公平机制和非公平机制

2.1.1 公平机制

new ReentrantLock(true); 是true是公平锁,公平的意思就是大家都有机会执行

线程请求锁时,会进入 “等待队列”,按请求顺序排队;只有队首的线程能获取锁,不允许 “插队”。

package com.hy.chapter16;// 导入java.util.concurrent(JUC并发包)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Buy implements Runnable {Lock lock;private boolean flag = true;private int sum = 10;public Buy(Lock lock) {this.lock = lock;}@Overridepublic void run() {while (flag) {lock.lock(); // 明锁try {Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + ",买到了票,是第" + this.sum-- + "张票");if (this.sum <= 1) {this.flag = false;}lock.unlock(); // 一定要手动释放锁} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {// 明锁机制// new ReentrantLock(true); 是true是公平锁,公平的意思就是大家都有机会执行// new ReentrantLock(false); 是false是非公平锁,非公平的意思就是会有一个线程独占执行Lock lock = new ReentrantLock(true);Buy buy = new Buy(lock);new Thread(buy, "张三线程").start();new Thread(buy, "李四线程").start();new Thread(buy, "王五线程").start();}
}

输出结果:

张三线程,买到了票,是第10张票
李四线程,买到了票,是第9张票
王五线程,买到了票,是第8张票
张三线程,买到了票,是第7张票
李四线程,买到了票,是第6张票
王五线程,买到了票,是第5张票
张三线程,买到了票,是第4张票
李四线程,买到了票,是第3张票
王五线程,买到了票,是第2张票
张三线程,买到了票,是第1张票
李四线程,买到了票,是第0张票

2.1.2 不公平机制

new ReentrantLock(false); 是false是非公平锁,非公平的意思就是会有一个线程独占执行

线程请求锁时,会先尝试 “插队”(若此时锁刚好被释放,直接获取锁);插队失败才进入等待队列。

package com.hy.chapter16;// 导入java.util.concurrent(JUC并发包)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Buy implements Runnable {Lock lock;private boolean flag = true;private int sum = 10;public Buy(Lock lock) {this.lock = lock;}@Overridepublic void run() {while (flag) {lock.lock(); // 明锁try {Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + ",买到了票,是第" + this.sum-- + "张票");if (this.sum <= 1) {this.flag = false;}lock.unlock(); // 一定要手动释放锁} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {// 明锁机制// new ReentrantLock(true); 是true是公平锁,公平的意思就是大家都有机会执行// new ReentrantLock(false); 是false是非公平锁,非公平的意思就是会有一个线程独占执行Lock lock = new ReentrantLock(false);Buy buy = new Buy(lock);new Thread(buy, "张三线程").start();new Thread(buy, "李四线程").start();new Thread(buy, "王五线程").start();}
}

输出结果:

张三线程,买到了票,是第10张票
张三线程,买到了票,是第9张票
张三线程,买到了票,是第8张票
张三线程,买到了票,是第7张票
张三线程,买到了票,是第6张票
张三线程,买到了票,是第5张票
张三线程,买到了票,是第4张票
张三线程,买到了票,是第3张票
张三线程,买到了票,是第2张票
李四线程,买到了票,是第1张票
王五线程,买到了票,是第0张票

2.2 余额变化

package com.hy.chapter17;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class Bank {public Bank(String bankNumber, double money) {super();this.bankNumber = bankNumber;this.money = money;}private String bankNumber;private double money;public void operatorMoney(double opmoney, Lock lock) {System.out.println("欢迎您来到银行办理业务");lock.lock();this.money += opmoney;System.out.println(Thread.currentThread().getName() + ",操作的金额是:" + opmoney + ",剩余的金额为:" + this.money);lock.unlock();System.out.println("谢谢您的光临");}}class Operator extends Thread {Bank bank;double opmoney;Lock lock;String threadName;public Operator(Bank bank, double opmoney, Lock lock, String threadName) {super(threadName);this.bank = bank;this.opmoney = opmoney;this.lock = lock;}public void run() {this.bank.operatorMoney(opmoney, lock);}}public class Test {public static void main(String[] args) {Bank bank = new Bank("10086", 1000);Lock lock = new ReentrantLock();Operator op1 = new Operator(bank, 100, lock, "微信支付");Operator op2 = new Operator(bank, -300, lock, "支付宝支付");Operator op3 = new Operator(bank, 400, lock, "京东支付");Operator op4 = new Operator(bank, -500, lock, "华为支付");Operator op5 = new Operator(bank, 200, lock, "银行卡支付");op1.start();op2.start();op3.start();op4.start();op5.start();}}

输出结果:

欢迎您来到银行办理业务
欢迎您来到银行办理业务
欢迎您来到银行办理业务
欢迎您来到银行办理业务
欢迎您来到银行办理业务
微信支付,操作的金额是:100.0,剩余的金额为:1100.0
谢谢您的光临
银行卡支付,操作的金额是:200.0,剩余的金额为:1300.0
谢谢您的光临
华为支付,操作的金额是:-500.0,剩余的金额为:800.0
谢谢您的光临
支付宝支付,操作的金额是:-300.0,剩余的金额为:500.0
谢谢您的光临
京东支付,操作的金额是:400.0,剩余的金额为:900.0
谢谢您的光临

2.3 设置年龄

2.3.1 未加任何锁

package com.hy.chapter18;public class User {private int age;public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
package com.hy.chapter18;import java.util.Random;public class UserRunnable implements Runnable {private User u;// 多线程数据安全问题@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + ",执行run方法");u = new User();Random r = new Random();int age = r.nextInt(100);System.out.println(Thread.currentThread().getName() + ",产生的随机数的年龄为:" + age);u.setAge(age);System.out.println(Thread.currentThread().getName() + ",before设置年龄的值为:" + u.getAge());try {Thread.sleep(2000);System.out.println(Thread.currentThread().getName() + ",after设置年龄的值为:" + u.getAge());} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {UserRunnable u = new UserRunnable();Thread s1 = new Thread(u, "张三");Thread s2 = new Thread(u, "李四");s1.start();s2.start();}}

输出结果:

张三,执行run方法
李四,执行run方法
张三,产生的随机数的年龄为:16
张三,before设置年龄的值为:16
李四,产生的随机数的年龄为:33
李四,before设置年龄的值为:33
李四,after设置年龄的值为:33
张三,after设置年龄的值为:33

2.3.2 加暗锁

package com.hy.chapter18;public class User {private int age;public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
package com.hy.chapter18;import java.util.Random;public class UserRunnable implements Runnable {private User u;// 多线程数据安全问题@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + ",执行run方法");// 同步块synchronized (this) {u = new User();Random r = new Random();int age = r.nextInt(100);System.out.println(Thread.currentThread().getName() + ",产生的随机数的年龄为:" + age);u.setAge(age);System.out.println(Thread.currentThread().getName() + ",before设置年龄的值为:" + u.getAge());try {Thread.sleep(2000);System.out.println(Thread.currentThread().getName() + ",after设置年龄的值为:" + u.getAge());} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {UserRunnable u = new UserRunnable();Thread s1 = new Thread(u, "张三");Thread s2 = new Thread(u, "李四");s1.start();s2.start();}}

输出结果:

张三,执行run方法
李四,执行run方法
张三,产生的随机数的年龄为:30
张三,before设置年龄的值为:30
张三,after设置年龄的值为:30
李四,产生的随机数的年龄为:91
李四,before设置年龄的值为:91
李四,after设置年龄的值为:91

2.3.2 加明锁

ReentrantLock必须手动释放锁

package com.hy.chapter18;public class User {private int age;public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
package com.hy.chapter18;import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class UserRunnable implements Runnable {private User u;private Lock lock = new ReentrantLock();// 多线程数据安全问题@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + ",执行run方法");lock.lock();u = new User();Random r = new Random();int age = r.nextInt(100);System.out.println(Thread.currentThread().getName() + ",产生的随机数的年龄为:" + age);u.setAge(age);System.out.println(Thread.currentThread().getName() + ",before设置年龄的值为:" + u.getAge());try {Thread.sleep(2000);System.out.println(Thread.currentThread().getName() + ",after设置年龄的值为:" + u.getAge());lock.unlock();} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {UserRunnable u = new UserRunnable();Thread s1 = new Thread(u, "张三");Thread s2 = new Thread(u, "李四");s1.start();s2.start();}}

输出结果:

李四,执行run方法
张三,执行run方法
李四,产生的随机数的年龄为:43
李四,before设置年龄的值为:43
李四,after设置年龄的值为:43
张三,产生的随机数的年龄为:92
张三,before设置年龄的值为:92
张三,after设置年龄的值为:92

拓展:

线程同步安全机制:1.synchronized  2.lock,锁的特点:以性能换安全

3.ThreadLocal:线程本地变量

package com.hy.chapter19;public class User {private int age;public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
package com.hy.chapter19;import java.util.Random;public class UserRunnable implements Runnable {// 线程本地变量 key-valueThreadLocal<User> userLocal = new ThreadLocal<User>();private User getUser() {// key:对象hascode() value:对应这个对象User u = userLocal.get(); // 张三key ,李四keyif (null == u) {u = new User();System.out.println(u);userLocal.set(u);}return u;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + ",执行run方法");Random r = new Random();int age = r.nextInt(100);System.out.println(Thread.currentThread().getName() + ",产生的随机数的年龄为:" + age);User u = this.getUser();u.setAge(age);System.out.println(Thread.currentThread().getName() + ",before设置年龄的值为:" + u.getAge());try {Thread.sleep(2000);System.out.println(Thread.currentThread().getName() + ",after设置年龄的值为:" + u.getAge());} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {UserRunnable u = new UserRunnable();Thread s1 = new Thread(u, "张三");Thread s2 = new Thread(u, "李四");
//		Thread s3 = new Thread(u, "王五");s1.start();s2.start();
//		s3.start();}}

输出结果:

张三,执行run方法
李四,执行run方法
李四,产生的随机数的年龄为:24
张三,产生的随机数的年龄为:40
com.hy.chapter19.User@25ed5eff
com.hy.chapter19.User@b9098ae
张三,before设置年龄的值为:40
李四,before设置年龄的值为:24
李四,after设置年龄的值为:24
张三,after设置年龄的值为:40

4.volatile关键字

5.1 保证可见性
可见性指:当一个线程修改了 volatile 修饰的变量后,其他线程能立即看到该变量的最新值,避免因 “线程本地缓存” 导致的旧值读取问题。

5.2 禁止指令重排序
指令重排序是 JVM 为优化性能,对代码执行顺序的调整(不改变单线程语义,但可能破坏多线程语义)。volatile 通过内存屏障(特殊指令)阻止重排序,保证代码执行顺序与预期一致。

5.3 不保证原子性
volatile 仅保证可见性和禁止重排序,但不保证复合操作的原子性(如 i++、i += 1 等包含 “读取 - 修改 - 写入” 的操作)。

5.线程通信

必要条件:同一个对象

5.1 主线程和子线程通信

package com.hy.chapter23;public class UserThread extends Thread {int a;public UserThread(int a) {this.a = a;}public void run() {System.out.println(Thread.currentThread().getName() + ",执行run方法,a的值为:" + a);}}
package com.hy.chapter23;public class Test {public static void main(String[] args) {System.out.println(Thread.currentThread().getName() + ",主线程");int a = 10;UserThread u = new UserThread(a);u.start();}
}

输出结果:

main,主线程
Thread-0,执行run方法,a的值为:10

5.2 子线程和主线程通信

// 1.子线程通过创建callable接口,返回值给主线程。

// 2.通过wait()和notify()方法

// 前提是同一个对象

package com.hy.demo14;public class UserThread extends Thread {private int sum = 0;Object obj = new Object();public void run() {// 通过Object类对象可以解锁synchronized (obj) {for (int i = 0; i <= 100; i++) {sum += i;}// 通过持有同一个对象this被阻塞的线程解锁obj.notify();}}public int getSum() {return this.sum;}}
package com.hy.demo14;class User {}public class Test {public static void main(String[] args) {User u1 = new User();UserThread u = new UserThread();u.start();// 线程通信的必须的条件是:同一个对象// 如果持有的是不是线程类对象,通知解锁的一定是这个类的同一个对象synchronized (u1) {try {// 主线程持有u对象的锁,内阻塞u1.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("主线程获取的值为:" + u.getSum());}}}

没有结果

package com.hy.chapter24;public class UserThread extends Thread {private int sum = 0;public void run() {// 同步块:锁对象是当前UserThread实例(this)synchronized (this) {for (int i = 0; i <= 100; i++) {this.sum += i;}// 计算完成后,唤醒在当前对象(this)上等待的线程this.notify();// 如果是当前类UserThread的锁,可以默认不写this.notify();}}public int getSum() {return this.sum;}}
package com.hy.chapter24;public class Test {public static void main(String[] args) {UserThread ut = new UserThread(); // 创建子线程实例ut.start(); // 启动子线程// 同步块:锁对象是子线程实例ut(与子线程的锁对象一致)synchronized (ut) {try {// 主线程释放ut的锁,进入等待状态,直到被唤醒ut.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(ut.getSum());}}

输出结果:

5050

wait() 与 notify() 的作用:
ut.wait()(主线程中):释放锁 + 暂停执行,让子线程有机会获取锁并执行计算(避免主线程提前获取未完成的结果)。
this.notify()(子线程中):唤醒等待锁的线程(此处特指主线程),通知其 “计算已完成,可以获取结果”。

wait()对象的范围不能大于notify()对象的范围,否则会发生死锁现象

package com.hy.chapter27;public class UserThread extends Thread {private int sum = 0;public void run() {System.out.println("子线程去计算");synchronized (this) {for (int i = 0; i <= 100; i++) {try {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}this.sum += i;System.out.println(this.sum);}	this.notify();}}public int getSum() {return this.sum;}}
package com.hy.chapter27;public class Test {public static void main(String[] args) {Object obj = new Object();UserThread ut = new UserThread();ut.start();synchronized (obj) {System.out.println(Thread.currentThread().getName() + "before...");try {// 主线程持有obj的锁,在此被阻塞obj.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println("主线程获取的值为:" + ut.getSum());}}

不能输出:System.out.println("主线程获取的值为:" + ut.getSum());  的结果!

因为:obj.wait()对象 > this.notify()对象

特殊:若持有的是非线程对象的锁,则解锁需要用该非线程对象,且需要显示解锁 !

package com.hy.chapter28;public class UserThread extends Thread {private int sum = 0;User u;public UserThread(User u) {this.u = u;}public void run() {System.out.println("子线程去计算");synchronized (u) {for (int i = 0; i <= 100; i++) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}this.sum += i;// System.out.println(this.sum);}// 算好时,通知主线程获取结果// 若持有的是非线程对象的锁,则解锁需要用该非线程对象,且需要显示解锁 !u.notify(); // 必须要显示解锁!}}public int getSum() {return this.sum;}}
package com.hy.chapter28;class User {}public class Test {public static void main(String[] args) {User u = new User();Object obj = new Object();UserThread ut = new UserThread(u);ut.start();// 同步块 ut子线程对象// 主线程持有ut的锁synchronized (u) {System.out.println(Thread.currentThread().getName() + ",before...");try {// 主线程持有ut的锁,在此被阻塞u.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("主线程获取的值为:" + ut.getSum());}}

输出结果:

main,before...
子线程去计算
主线程获取的值为:5050

// 3.lock(Condition)

单个

package com.hy.chapter3;public class UserThreadA extends Thread {PrintChar p;public UserThreadA(PrintChar p) {this.p = p;}public void run() {this.p.showA();}}
package com.hy.chapter3;public class UserThreadB extends Thread {PrintChar p;public UserThreadB(PrintChar p) {this.p = p;}public void run() {this.p.showB();}}
package com.hy.chapter3;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;public class PrintChar {// 控制标识位private boolean flag = false;private Lock lock;private Condition con;public PrintChar(Lock lock) {this.lock = lock;this.con = lock.newCondition();}public void showA() {try {lock.lock();while (true) {if (this.flag) {try {this.con.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ",A");Thread.sleep(2000);this.flag = true;this.con.signal();}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void showB() {try {lock.lock();while (true) {if (!this.flag) {try {this.con.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ",B");Thread.sleep(2000);this.flag = false;this.con.signal();}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}}
package com.hy.chapter3;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Test {public static void main(String[] args) {Lock  lock   = new ReentrantLock();PrintChar  p =  new PrintChar(lock);UserThreadA a = new UserThreadA(p);UserThreadB b = new UserThreadB(p);a.start();b.start();}}

多个

package com.hy.chapter4;public class UserThreadA extends Thread {PrintChar p;public UserThreadA(PrintChar p) {this.p = p;}public void run() {this.p.showA();}}
package com.hy.chapter4;public class UserThreadB extends Thread {PrintChar p;public UserThreadB(PrintChar p) {this.p = p;}public void run() {this.p.showB();}}
package com.hy.chapter4;public class UserThreadC extends Thread {PrintChar p;public UserThreadC(PrintChar p) {this.p = p;}public void run() {p.showC();}}
package com.hy.chapter4;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class PrintChar {// 计数器变量private int num = 0;Lock lock = new ReentrantLock();// 创建一个监视器Condition c1 = lock.newCondition();Condition c2 = lock.newCondition();Condition c3 = lock.newCondition();// 5次Apublic void showA() {while (true) {lock.lock();if (num != 0) {try {this.c1.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + ",A");}try {Thread.sleep(2000);num = 1;this.c2.signal();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}lock.unlock();}}// 10次Bpublic void showB() {while (true) {lock.lock();if (num != 1) {try {this.c2.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + ",B");}try {Thread.sleep(2000);num = 2;this.c3.signal();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}lock.unlock();}}// 15次Cpublic void showC() {while (true) {lock.lock();if (num != 2) {try {this.c3.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int i = 0; i < 15; i++) {System.out.println(Thread.currentThread().getName() + ",C");}try {Thread.sleep(2000);num = 0;this.c1.signal();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}lock.unlock();}}}
package com.hy.chapter4;/*** 一个线程打印5个A,通知另外一个线程打印10次的B,通知第三个线程打印15次的C * * Lock机制* * @author hy**/public class Test {public static void main(String[] args) {PrintChar   p   = new PrintChar();UserThreadA  a  = new UserThreadA(p);UserThreadB  b  = new UserThreadB(p);UserThreadC  c  = new UserThreadC(p);a.start();b.start();c.start();}}

5.3 拓展:制作验证码——java spi机制

package com.hy.interfaces;public interface ICode {public String makeCode();}
package com.hy.interfaces.impl;import java.util.Random;import com.hy.interfaces.ICode;public class ChineseCodeImpl implements ICode {private String[] nums = { "赵", "钱", "孙", "李", "王", "五", "马", "六", "天", "地" };public String makeCode() {// TODO Auto-generated method stubString code = "";for (int i = 0; i < 4; i++) {String s = nums[new Random().nextInt(nums.length)];if (!code.contains(s)) {code += s;} else {i--;}}return code;}}
package com.hy.interfaces.impl;import java.util.Random;import com.hy.interfaces.ICode;public class NumberCodeImpl implements ICode {private String[] nums = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };public String makeCode() {// TODO Auto-generated method stubString code = "";for (int i = 0; i < 4; i++) {String s = String.valueOf(new Random().nextInt(nums.length));if (!code.contains(s)) {code += s;} else {i--;}}return code;}}
package com.hy.javaspi;import com.hy.interfaces.ICode;
import com.hy.service.CodeServiceFactory;public class App {public static void main(String[] args) {while (true) {String checkCode = CodeServiceFactory.createCode(ICode.class);System.out.println("获取的验证码为:" + checkCode);try {Thread.sleep(6000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
}
package com.hy.service;import java.util.Iterator;
import java.util.ServiceLoader;import com.hy.interfaces.ICode;/*** Java面向对象 Java多线程 70% Java反射和注解* * @author hy**/public class CodeServiceFactory {public static String createCode(Class targetClass) {// 服务发现,是通过一个接口的策略文件来动态加载的ServiceLoader s = ServiceLoader.load(targetClass);Iterator its = s.iterator();ICode code = null;while (its.hasNext()) {// 实现子类的动态绑定code = (ICode) its.next();}String checkCode = code.makeCode();return checkCode;}
}
com.hy.interfaces.impl.NumberCodeImpl
#com.hy.interfaces.impl.ChineseCodeImpl


文章转载自:

http://SHnPzb3B.Lzsxp.cn
http://xrh41JqX.Lzsxp.cn
http://ohFYM57c.Lzsxp.cn
http://gj6ExsMJ.Lzsxp.cn
http://4gFj8Lv1.Lzsxp.cn
http://NjiqBnt6.Lzsxp.cn
http://yTWLSUbU.Lzsxp.cn
http://iNf2RE6R.Lzsxp.cn
http://FRcjBfA2.Lzsxp.cn
http://p4ZKtIHu.Lzsxp.cn
http://l0mudqAD.Lzsxp.cn
http://OzWTYSu7.Lzsxp.cn
http://G1VcaK58.Lzsxp.cn
http://Hp5PDxmS.Lzsxp.cn
http://0D8VofTE.Lzsxp.cn
http://KzyZ5goS.Lzsxp.cn
http://XmRTEMsl.Lzsxp.cn
http://EckoPiZT.Lzsxp.cn
http://2jorVQD8.Lzsxp.cn
http://E2qHi0jz.Lzsxp.cn
http://Vjxy5QHy.Lzsxp.cn
http://ubhQ0WMC.Lzsxp.cn
http://vOtmKFsy.Lzsxp.cn
http://0donjK88.Lzsxp.cn
http://thQPkeQp.Lzsxp.cn
http://dmh2APbk.Lzsxp.cn
http://Z8MuQFRV.Lzsxp.cn
http://AWnJMkue.Lzsxp.cn
http://pdiqD9LY.Lzsxp.cn
http://ZJY483zy.Lzsxp.cn
http://www.dtcms.com/a/375329.html

相关文章:

  • [JavaWeb]模拟一个简易的Tomcat服务(Servlet注解)
  • MongoDB vs MySQLNoSQL与SQL数据库的架构差异与选型指南
  • Vue框架技术详解——项目驱动概念理解【前端】【Vue】
  • mardown-it 有序列表ios序号溢出解决办法
  • 目前主流热门的agent框架
  • 如何验证邮箱是否有效?常见方法与工具推荐
  • Python 类型注释核心知识点:变量、函数 / 方法与 Union 类型分步解析
  • 端口转发实操
  • 【算法--链表】116.填充每个节点的下一个右侧节点指针--通俗讲解
  • html+js实现表格本地筛选
  • 领码方案|Linux 下 PLT → PDF 转换服务超级完整版:异步、权限、进度
  • pyside6 的pdf显示测试 -- 01
  • 算法篇——动态规划【力扣Hot100】
  • 《WINDOWS 环境下32位汇编语言程序设计》第14章 异常处理
  • 中间件八股
  • thrust cub cccl 安装与应用示例
  • Expect-自动化交互工具
  • RL【6】:Stochastic Approximation and Stochastic Gradient Descent
  • 计算机毕设Python项目:基于爬虫技术的网络小说数据分析系统
  • 基于springboot 校园餐厅预约点餐微信小程序的设计与实现(代码+数据库+LW)
  • Day20 K8S学习
  • Mockito 原理与实战
  • Django项目架构
  • SpringBoot整合通用ClamAV文件扫描病毒
  • 提权分析报告 —— 基于DriftingBlues: 4靶场
  • 设计模式-原则概述
  • LAMPSecurity: CTF8靶场渗透
  • python网络爬取个人学习指南-(五)
  • CSS 基础概念
  • 在企业内部分发 iOS App 时如何生成并使用 manifest.plist