Java JUC&多线程 基础完整版
Java JUC&多线程 基础完整版
1、 多线程的第一种启动方式之继承Thread类
优点: 比较简单,可以直接使用Thread类中的方法,缺点: 可以拓展性比较差,不能再继承其他的类
线程类MyThread
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "Hello World");
}
}
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程第一种启动方式
* 1. 自己定义一个类继承Thread
* 2. 重写run方法
* 3. 创建启动对象,并启动线程
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
2、多线程的第二种启动方式之实现Runnable接口
优点: 拓展性强,实现该接口的同时可以继承其他的类,缺点: 相对复杂,不能直接使用Thread类中的方法
线程类MyRunnable
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程要执行的代码
for (int i = 0; i < 100; i++) {
// 先获取当前线程的对象
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "Hello World");
}
}
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第二种启动方式
* 1. 自定义一个类实现Runnable接口
* 2. 重写里面的run方法
* 3. 创建自己的类对象
* 4.创建一个Thread类的对象,并开启线程
*
*/
// 任务对象
MyRunnable myRun = new MyRunnable();
// 线程对象
Thread t1 = new Thread(myRun);
Thread t2 = new Thread(myRun);
// 给线程设置名字
t1.setName("线程1");
t2.setName("线程2");
// 开启线程
t1.start();
t2.start();
}
}
3、多线程的第三种实现方式之实现Callable接口
优点: 拓展性强,实现该接口的同时可以继承其他的类,相对复杂,不能直接使用Thread类中的方法
线程类MyCallable
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 求1~100之间的和
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum = sum + i;
}
return sum;
}
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
* 多线程的第三种实现方式
* 特点: 可以获取到多线程的运行结果
* 1. 创建一个MyCallable类实现Callable接口
* 2. 重写call (是有返回值的,表示多线程运行的结果)
* 3. 创建MyCallable的对象 (表示多线程要执行的任务)
* 4. 创建FutureTask的对象 (管理多线程运行的结果)
* 5. 创建Thread类的对象 并启动(表示线程)
*/
// 创建MyCallable对象 (表示要执行的多线程的任务)
MyCallable mc = new MyCallable();
// 创建FutureTask的对象 (作用管理多线程运行的结果)
FutureTask<Integer> ft = new FutureTask<>(mc);
// 创建线程对象
Thread t1 = new Thread(ft);
// 启动线程
t1.start();
// 获取多线程运行的结果
Integer result = ft.get();
System.out.println(result);
}
}
4、多线的常用成员方法
- String getName() 返回此线程的名称
- void setName(String name) 设置线程名称(构造方法也可以设置名称)
- 细节
- 1.如果不设置线程名称,线程也是有默认序号的,从0开始格式为Thread-X
- 2.如果给线程设置名称可以使用setName和子类的构造方法
- 细节
- 当jvm虚拟机启动后会自动启动多条线程,其中就有一个线程名字为main他的作用就是调用main方法,执行里面的代码
- 细节
- static void sleep(long time) 让线程休眠指定的时间, 单位为毫秒
- 细节
- 哪条线程执行到了这个方法,那么哪条线程就会在这里停留相对于的时间方法的参数就表示睡眠的时间,单位为毫秒时间到了后线程会自动苏醒,并继续执行
- 细节
线程类MyThread
public class MyThread extends Thread {
// 构造方式设置线程名称
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
// 1.创建线程的对象
MyThread t1 = new MyThread("飞机");
MyThread t2 = new MyThread("坦克");
// 2.开启线程
t1.start();
t2.start();
// 哪条线程执行到这个方法,此时获取的就是哪条线程的对象
Thread thread = Thread.currentThread();
System.out.println("main方法线程" + thread.getName());
}
}
5、线程的优先级
- setPriority(int newPriority) 设置线程优先级
- final int getPriority() 获取线程优先级
线程类MyRunnable
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "===" + i);
}
}
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* setPriority(int newPriority) 设置线程优先级
* final int getPriority() 获取线程优先级
*
*/
// 创建线程要执行的参数对象
MyRunnable mr = new MyRunnable();
// 创建线程对象
Thread t1 = new Thread(mr, "飞机");
Thread t2 = new Thread(mr, "坦克");
// 设置优先级
t1.setPriority(1);
t2.setPriority(10);
// 查看线程优先级
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
// 启动线程
t1.start();
t2.start();
}
}
6、守护线程
final void setDaemon(boolean on) 设置为守护线程(备胎线程),当其他的非守护线程执行结束后,守护线程会陆续结束,当非守护线程结束后,守护线程就没有存在的必要了。
线程类MyThread1
,MyThread2
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "@" + i);
}
}
}
public class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.setName("女神");
t2.setName("备胎");
// 线程2设置为守护线程
t2.setDaemon(true);
t1.start();
t2.start();
}
}
7、线程的让出
public static void yield() 出让线程/礼让线程,让出当前执行线程CPU的执行权
线程类MyThread
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
// 出让当前CPU的执行权
Thread.yield();
}
}
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("飞机");
t2.setName("坦克");
t1.start();
t2.start();
}
}
8、线程插队
public final void join() 插入线程/插队线程,讲指定的线程插入到main(当前线程)之前执行
线程类MyThread
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.setName("土豆");
t1.start();
// 将t1(土豆) 线程插入到main线程(当前线程)之前
t1.join();
// 执行在main线程中
for (int i = 0; i < 10; i++) {
System.out.println("main线程" + i);
}
}
}
9、同步代码块
在需要同步的代码块中加入synchronized(当前类字节码文件)
线程类MyThread
public class MyThread extends Thread {
/**
* 票号,所有的类都共享该票号
*/
static int ticket = 0;
@Override
public void run() {
while (true) {
// 使用同步代码块枷锁,锁对象必须是唯一的才行(使用当前类字节码)
synchronized (MyThread.class) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (ticket < 100) {
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票!");
} else {
break;
}
}
}
}
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求
* 某电影院有100张票,只有三个买票窗口,使用多线程设计一个模拟程序来卖票
* 利用同步代码块来完成
*/
// 创建线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
// 起名字
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
// 开启线程
t1.start();
t2.start();
t3.start();
}
}
10、同步方法
将需要同步的代码抽取为一个方法,并使用synchronized进行修饰
线程类MyRunnable
public class MyRunnable implements Runnable {
int ticket = 0;
@Override
public void run() {
// 1.写循环
while (true) {
// 2.同步代码块(同步方法)
if (method()) {
break;
}
}
}
// 3.同步方法(从同步代码块中抽取出来)
private synchronized boolean method() {
// 4.共享代码是否到了末尾
if (ticket == 1000) {
return true;
} else {
ticket++;
System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
}
return false;
}
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求
* 某电影院有100张票,只有三个买票窗口,使用多线程设计一个模拟程序来卖票
* 利用同步方法来完成
*/
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
11、线程锁
lock() 配合使用可以达到synchronized相同操作
线程类MyThread
public class MyThread extends Thread {
static int ticket = 0;
// 只有一把锁
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 2.同步代码块
// synchronized (MyThread.class) {
lock.lock(); // 加锁
// 3.判断
try {
if (ticket == 100) {
break;
} else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName() + "在卖第" + ticket + "张票");
}
}finally {
lock.unlock(); // 释放锁
}
// }
}
}
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求:
* 某电影院目前正在上映国产电影,共有100张票,3个窗口
* 使用JDK的lock实现
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
12、死锁问题
死锁原因常见于锁的嵌套等操作,导致相互获取不到资源产生的等待问题
线程类MyThread
public class MyThread extends Thread{
static Object objA = new Object();
static Object objB = new Object();
@Override
public void run() {
// 1.循环
while (true){
if ("线程A".equals(getName())){
synchronized (objA){
System.out.println("线程A拿到了A锁,准备拿B锁");
synchronized (objB){
System.out.println("线程A拿到了B");
}
}
}else if ("线程B".equals(getName())){
if ("线程B".equals(getName())){
synchronized (objB){
System.out.println("线程B拿到了B锁,准备拿A锁");
synchronized (objA){
System.out.println("线程B拿到了A锁,顺利执行完了一轮");
}
}
}
}
}
}
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求:死锁
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程A");
t2.setName("线程B");
t1.start();
t2.start();
}
}
13、等待唤醒机制(消费者模式)
生产者(Cook) => 中间者(Desk) <= 消费者(Foodie)
线程类Cook
,Foodie
/**
* 厨师
*/
public class Cook extends Thread {
/**
* 1.循环
* 2.同步代码块
* 3.判断共享数据是否到末尾(到了)
* 4.判断共享数据是否到末尾(没有,执行核心逻辑)
*/
@Override
public void run() {
// 1.循环
while (true) {
// 2.同步代码块
synchronized (Desk.lock) {
// 3.判断共享数据是否到末尾(到了)
if (Desk.count == 0) {
break;
} else {
// 4.判断共享数据是否到末尾(没有,执行核心逻辑)
// 判断桌子上是否有食物
if (Desk.foodFlag == 1) {
// 如果有 就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
// 如果没有 就制作
System.out.println("厨师做了一碗面条");
// 修改桌子上食物状态
Desk.foodFlag = 1;
// 叫醒等待的消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
/**
* 客人
*/
public class Foodie extends Thread {
/**
* 1.循环
* 2.同步代码块
* 3.判断共享数据是否到末尾(到了)
* 4.判断共享数据是否到末尾(没有,执行核心逻辑)
*/
@Override
public void run() {
// 1.循环
while (true) {
// 2.同步代码块
synchronized (Desk.lock) {
// 3.判断共享数据是否到了末尾(到了末尾,线程执行完毕)
if (Desk.count == 0) {
break;
} else {
// 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
// 先判断桌子上是否有面条
if (Desk.foodFlag == 0) {
// 没有就等待
try {
Desk.lock.wait(); // 让当前线程跟锁进行绑定
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
// 把吃的总数减一
Desk.count--;
// 有就开吃
System.out.println("吃货正在吃面条,还能吃" + Desk.count + "碗!");
// 吃完后唤醒厨师继续制作
Desk.lock.notifyAll();
// 修改桌子状态
Desk.foodFlag = 0;
}
}
}
}
}
}
/**
* 中间者 桌子
*/
public class Desk {
/*
* 控制生产者和消费者的执行
*/
/**
* 桌子上是否有面条 0:没有 1:有
*/
public static int foodFlag = 0;
/**
* 总个数,最多能吃10碗
*/
public static int count = 10;
// 锁对象
public static Object lock = new Object();
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求:完成生产者和消费者(等待唤醒机制)的代码
* 实现线程轮流交替的执行效果
*
* 生产者 ===> 中间者 <=== 消费者
*
*/
// 创建线程对象
Cook cook = new Cook();
Foodie foodie = new Foodie();
cook.setName("厨师");
foodie.setName("客人");
// 开启线程
cook.start();
foodie.start();
}
}
14、阻塞队列下的等待唤醒机制
生产者和消费者必须使用同一个队列ArrayBlockingQueue
生产者 => ArrayBlockingQueue <= 消费者
线程类Cook
,Foodie
/**
* 厨师
*/
public class Cook extends Thread {
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
// 不断的把面条放入阻塞队列中
try {
// put方法底层实现了锁操作,所以无需加锁
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 客人
*/
public class Foodie extends Thread {
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
// 不断的从阻塞队列中获取面条
// peek方法底层实现了锁操作,所以无需加锁
String food = null;
try {
food = queue.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("客户吃了 " + food);
}
}
}
执行类ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需要:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
* 细节:
* 生产者和消费者必须使用同一个队列
*/
// 1.创建阻塞队列(容量位1 )
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
// 2.创建线程对象,并把阻塞队列传递过去
Cook cook = new Cook(queue);
Foodie foodie = new Foodie(queue);
// 3.开启线程
cook.start();
foodie.start();
}
}
线程池
1、线程池的创建
ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
ExecutorService new FixedThreadPool(int nThread) 创建有上限的线程池
线程类MyRunnable
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
执行类MyThreadPoolDemo
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
/*
* public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
* public static ExecutorService new FixedThreadPool(int nThread) 创建有上限的线程池
*/
fixedThreadPool();
}
public static void newCachedThreadPool() throws InterruptedException {
// 1.获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();
// 2.提交任务
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
// 3.销毁线程池
pool1.shutdown();
}
public static void fixedThreadPool() throws InterruptedException {
// 创建三个线程
ExecutorService pool1 = Executors.newFixedThreadPool(3);
// 2.提交任务
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
// 3.销毁线程池
pool1.shutdown();
}
}
2、自定义线程池
(核心线程数量, 最大线程数量, 空闲线程最大存活时间, 任务队列, 创建线程工厂, 任务的拒绝策略)
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
参数一: 核心线程数量 不能小于0,
参数二: 最大线程数 不能小于等于0,最大数量 >= 核心线程数量,
参数三: 控线线程最大存活时间 不能小于0,
参数四: 线程存活时间单位 用TimeUnit指定,
参数五: 任务队列 不能为null,
参数六: 创建线程工厂 不能为null,
参数七: 任务的拒绝策略 不能为null
);
例子,MyThreadPoolDemo1
public class MyThreadPoolDemo1 {
public static void main(String[] args) {
// 自定义线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
// 核心线程数量,不能小于0
3,
// 最大线程数不能小于等于0,最大数量 >= 核心线程数量
6,
// 控线线程最大存活时间
60,
// 线程存活时间单位,用TimeUnit指定
TimeUnit.SECONDS,
// 任务队列
new ArrayBlockingQueue<>(3),
// 创建线程工厂
Executors.defaultThreadFactory(),
// 任务的拒绝策略
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 提交任务到线程池
pool.submit(() -> {
System.out.println(Thread.currentThread().getName());
});
}
}
Java JUC&多线程 基础完整版的更多相关文章
- JAVA在线观看视频教程完整版
今天给大家介绍一下JAVA在线观看视频教程完整版,我们知道Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由Sun Microsystems公司于1995年5月推出的Java程序设计语 ...
- HTML5+JavaScript动画基础 完整版 中文pdf扫描版
<HTML5+JavaScript动画基础>包括了基础知识.基础动画.高级动画.3D动画和其他技术5大部分,分别介绍了动画的基本概念.动画的JavaScript基础.动画中的三角学.渲染技 ...
- 剑指offer】Java版代码(完整版)
转自:剑指offer]Java版代码(完整版) 转自:[剑指offer] JAVA版题解(完整版)
- java核心技术-多线程基础
进程.线程 进程(Process) 是程序的运行实例.例如,一个运行的 Eclipse 就是一个进程.进程是程序向操作系统申请资源(如内存空间和文件句柄)的基本单位.线程(Thread)是进程中可 ...
- Java 并发——多线程基础
Thead类与Runnable接口 Java的线程,即一个Thread实例. Java的线程执行过程有两种实现方式: 子类继承Thread类,并且重写void run()方法. 自定义类实现Runna ...
- 【重学Java】多线程基础(三种创建方式,线程安全,生产者消费者)
实现多线程 简单了解多线程[理解] 是指从软件或者硬件上实现多个线程并发执行的技术. 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能. 并发和并行[理解] 并行:在同一时刻, ...
- java笔记--多线程基础
多线程技术 --如果朋友您想转载本文章请注明转载地址"http://www.cnblogs.com/XHJT/p/3889579.html "谢谢-- 在java中实现多线程技术 ...
- Java中间MD5加密算法完整版
携带Java软件开发过程.,因此Java中提供了自带的MessageDigest实现对文本的加密算法,以下是一个对文本进行加密的MD5加密工具类代码演示样例: package net.yuerwan. ...
- 【剑指offer】Java版代码(完整版)
原文地址:https://blog.csdn.net/baiye_xing/article/details/78428561 一.引言 <剑指offer>可谓是程序猿面试的神书了,在面试中 ...
- java debug源码完整版
第一步:现在myeclipse或者eclipse中下载jad插件,将class文件翻译成java文件 点击下载安装 第二步:创建一个java工程,导出成jar包.jdk自带的jar包不包含debug ...
随机推荐
- Js运算符(操作符)
算数运算符 a = 1 + 1 // 2 a = 10 - 5 // 5 a = 10 / 5 // 2 a = 10 / 0 // js中除以0不会报错,结果是Infinity a = 2*2 // ...
- Windows下安装Nessus 10.8.3安装破解教程
1.下载: 下载地址:https://www.tenable.com/downloads/nessus 浏览器访问 https://127.0.0.1:8834 重点:Register offline ...
- std::stod:“123.456”-> 123.456
std::stod 是 C++ 标准库中一个用于将字符串转换为 double 类型的函数.它属于 <string> 头文件中的函数,通常用于将包含数字的字符串转换为相应的浮点数值. 函数原 ...
- USB 逻辑分析仪分析丢包怎么分析(lecroy USB 逻辑分析仪)
使用 LeCroy USB 逻辑分析仪分析 USB 数据传输中的丢包现象,通常涉及以下步骤: 1. 设置触发条件 在 LeCroy USB 逻辑分析仪中,设置适当的触发条件来捕获数据包丢失的场景.常见 ...
- 一篇文章彻底讲懂malloc的实现(ptmalloc)
一.前言 C语言提供了动态内存管理功能, 在C语言中, 程序员可以使用 malloc() 和 free() 函数显式的分配和释放内存. 关于 malloc() 和free() 函数, C语言标准只是规 ...
- mysql+navicat+eclipse+jsp
mysql server 5.5安装 微信公众号搜软件智库,然后找到mysql 5.5 百度网盘下载对应自己电脑版本的mysql 百度网盘:http://pan.baidu.com/s/1jI5oB6 ...
- Kubernetes 边缘节点抓不到监控指标?试试这个方法!
KubeSphere v3.1.0 通过集成 KubeEdge,将节点和资源的管理延伸到了边缘,也是 KubeSphere 正式支持边缘计算的第一个版本. 笔者也第一时间搭建和试用了边缘节点相关的功能 ...
- Machine Learning Week_7 Support Vector Machines
目录 1 Large Margin Classification 1.1 Optimization Objective 1.1 Logistic Regresson 1.2 Cost 1.3 Supp ...
- C# 13(.Net 9) 中的新特性 - 半自动属性
C# 13 即 .Net 9 按照计划会在2024年11月发布,目前一些新特性已经定型,今天让我们来预览其中的一个新特性: 作者注:该特性虽然随着 C# 13 发布,但是仍然是处于 preview 状 ...
- 第八届御网杯线下赛Pwn方向题解
由于最近比赛有点多,而且赶上招新,导致原本应该及时总结的比赛搁置了,总结来说还是得多练,因为时间很短像这种线下赛,一般只有几个小时,所以思路一定要清晰,我还是经验太少了,导致比赛力不从心,先鸽了~ S ...