使用多线程实现多客户端连接服务端

流程图



服务端代码改造:

package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket; /**
* 聊天室服务端
*/
public class Server {
/**
* 运行在服务端的ServerSocket主要完成两个工作:
* 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
* 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
* 就可以和该客户端交互了
*
* 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
* 电话使得服务端与你沟通。
*/
private ServerSocket serverSocket; /**
* 服务端构造方法,用来初始化
*/
public Server(){
try {
System.out.println("正在启动服务端...");
/*
实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
应用程序占用的端口相同,否则会抛出异常:
java.net.BindException:address already in use 端口是一个数字,取值范围:0-65535之间。
6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
*/
serverSocket = new ServerSocket(8088);
System.out.println("服务端启动完毕!");
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 服务端开始工作的方法
*/
public void start(){
try {
while(true) {
System.out.println("等待客户端链接...");
/*
ServerSocket提供了接受客户端链接的方法:
Socket accept()
这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
通过这个Socket就可以与客户端进行交互了。 可以理解为此操作是接电话,电话没响时就一直等。
*/
Socket socket = serverSocket.accept();
System.out.println("一个客户端链接了!");
//启动一个线程与该客户端交互
ClientHandler clientHandler = new ClientHandler(socket);
Thread t = new Thread(clientHandler);
t.start(); }
} catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
Server server = new Server();
server.start();
} /**
* 定义线程任务
* 目的是让一个线程完成与特定客户端的交互工作
*/
private class ClientHandler implements Runnable{
private Socket socket;
public ClientHandler(Socket socket){
this.socket = socket;
}
public void run(){
try{
/*
Socket提供的方法:
InputStream getInputStream()
获取的字节输入流读取的是对方计算机发送过来的字节
*/
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in, "UTF-8");
BufferedReader br = new BufferedReader(isr); String message = null;
while ((message = br.readLine()) != null) {
System.out.println("客户端说:" + message);
}
}catch(IOException e){
e.printStackTrace();
}
}
} }

线程API

获取线程相关信息的方法

package thread;

/**
* 获取线程相关信息的一组方法
*/
public class ThreadInfoDemo {
public static void main(String[] args) {
Thread main = Thread.currentThread();//获取主线程 String name = main.getName();//获取线程的名字
System.out.println("名字:"+name); long id = main.getId();//获取该线程的唯一标识
System.out.println("id:"+id); int priority = main.getPriority();//获取该线程的优先级
System.out.println("优先级:"+priority); boolean isAlive = main.isAlive();//该线程是否活着
System.out.println("是否活着:"+isAlive); boolean isDaemon = main.isDaemon();//是否为守护线程
System.out.println("是否为守护线程:"+isDaemon); boolean isInterrupted = main.isInterrupted();//是否被中断了
System.out.println("是否被中断了:"+isInterrupted); }
}

线程优先级

线程start后会纳入到线程调度器中统一管理,线程只能被动的被分配时间片并发运行,而无法主动索取时间片.线程调度器尽可能均匀的将时间片分配给每个线程.

线程有10个优先级,使用整数1-10表示

  • 1为最小优先级,10为最高优先级.5为默认值
  • 调整线程的优先级可以最大程度的干涉获取时间片的几率.优先级越高的线程获取时间片的次数越多,反之则越少.
package thread;

public class PriorityDemo {
public static void main(String[] args) {
Thread max = new Thread(){
public void run(){
for(int i=0;i<10000;i++){
System.out.println("max");
}
}
};
Thread min = new Thread(){
public void run(){
for(int i=0;i<10000;i++){
System.out.println("min");
}
}
};
Thread norm = new Thread(){
public void run(){
for(int i=0;i<10000;i++){
System.out.println("nor");
}
}
};
min.setPriority(Thread.MIN_PRIORITY);
max.setPriority(Thread.MAX_PRIORITY);
min.start();
norm.start();
max.start();
}
}

sleep阻塞

线程提供了一个静态方法:

  • static void sleep(long ms)
  • 使运行该方法的线程进入阻塞状态指定的毫秒,超时后线程会自动回到RUNNABLE状态等待再次获取时间片并发运行.
package thread;

public class SleepDemo {
public static void main(String[] args) {
System.out.println("程序开始了!");
try {
Thread.sleep(5000);//主线程阻塞5秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("程序结束了!");
}
}

线程API

sleep阻塞(续)

sleep方法处理异常:InterruptedException.

当一个线程调用sleep方法处于睡眠阻塞的过程中,该线程的interrupt()方法被调用时,sleep方法会抛出该异常从而打断睡眠阻塞.

package thread;

/**
* sleep方法要求必须处理中断异常:InterruptedException
* 当一个线程调用sleep方法处于睡眠阻塞的过程中,它的interrupt()方法被调用时
* 会中断该阻塞,此时sleep方法会抛出该异常。
*/
public class SleepDemo2 {
public static void main(String[] args) {
Thread lin = new Thread(){
public void run(){
System.out.println("林:刚美完容,睡一会吧~");
try {
Thread.sleep(9999999);
} catch (InterruptedException e) {
System.out.println("林:干嘛呢!干嘛呢!干嘛呢!都破了像了!");
}
System.out.println("林:醒了");
}
}; Thread huang = new Thread(){
public void run(){
System.out.println("黄:大锤80!小锤40!开始砸墙!");
for(int i=0;i<5;i++){
System.out.println("黄:80!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println("咣当!");
System.out.println("黄:大哥,搞定!");
lin.interrupt();//中断lin的睡眠阻塞
}
};
lin.start();
huang.start();
}
}

守护线程

守护线程也称为:后台线程

  • 守护线程是通过普通线程调用setDaemon(boolean on)方法设置而来的,因此创建上与普通线程无异.
  • 守护线程的结束时机上有一点与普通线程不同,即:进程的结束.
  • 进程结束:当一个进程中的所有普通线程都结束时,进程就会结束,此时会杀掉所有正在运行的守护线程.
package thread;

/**
* 守护线程
* 守护线程是通过普通线程调用setDaemon(true)设置而转变的。因此守护线程创建上
* 与普通线程无异。
* 但是结束时机上有一点不同:进程结束。
* 当一个java进程中的所有普通线程都结束时,该进程就会结束,此时会强制杀死所有正在
* 运行的守护线程。
*/
public class DaemonThreadDemo {
public static void main(String[] args) {
Thread rose = new Thread(){
public void run(){
for(int i=0;i<5;i++){
System.out.println("rose:let me go!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println("rose:啊啊啊啊啊啊AAAAAAAaaaaa....");
System.out.println("噗通");
}
}; Thread jack = new Thread(){
public void run(){
while(true){
System.out.println("jack:you jump!i jump!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
};
rose.start();
jack.setDaemon(true);//设置守护线程必须在线程启动前进行
jack.start(); }
}

通常当我们不关心某个线程的任务什么时候停下来,它可以一直运行,但是程序主要的工作都结束时它应当跟着结束时,这样的任务就适合放在守护线程上执行.比如GC就是在守护线程上运行的.

多线程并发安全问题

当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能导致系统瘫痪.
临界资源:操作该资源的全过程同时只能被单个线程完成.
package thread;

/**
* 多线程并发安全问题
* 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现
* 混乱,严重时可能导致系统瘫痪。
* 临界资源:同时只能被单一线程访问操作过程的资源。
*/
public class SyncDemo {
public static void main(String[] args) {
Table table = new Table();
Thread t1 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
Thread t2 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
/*
static void yield()
线程提供的这个静态方法作用是让执行该方法的线程
主动放弃本次时间片。
这里使用它的目的是模拟执行到这里CPU没有时间了,发生
线程切换,来看并发安全问题的产生。
*/
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
} class Table{
private int beans = 20;//桌子上有20个豆子 public int getBean(){
if(beans==0){
throw new RuntimeException("没有豆子了!");
}
Thread.yield();
return beans--;
}
}

synchronized关键字

synchronized有两种使用方式

  • 在方法上修饰,此时该方法变为一个同步方法
  • 同步块,可以更准确的锁定需要排队的代码片段

同步方法

当一个方法使用synchronized修饰后,这个方法称为"同步方法",即:多个线程不能同时 在方法内部执行.只能有先后顺序的一个一个进行. 将并发操作同一临界资源的过程改为同步执行就可以有效的解决并发安全问题.

package thread;

/**
* 多线程并发安全问题
* 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现
* 混乱,严重时可能导致系统瘫痪。
* 临界资源:同时只能被单一线程访问操作过程的资源。
*/
public class SyncDemo {
public static void main(String[] args) {
Table table = new Table();
Thread t1 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
Thread t2 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
/*
static void yield()
线程提供的这个静态方法作用是让执行该方法的线程
主动放弃本次时间片。
这里使用它的目的是模拟执行到这里CPU没有时间了,发生
线程切换,来看并发安全问题的产生。
*/
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
} class Table{
private int beans = 20;//桌子上有20个豆子 /**
* 当一个方法使用synchronized修饰后,这个方法称为同步方法,多个线程不能
* 同时执行该方法。
* 将多个线程并发操作临界资源的过程改为同步操作就可以有效的解决多线程并发
* 安全问题。
* 相当于让多个线程从原来的抢着操作改为排队操作。
*/
public synchronized int getBean(){
if(beans==0){
throw new RuntimeException("没有豆子了!");
}
Thread.yield();
return beans--;
}
}

同步块

有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率.同步块可以更准确的控制需要多个线程排队执行的代码片段.

语法:

synchronized(同步监视器对象){
需要多线程同步执行的代码片段
}

同步监视器对象即上锁的对象,要想保证同步块中的代码被多个线程同步运行,则要求多个线程看到的同步监视器对象是同一个.

package thread;

/**
* 有效的缩小同步范围可以在保证并发安全的前提下尽可能提高并发效率。
*
* 同步块
* 语法:
* synchronized(同步监视器对象){
* 需要多个线程同步执行的代码片段
* }
* 同步块可以更准确的锁定需要多个线程同步执行的代码片段来有效缩小排队范围。
*/
public class SyncDemo2 {
public static void main(String[] args) {
Shop shop = new Shop();
Thread t1 = new Thread(){
public void run(){
shop.buy();
}
};
Thread t2 = new Thread(){
public void run(){
shop.buy();
}
};
t1.start();
t2.start();
}
} class Shop{
public void buy(){
/*
在方法上使用synchronized,那么同步监视器对象就是this。
*/
// public synchronized void buy(){
Thread t = Thread.currentThread();//获取运行该方法的线程
try {
System.out.println(t.getName()+":正在挑衣服...");
Thread.sleep(5000);
/*
使用同步块需要指定同步监视器对象,即:上锁的对象
这个对象可以是java中任何引用类型的实例,只要保证多个需要排队
执行该同步块中代码的线程看到的该对象是"同一个"即可
*/
synchronized (this) {
// synchronized (new Object()) {//没有效果!
System.out.println(t.getName() + ":正在试衣服...");
Thread.sleep(5000);
} System.out.println(t.getName()+":结账离开");
} catch (InterruptedException e) {
e.printStackTrace();
} }
}

在静态方法上使用synchronized

当在静态方法上使用synchronized后,该方法是一个同步方法.由于静态方法所属类,所以一定具有同步效果.

静态方法使用的同步监视器对象为当前类的类对象(Class的实例).

注:类对象会在后期反射知识点介绍.

package thread;

/**
* 静态方法上如果使用synchronized,则该方法一定具有同步效果。
*/
public class SyncDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
Boo.dosome();
}
};
Thread t2 = new Thread(){
public void run(){
Boo.dosome();
}
};
t1.start();
t2.start();
}
}
class Boo{
/**
* synchronized在静态方法上使用是,指定的同步监视器对象为当前类的类对象。
* 即:Class实例。
* 在JVM中,每个被加载的类都有且只有一个Class的实例与之对应,后面讲反射
* 知识点的时候会介绍类对象。
*/
public synchronized static void dosome(){
Thread t = Thread.currentThread();
try {
System.out.println(t.getName() + ":正在执行dosome方法...");
Thread.sleep(5000);
System.out.println(t.getName() + ":执行dosome方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

静态方法中使用同步块时,指定的锁对象通常也是当前类的类对象

class Boo{
public static void dosome(){
/*
静态方法中使用同步块时,指定同步监视器对象通常还是用当前类的类对象
获取方式为:类名.class
*/
synchronized (Boo.class) {
Thread t = Thread.currentThread();
try {
System.out.println(t.getName() + ":正在执行dosome方法...");
Thread.sleep(5000);
System.out.println(t.getName() + ":执行dosome方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

互斥锁

当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的.

使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的.

package thread;

/**
* 互斥锁
* 当使用synchronized锁定多个不同的代码片段,并且指定的同步监视器对象相同时,
* 这些代码片段之间就是互斥的,即:多个线程不能同时访问这些方法。
*/
public class SyncDemo4 {
public static void main(String[] args) {
Foo foo = new Foo();
Thread t1 = new Thread(){
public void run(){
foo.methodA();
}
};
Thread t2 = new Thread(){
public void run(){
foo.methodB();
}
};
t1.start();
t2.start();
}
}
class Foo{
public synchronized void methodA(){
Thread t = Thread.currentThread();
try {
System.out.println(t.getName()+":正在执行A方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":执行A方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void methodB(){
Thread t = Thread.currentThread();
try {
System.out.println(t.getName()+":正在执行B方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":执行B方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

死锁

死锁的产生:

两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。这个现象就是死锁。

package thread;

/**
* 死锁
* 死锁的产生:
* 两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。
* 这个现象就是死锁。
*/
public class DeadLockDemo {
//定义两个锁对象,"筷子"和"勺"
public static Object chopsticks = new Object();
public static Object spoon = new Object(); public static void main(String[] args) {
Thread np = new Thread(){
public void run(){
System.out.println("北方人开始吃饭.");
System.out.println("北方人去拿筷子...");
synchronized (chopsticks){
System.out.println("北方人拿起了筷子开始吃饭...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("北方人吃完了饭,去拿勺...");
synchronized (spoon){
System.out.println("北方人拿起了勺子开始喝汤...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("北方人喝完了汤");
}
System.out.println("北方人放下了勺");
}
System.out.println("北方人放下了筷子,吃饭完毕!");
}
}; Thread sp = new Thread(){
public void run(){
System.out.println("南方人开始吃饭.");
System.out.println("南方人去拿勺...");
synchronized (spoon){
System.out.println("南方人拿起了勺开始喝汤...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("南方人喝完了汤,去拿筷子...");
synchronized (chopsticks){
System.out.println("南方人拿起了筷子开始吃饭...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("南方人吃完了饭");
}
System.out.println("南方人放下了筷子");
}
System.out.println("南方人放下了勺,吃饭完毕!");
}
}; np.start();
sp.start(); }
}
package thread;

/**
* 解决死锁:
* 1:尽量避免在持有一个锁的同时去等待持有另一个锁(避免synchronized嵌套)
* 2:当无法避免synchronized嵌套时,就必须保证多个线程锁对象的持有顺序必须一致。
* 即:A线程在持有锁1的过程中去持有锁2时,B线程也要以这样的持有顺序进行。
*/
public class DeadLockDemo2 {
//筷子
private static Object chopsticks = new Object();
//勺
private static Object spoon = new Object(); public static void main(String[] args) {
//北方人
Thread np = new Thread(){
public void run(){
try {
System.out.println("北方人:开始吃饭");
System.out.println("北方人去拿筷子...");
synchronized (chopsticks) {
System.out.println("北方人拿起了筷子,开始吃饭...");
Thread.sleep(5000);
}
System.out.println("北方人吃完了饭,放下了筷子");
System.out.println("北方人去拿勺子...");
synchronized (spoon){
System.out.println("北方人拿起了勺子,开始喝汤...");
Thread.sleep(5000);
}
System.out.println("北方人喝完了汤,北方人放下了勺子");
System.out.println("吃饭完毕。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//南方人
Thread sp = new Thread(){
public void run(){
try {
System.out.println("南方人:开始吃饭");
System.out.println("南方人去拿勺...");
synchronized (spoon) {
System.out.println("南方人拿起了勺,开始喝汤...");
Thread.sleep(5000);
}
System.out.println("南方人喝完了汤,放下勺子...");
System.out.println("南方人去拿筷子...");
synchronized (chopsticks){
System.out.println("南方人拿起了筷子,开始吃饭...");
Thread.sleep(5000);
}
System.out.println("南方人吃完了饭,南方人放下了筷子");
System.out.println("吃饭完毕。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}; np.start();
sp.start();
}
}

day06 Socket_线程API_线程并发安全的更多相关文章

  1. Python 第八篇:异常处理、Socket语法、SocketServer实现多并发、进程和线程、线程锁、GIL、Event、信号量、进程间通讯

    本节内容: 异常处理.Socket语法.SocketServer实现多并发.进程和线程.线程锁.GIL.Event.信号量.进程间通讯.生产者消费者模型.队列Queue.multiprocess实例 ...

  2. 设计模式:基于线程池的并发Visitor模式

    1.前言 第二篇设计模式的文章我们谈谈Visitor模式. 当然,不是简单的列个的demo,我们以电商网站中的购物车功能为背景,使用线程池实现并发的Visitor模式,并聊聊其中的几个关键点. 一,基 ...

  3. python并发编程之线程(一):线程&守护线程&全局解释器锁

      一 threading模块介绍 multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍 官网链接:https://docs.pyth ...

  4. java线程安全之并发Queue

    关闭 原 java线程安全之并发Queue(十三) 2017年11月19日 23:40:23 小彬彬~ 阅读数:12092更多 所属专栏: 线程安全    版权声明:本文为博主原创文章,未经博主允许不 ...

  5. python 之 线程池实现并发

    使用线程池实现高IO并发 模块:ThreadPoolExecutor, as_completed 测试代码如下: #!/opt/python3/bin/python3 from concurrent. ...

  6. 性能测试:深入理解线程数,并发量,TPS,看这一篇就够了

    并发数,线程数,吞吐量,每秒事务数(TPS)都是性能测试领域非常关键的数据和指标. 那么他们之间究竟是怎样的一个对应关系和内在联系? 测试时,我们经常容易将线程数等同于表述为并发数,这一表述正确吗? ...

  7. 子进程回收资源两种方式,僵尸进程与孤儿进程,守护进程,进程间数据隔离,进程互斥锁,队列,IPC机制,线程,守护线程,线程池,回调函数add_done_callback,TCP服务端实现并发

    子进程回收资源两种方式 - 1) join让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源. - 2) 主进程 “正常结束” ,子进程与主进程一并被回收资源. from multipr ...

  8. python之线程和进程(并发编程)

    python的GIL In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native ...

  9. Python并发编程-线程同步(线程安全)

    Python并发编程-线程同步(线程安全) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 线程同步,线程间协调,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直 ...

随机推荐

  1. [还不会搭建博客吗?]centos7系统部署hexo博客新手入门-进阶,看这一篇就够了

    @ 目录 *本文说明 请大家务必查看 前言 首先介绍一下主角:Hexo 什么是 Hexo? 环境准备 详细版 入门:搭建步骤 安装git: 安装node: 安装Hexo: 进阶:hexo基本操作 发布 ...

  2. 快速创建简单的mybatis应用

    1.导包(配置pom.xml) 一定要用这个网站:https://mvnrepository.com/ 点击查看代码 <dependency> <groupId>org.myb ...

  3. 1.6 为什么要学Linux,它比Windows好在哪里?

    早在 20 世纪 70 年代,UNIX 系统是开源而且免费的,但是在 1979 年时,AT&T 公司宣布了对 UNIX 系统的商业化计划,随之开源软件业转变成了版权式软件产业,源代码被当作商业 ...

  4. 重新审视C# Span<T>数据结构

    先谈一下我对Span的看法, span是指向任意连续内存空间的类型安全.内存安全的视图. Span和Memory都是包装了可以在pipeline上使用的结构化数据的内存缓冲器,他们被设计用于在pipe ...

  5. 【Python数据分析案例】python数据分析老番茄B站数据(pandas常用基础数据分析代码)

    一.爬取老番茄B站数据 前几天开发了一个python爬虫脚本,成功爬取了B站李子柒的视频数据,共142个视频,17个字段,含: 视频标题,视频地址,视频上传时间,视频时长,是否合作视频,视频分区,弹幕 ...

  6. python PDF转图片,World转PDF

    软件不用续费了... PDF转World暂时没需求,有需求了再搞 Python3.9 ---------------pip3 install  PyMuPdf ---------------pip3 ...

  7. JavaScript中if语句优化和部分语法糖小技巧推荐

    前言 在前端日常开发过程中,if else判断语句使用的次数应该是比较频繁的了,一些较为复杂的场景,可能会用到很多判断,在某个代码块使用很多if else时,代码会显得较为冗余,阅读起来不够清晰. 除 ...

  8. 119_Power Pivot 长尾明细显示为【其他】

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.背景 最近比较忙,太久不没有更新文章,确实没有好的素材,就写一个吧. 在关于产品数据分析的时候,我们经常关注的是主要的 ...

  9. c++:-9

    上节(c++:-8)主要学习了C++的流类库和输入输出,本节学习C++的异常处理. 异常处理 介绍 (1)异常处理的基本思想: (2)异常处理的语法: (3)举例:处理除0异常 #include &l ...

  10. [漏洞复现] [Vulhub靶机] Struts2-045 Remote Code Execution Vulnerablity(CVE-2017-5638)

    免责声明:本文仅供学习研究,严禁从事非法活动,任何后果由使用者本人负责. 0x00 背景知识 Apache Struts 2是美国Apache软件基金会的一个开源项目,是一套用于创建企业级Java W ...