Java动态代理实现与原理详细分析

Java动态代理实现与原理详细分析

关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式–代理模式,而对于代理,根据创建代理类的

时间点,又可以分为静态代理和动态代理。

1、代理模式

代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消

息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理

类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方

法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问

实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。在后面我会解释这种间接性带来的好

处。代理模式结构图(图片来自《大话设计模式》):

在这里插入图片描述

2、静态代理

2.1 静态代理

静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下

来。在程序运行之前,代理类的.class文件就已经生成。

2.2 静态代理简单实现

根据上面代理模式的类图,来写一个简单的静态代理的例子。我这儿举一个比较粗糙的例子,假如一个班的同学要

向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,班长就是学生的代

理。

首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上

交班费的行为。这样,学生上交班费就可以让班长来代理执行。

package com.proxy.staticproxy;/*** 创建Person接口*/
public interface Person {//上交班费void giveMoney();
}

Student类实现Person接口,Student可以具体实施上交班费的动作。

package com.proxy.staticproxy;public class Student implements Person {private String name;public Student(String name) {this.name = name;}@Overridepublic void giveMoney() {System.out.println(name + "上交班费50元");}
}

StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,由于实现了Person接口,同

时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。

package com.proxy.staticproxy;/*** 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为*/
public class StudentsProxy implements Person {//被代理的学生Student stu;public StudentsProxy(Person stu) {// 只代理学生对象if (stu.getClass() == Student.class) {this.stu = (Student) stu;}}//代理上交班费,调用被代理学生的上交班费行为public void giveMoney() {stu.giveMoney();}
}

下面测试一下,看如何使用代理模式:

package com.proxy.staticproxy;public class StaticProxyTest {public static void main(String[] args) {//被代理的学生张三,他的班费上交有代理对象monitor(班长)完成Person zhangsan = new Student("张三");//生成代理对象,并将张三传给代理对象Person monitor = new StudentsProxy(zhangsan);//班长代理上交班费monitor.giveMoney();}
}

运行结果:

# 程序输出
张三上交班费50元

这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就

是代理模式。

代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),

代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的

间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理

过程中就可以加上一些其他用途。就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习

有很大进步,通过代理模式很轻松就能办到:

package com.proxy.staticproxy;/*** @author zhangshixing* @date 2021年11月02日 19:13*//*** 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为** @author Gonjan*/
public class StudentsProxy implements Person {//被代理的学生Student stu;public StudentsProxy(Person stu) {// 只代理学生对象if (stu.getClass() == Student.class) {this.stu = (Student) stu;}}//代理上交班费,调用被代理学生的上交班费行为public void giveMoney() {System.out.println("张三最近学习有进步!");stu.giveMoney();}
}

运行结果:

张三最近学习有进步!
张三上交班费50元

可以看到,只需要在代理类中帮张三上交班费之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个

很大的优点。最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切

点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其

他操作。

3、动态代理

3.1 动态代理

代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(studentProxy)是自

己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时

根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数

进行统一的处理,而不用修改每个代理类中的方法。 比如说,想要在每个代理的方法前都加上一个处理方法:

public void giveMoney() {//调用被代理方法前加入处理方法beforeMethod();stu.giveMoney();
}

这里只有一个giveMoney方法,就写一次beforeMethod方法,但是如果出了giveMonney还有很多其他的方法,

那就需要写很多次beforeMethod方法,麻烦。那看看下面动态代理如何实现。

3.2 动态代理简单实现

在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生

成JDK动态代理类和动态代理对象。

创建一个动态代理对象步骤,具体代码见后面:

  • 创建一个InvocationHandler对象
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
  • 使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass
Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
  • 获得stuProxyClass 中一个带InvocationHandler参数的构造器constructor
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
  • 通过构造器constructor来创建一个动态实例stuProxy
Person stuProxy = (Person) cons.newInstance(stuHandler);

就此,一个动态代理对象就创建完毕,当然,上面四个步骤可以通过Proxy类的newProxyInstances方法来简化:

//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
//创建一个代理对象stuProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

到这里肯定都会很疑惑,这动态代理到底是如何执行的,是如何通过代理对象来执行被代理对象的方法的,先不

急,我们先看看一个简单的完整的动态代理的例子。还是上面静态代理的例子,班长需要帮学生代交班费。

首先是定义一个Person接口:

package com.proxy.dynamicproxy;/*** 创建Person接口*/
public interface Person {//上交班费void giveMoney();
}

创建需要被代理的实际类:

package com.proxy.dynamicproxy;public class Student implements Person {private String name;public Student(String name) {this.name = name;}@Overridepublic void giveMoney() {try {//假设数钱花了一秒时间Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(name + "上交班费50元");}
}

再定义一个检测方法执行时间的工具类,在任何方法执行前先调用start方法,执行后调用finish方法,就可以计算

出该方法的运行时间,这也是一个最简单的方法执行时间检测工具。

package com.proxy.dynamicproxy;public class MonitorUtil {private static ThreadLocal<Long> tl = new ThreadLocal<>();public static void start() {tl.set(System.currentTimeMillis());}//结束时打印耗时public static void finish(String methodName) {long finishTime = System.currentTimeMillis();System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");}
}

创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。

InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。

再再invoke方法中执行被代理对象target的相应方法。当然,在代理过程中,我们在真正执行被代理对象的方法前

加入自己其他处理。这也是Spring中的AOP实现的主要原理,这里还涉及到一个很重要的关于java反射方面的基础

知识。

package com.proxy.dynamicproxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class StuInvocationHandler<T> implements InvocationHandler {//invocationHandler持有的被代理对象T target;public StuInvocationHandler(T target) {this.target = target;}/*** proxy:代表动态代理对象* method:代表正在执行的方法* args:代表调用目标方法时传入的实参*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理执行" + method.getName() + "方法");//代理过程中插入监测方法,计算该方法耗时MonitorUtil.start();Object result = method.invoke(target, args);MonitorUtil.finish(method.getName());return result;}
}

做完上面的工作后,我们就可以具体来创建动态代理对象了,上面简单介绍了如何创建动态代理对象,我们使用简

化的方式创建动态代理对象:

package com.proxy.dynamicproxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;public class ProxyTest {public static void main(String[] args) {//创建一个实例对象,这个对象是被代理的对象Person zhangsan = new Student("张三");//创建一个与代理对象相关联的InvocationHandlerInvocationHandler stuHandler = new StuInvocationHandler<>(zhangsan);//创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);//代理执行上交班费的方法stuProxy.giveMoney();}
}

我们执行这个ProxyTest类,先想一下,我们创建了一个需要被代理的学生张三,将zhangsan对象传给了

stuHandler中,我们在创建代理对象stuProxy时,将stuHandler作为参数了的,上面也有说到所有执行代理对象

的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。所以在

看到下面的运行结果也就理所当然了。

运行结果:

# 程序输出
代理执行giveMoney方法
张三上交班费50元
giveMoney方法耗时1014ms

上面说到,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。

是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方

法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的

方法都会被计时,然而我只做了很少的代码量。

动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,

我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是

通过InvocationHandler来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过

InvocationHandler中的invoke方法来执行。带着这些问题,我们就需要对java动态代理的源码进行简要的分析,

弄清楚其中缘由。

4、动态代理原理分析

4.1 Java动态代理创建出来的动态代理类

上面我们利用Proxy类的newProxyInstance方法创建了一个动态代理对象,查看该方法的源码,发现它只是封装

了创建动态代理类的步骤:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
{Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.*/Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}
}

其实,我们最应该关注的是 Class<?> cl = getProxyClass0(loader, intfs);这句,这里产生了代理类,后面代码中的

构造器也是通过这里产生的类来获得,可以看出,这个类的产生就是整个动态代理的关键,由于是动态生成的类文

件,我这里不具体进入分析如何产生的这个类文件,只需要知道这个类文件时缓存在java虚拟机中的,我们可以通

过下面的方法将其打印到文件里面,一睹真容:

package com.proxy.dynamicproxy;import sun.misc.ProxyGenerator;import java.io.FileOutputStream;public class ProxyGeneratorTest {public static void main(String[] args) {byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());String path = "./StuProxy.class";try(FileOutputStream fos = new FileOutputStream(path)) {fos.write(classFile);fos.flush();System.out.println("代理类class文件写入成功");} catch (Exception e) {System.out.println("写文件错误");}}
}

对这个class文件进行反编译,我们看看jdk为我们生成了什么样的内容:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//import com.proxy.dynamicproxy.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements Person {private static Method m1;private static Method m2;private static Method m3;private static Method m0;public $Proxy0(InvocationHandler var1) throws  {super(var1);}public final boolean equals(Object var1) throws  {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final void giveMoney() throws  {try {super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws  {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString");m3 = Class.forName("com.proxy.dynamicproxy.Person").getMethod("giveMoney");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件

时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这

个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。

我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象

的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。

代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理

对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。

5、总结

生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了Proxy类,所以也就决定了

java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。

上面的动态代理的例子,其实就是AOP的一个简单实现了,在目标对象的方法执行之前和执行之后进行了处理,对

方法耗时统计。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/202541.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从cot到agent的survey视频笔记

参考视频&#xff1a; 从CoT到Agent的列车即将发车&#xff0c;请各位旅客尽快上车 姚杳 由于总结不易&#xff0c;所以暂时都是粉丝可见&#xff0c;如果总结的不好见谅。 核心理解点总结&#xff1a; paradigm shifts of cot when cot&#xff1f;推理多的任务时 how cot…

是否曾经想过关闭Microsoft账户,那么你来对地方了

本文介绍如何使用web浏览器删除Microsoft账户。 如果你删除Microsoft帐户&#xff0c;你将无法访问所有Microsoft应用程序和服务&#xff0c;包括Xbox网络。 如何永久删除Microsoft帐户 完全关闭你的Microsoft帐户的唯一方法是访问Microsoft网站。在开始之前&#xff0c;你应…

ssh远程连接服务器

目录 一、远程连接服务器简介 1、什么是远程连接服务器 2、远程连接服务器的功能 3、远程连接服务器的类型&#xff08;以登录的连接界面来分类&#xff09; 4、文字接口连接服务器 二、连接加密技术简介 安全套接层&#xff1a; 1、版本协商阶段 2、密钥和算法协商阶…

初识MQ——消息队列技术选型

文章目录 同步和异步通讯同步通讯异步通讯 技术对比 同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c…

解决vue3使用iconpark控制台预警提示问题

前言 最近在项目中使用 iconpark-icon 来管理图标&#xff0c;一切都很顺利&#xff0c;引入链接后&#xff0c;图标正常显示&#xff0c;没有报错。但是控制台却发出了预警信息。 [Vue warn]: Failed to resolve component: iconpark-icon If this is a native custom eleme…

redis------在java中操作redis

Redis&#xff08;非关系型数据库&#xff09;简介 redis下载 点击即可进入redis中文网进行下载 百度网盘windows版本 提取码 DMH6 redis主要特点 基于内存存储&#xff0c;读写性能高 适合存储热点数据&#xff08;热点商品、资讯、新闻&#xff09; 企业应用广泛 redis不同…

JAVA IO:NIO

1.阻塞 IO 模型 ​ 最传统的一种 IO 模型&#xff0c;即在读写数据过程中会发生阻塞现象。当用户线程发出 IO 请求之后&#xff0c;内核会去查看数据是否就绪&#xff0c;如果没有就绪就会等待数据就绪&#xff0c;而用户线程就会处于阻塞状态&#xff0c;用户线程交出 CPU。当…

你敢信!贪吃蛇游戏居然能插入到富文本编辑器里!

所有的大人都曾经是小孩子&#xff0c;虽然&#xff0c;只有少数的人记得。 ——《小王子》 引言 突然想起来前段时间看过的对半同学写的贪吃蛇游戏&#xff0c;据说对半同学只花了一个小时就写出来了。 《canvas 300行代码实现一个贪吃蛇 》 当时我正躺在床上刷手机&#x…

数据结构——链式二叉树

前言&#xff1a;哈喽小伙伴们&#xff0c;上篇文章我们讲述了一个特殊的二叉树——使用数组实现的堆的基本知识之后呢&#xff0c;从这篇文章开始&#xff0c;我们就正式进入普通二叉树的介绍啦&#xff0c;二叉树真正的难点——递归&#xff0c;即将来临&#xff0c;小伙伴们…

unity旋转选中效果

代码和预制体 函数PlayAnim&#xff08;&#xff09;中的角度要根据按钮数量手动填好 using System; using DG.Tweening; using DG.Tweening.Core; using DG.Tweening.Plugins.Options; using UnityEngine;// Token: 0x0200001B RID: 27 public class BtnParentScript : Base…

QT5.4.1无法打开文件

问题描述&#xff1a;起初是在QT代码中运行打开文件代码&#xff1a; QString gFilename QFileDialog::getOpenFileName(this,"open File",path,"*", nullptr,QFileDialog::DontUseNativeDialog);时&#xff0c;出现了堵塞情况&#xff0c;经过多次实验一…

海鹰数据虾皮:为Shopee卖家提供的完美数据分析工具

在如今的电子商务领域中&#xff0c;如何更好地了解市场动态、优化商品策略以提高运营效果成为了卖家们关注的重要问题。而海鹰数据&#xff08;Haiying Data&#xff09;作为一款专为Shopee平台设计的数据分析工具&#xff0c;为卖家们提供了市场趋势、商品分析、关键词优化、…

改善男性生理健康,缓解前列腺问题

前列腺是男性生殖系统中非常重要的一部分&#xff0c;它对男性的生理健康都着至关重要的作用。但是随着年龄的增长&#xff0c;前列腺问题也越来越普遍&#xff0c;给男性带来了很大的困扰。前列宝是Mission Hill推出的一款番茄红素多效复合胶囊&#xff0c;主要针对男性前列腺…

11 款顶级的免费 iPhone 数据恢复软件

iPhone 拥有巨大的存储容量。您可以在 iPhone 设备上存储图像、文档和视频等数据。有时&#xff0c;您的 iPhone 会发生许多意外事件&#xff0c;例如意外删除&#xff0c;从而导致数据丢失。这里有 11 个最好的免费 iPhone 数据恢复软件&#xff0c;您可以免费下载&#xff0c…

喜报!MIAOYUN入选成都高新区“瞪羚企业”

近日&#xff0c;成都高新区科技创新局公示了《2022年领先科技园区政策拟支持企业&#xff08;机构&#xff09;名单&#xff08;第二批次&#xff09;》&#xff0c;即最新一批的高新区“瞪羚企业”名单。成都元来云志科技有限公司&#xff08;简称“MIAOYUN”&#xff09;以其…

JVM 类加载机制(七)

1.加载&#xff0c;验证&#xff0c;准备&#xff0c;解析&#xff0c;初始化 ​ JVM 类加载机制分为五个部分&#xff1a;加载&#xff0c;验证&#xff0c;准备&#xff0c;解析&#xff0c;初始化&#xff0c;下面我们就分别来看一下这五个过程。 1.1. 加载 ​ 加载是类加…

Spring Cloud Stream 4.0.4 rabbitmq 发送消息多function

使用 idea 创建 Springboot 项目 添加 Spring cloud stream 和 rabbitmq 依赖 pom文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchem…

学生信息管理系统

摘 要 学生成绩管理系统是典型的信息管理系统(MIS)&#xff0c;其开发主要包括后台数据库的建立和维护以及前端应用程序的开发两个方面。经过分析&#xff0c;我们使用Microsoft公司的C语言开发工具&#xff0c;将与C语言技术与数据库SQL2008相结合进行设计。首先&#xff0c;…

【多线程】线程的三种常见创建方式

文章目录 线程创建方式1——Thread线程创建方式2——Runnable线程创建方式2——匿名内部类线程创建方式3——Callable、FutureTask,带返回值 线程其实是程序中的一条执行路径。 那怎样的程序才是多线程程序呢&#xff1f; 例如12306网站就是支持多线程的&#xff0c;因为同时可…

一下午终于配好:如何用vs code连接远程主机jupyter server(notebook/lab)

因为教研室的机器有2060&#xff0c;笔记本只有集成显卡&#xff0c;虽然也可用浏览器访问&#xff0c;但是vs code不论从界面还是扩展功能来说&#xff0c;都有更好的编程体验&#xff0c;所以想通过vs code远程连接jupyter server。 要实现该需求总体需要三个步骤&#xff1…