广告
返回顶部
首页 > 资讯 > 前端开发 > VUE >ThreadLocal好不好用
  • 460
分享到

ThreadLocal好不好用

2024-04-02 19:04:59 460人浏览 泡泡鱼
摘要

这篇文章主要介绍“ThreadLocal好不好用”,在日常操作中,相信很多人在ThreadLocal好不好用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”ThreadLoca

这篇文章主要介绍“ThreadLocal好不好用”,在日常操作中,相信很多人在ThreadLocal好不好用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”ThreadLocal好不好用”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

在 Java 中,如果要问哪个类使用简单,但用好最不简单?我想你的脑海中一定会浮现出一次词——“ThreadLocal”。

确实如此,ThreadLocal  原本设计是为了解决并发时,线程共享变量的问题,但由于过度设计,如弱引用和哈希碰撞,从而导致它的理解难度大和使用成本高等问题。当然,如果稍有不慎还是导致脏数据、内存溢出、共享变量更新等问题,但即便如此,ThreadLocal  依旧有适合自己的使用场景,以及无可取代的价值,比如本文要介绍了这两种使用场景,除了 ThreadLocal 之外,还真没有合适的替代方案。

使用场景1:本地变量

我们以多线程格式化时间为例,来演示 ThreadLocal 的价值和作用,当我们在多个线程中格式化时间时,通常会这样操作。

① 2个线程格式化

当有 2 个线程进行时间格式化时,我们可以这样写:

import java.text.SimpleDateFORMat; import java.util.Date;  public class Test {     public static void main(String[] args) throws InterruptedException {         // 创建并启动线程1         Thread t1 = new Thread(new Runnable() {             @Override             public void run() {                 // 得到时间对象                 Date date = new Date(1 * 1000);                 // 执行时间格式化                 formatAndPrint(date);             }         });         t1.start();         // 创建并启动线程2         Thread t2 = new Thread(new Runnable() {             @Override             public void run() {                 // 得到时间对象                 Date date = new Date(2 * 1000);                 // 执行时间格式化                 formatAndPrint(date);             }         });         t2.start();     }           private static void formatAndPrint(Date date) {         // 格式化时间对象         SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");         // 执行格式化         String result = simpleDateFormat.format(date);         // 打印最终结果         System.out.println("时间:" + result);     } }

以上程序的执行结果为:

ThreadLocal好不好用

上面的代码因为创建的线程数量并不多,所以我们可以给每个线程创建一个私有对象 SimpleDateFormat 来进行时间格式化。

② 10个线程格式化

当线程的数量从 2 个升级为 10 个时,我们可以使用 for 循环来创建多个线程执行时间格式化,具体实现代码如下:

import java.text.SimpleDateFormat; import java.util.Date;  public class Test {     public static void main(String[] args) throws InterruptedException {         for (int i = 0; i < 10; i++) {             int finalI = i;             // 创建线程             Thread thread = new Thread(new Runnable() {                 @Override                 public void run() {                     // 得到时间对象                     Date date = new Date(finalI * 1000);                     // 执行时间格式化                     formatAndPrint(date);                 }             });             // 启动线程             thread.start();         }     }          private static void formatAndPrint(Date date) {         // 格式化时间对象         SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");         // 执行格式化         String result = simpleDateFormat.format(date);         // 打印最终结果         System.out.println("时间:" + result);     } }

以上程序的执行结果为:

ThreadLocal好不好用

从上述结果可以看出,虽然此时创建的线程数和 SimpleDateFormat 的数量不算少,但程序还是可以正常运行的。

③ 1000个线程格式化

然而当我们将线程的数量从 10 个变成 1000 个的时候,我们就不能单纯的使用 for 循环来创建 1000  个线程的方式来解决问题了,因为这样频繁的新建和销毁线程会造成大量的系统开销和线程过度争抢 CPU 资源的问题。

所以经过一番思考后,我们决定使用线程池来执行这 1000  次的任务,因为线程池可以复用线程资源,无需频繁的新建和销毁线程,也可以通过控制线程池中线程的数量来避免过多线程所导致的 CPU  资源过度争抢和线程频繁切换所造成的性能问题,而且我们可以将 SimpleDateFormat 提升为全局变量,从而避免每次执行都要新建  SimpleDateFormat 的问题,于是我们写下了这样的代码:

import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;  public class App {     // 时间格式化对象     private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");      public static void main(String[] args) throws InterruptedException {         // 创建线程池执行任务         ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,                 TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));         for (int i = 0; i < 1000; i++) {             int finalI = i;             // 执行任务             threadPool.execute(new Runnable() {                 @Override                 public void run() {                     // 得到时间对象                     Date date = new Date(finalI * 1000);                     // 执行时间格式化                     formatAndPrint(date);                 }             });         }         // 线程池执行完任务之后关闭         threadPool.shutdown();     }           private static void formatAndPrint(Date date) {         // 执行格式化         String result = simpleDateFormat.format(date);         // 打印最终结果         System.out.println("时间:" + result);     } }

以上程序的执行结果为:

ThreadLocal好不好用

当我们怀着无比喜悦的心情去运行程序的时候,却发现意外发生了,这样写代码竟然会出现线程安全的问题。从上述结果可以看出,程序的打印结果竟然有重复内容的,正确的情况应该是没有重复的时间才对。

PS:所谓的线程安全问题是指:在多线程的执行中,程序的执行结果与预期结果不相符的情况。

a) 线程安全问题分析

为了找到问题所在,我们尝试查看 SimpleDateFormat 中 format 方法的源码来排查一下问题,format 源码如下:

private StringBuffer format(Date date, StringBuffer toAppendTo,                                 FieldDelegate delegate) {     // 注意此行代码     calendar.setTime(date);      boolean useDateFormatSymbols = useDateFormatSymbols();      for (int i = 0; i < compiledPattern.length; ) {         int tag = compiledPattern[i] >>> 8;         int count = compiledPattern[i++] & 0xff;         if (count == 255) {             count = compiledPattern[i++] << 16;             count |= compiledPattern[i++];         }          switch (tag) {             case TAG_QUOTE_ASCII_CHAR:                 toAppendTo.append((char)count);                 break;              case TAG_QUOTE_CHARS:                 toAppendTo.append(compiledPattern, i, count);                 i += count;                 break;              default:                 subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);                 break;         }     }     return toAppendTo; }

从上述源码可以看出,在执行 SimpleDateFormat.format 方法时,会使用 calendar.setTime  方法将输入的时间进行转换,那么我们想想一下这样的场景:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 线程 1 执行了 calendar.setTime(date) 方法,将用户输入的时间转换成了后面格式化时所需要的时间;

  3. 线程 1 暂停执行,线程 2 得到 CPU 时间片开始执行;

  4. 线程 2 执行了 calendar.setTime(date) 方法,对时间进行了修改;

  5. 线程 2 暂停执行,线程 1 得出 CPU 时间片继续执行,因为线程 1 和线程 2 使用的是同一对象,而时间已经被线程 2 修改了,所以此时当线程 1  继续执行的时候就会出现线程安全的问题了。

正常的情况下,程序的执行是这样的:

ThreadLocal好不好用

非线程安全的执行流程是这样的:

ThreadLocal好不好用

b) 解决线程安全问题:加锁

当出现线程安全问题时,我们想到的第一解决方案就是加,具体的实现代码如下:

import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;  public class App {     // 时间格式化对象     private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");      public static void main(String[] args) throws InterruptedException {         // 创建线程池执行任务         ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,                 TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));         for (int i = 0; i < 1000; i++) {             int finalI = i;             // 执行任务             threadPool.execute(new Runnable() {                 @Override                 public void run() {                     // 得到时间对象                     Date date = new Date(finalI * 1000);                     // 执行时间格式化                     formatAndPrint(date);                 }             });         }         // 线程池执行完任务之后关闭         threadPool.shutdown();     }           private static void formatAndPrint(Date date) {         // 执行格式化         String result = null;         // 加锁         synchronized (App.class) {             result = simpleDateFormat.format(date);         }         // 打印最终结果         System.out.println("时间:" + result);     } }

以上程序的执行结果为:

ThreadLocal好不好用

从上述结果可以看出,使用了 synchronized 加锁之后程序就可以正常的执行了。

加锁的缺点

加锁的方式虽然可以解决线程安全的问题,但同时也带来了新的问题,当程序加锁之后,所有的线程必须排队执行某些业务才行,这样无形中就降低了程序的运行效率了。

有没有既能解决线程安全问题,又能提高程序的执行速度的解决方案呢?

答案是:有的,这个时候 ThreadLocal就要上场了。

c) 解决线程安全问题:ThreadLocal

1.ThreadLocal 介绍

ThreadLocal 从字面的意思来理解是线程本地变量的意思,也就是说它是线程中的私有变量,每个线程只能使用自己的变量。

以上面线程池格式化时间为例,当线程池中有 10 个线程时,SimpleDateFormat 会存入 ThreadLocal 中,它也只会创建 10  个对象,即使要执行 1000 次时间格式化任务,依然只会新建 10 个 SimpleDateFormat 对象,每个线程调用自己的 ThreadLocal  变量。

2.ThreadLocal 基础使用

ThreadLocal 常用的核心方法有三个:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. set 方法:用于设置线程独立变量副本。没有 set 操作的 ThreadLocal 容易引起脏数据。

  3. get 方法:用于获取线程独立变量副本。没有 get 操作的 ThreadLocal 对象没有意义。

  4. remove 方法:用于移除线程独立变量副本。没有 remove 操作容易引起内存泄漏。

ThreadLocal 所有方法如下图所示:

ThreadLocal好不好用

官方说明文档:https://docs.oracle.com/javase/8/docs/api/

ThreadLocal 基础用法如下:

 public class ThreadLocalExample {     // 创建一个 ThreadLocal 对象     private static ThreadLocal<String> threadLocal = new ThreadLocal<>();      public static void main(String[] args) {         // 线程执行任务         Runnable runnable = new Runnable() {             @Override             public void run() {                 String threadName = Thread.currentThread().getName();                 System.out.println(threadName + " 存入值:" + threadName);                 // 在 ThreadLocal 中设置值                 threadLocal.set(threadName);                 // 执行方法,打印线程中设置的值                 print(threadName);             }         };         // 创建并启动线程 1         new Thread(runnable, "MyThread-1").start();         // 创建并启动线程 2         new Thread(runnable, "MyThread-2").start();     }           private static void print(String threadName) {         try {             // 得到 ThreadLocal 中的值             String result = threadLocal.get();             // 打印结果             System.out.println(threadName + " 取出值:" + result);         } finally {             // 移除 ThreadLocal 中的值(防止内存溢出)             threadLocal.remove();         }     } }

以上程序的执行结果为:

ThreadLocal好不好用

从上述结果可以看出,每个线程只会读取到属于自己的 ThreadLocal 值。

3.ThreadLocal 高级用法

① 初始化:initialValue

public class ThreadLocalByInitExample {     // 定义 ThreadLocal     private static ThreadLocal<String> threadLocal = new ThreadLocal(){         @Override         protected String initialValue() {             System.out.println("执行 initialValue() 方法");             return "默认值";         }     };      public static void main(String[] args) {         // 线程执行任务         Runnable runnable = new Runnable() {             @Override             public void run() {                 // 执行方法,打印线程中数据(未设置值打印)                 print(threadName);             }         };         // 创建并启动线程 1         new Thread(runnable, "MyThread-1").start();         // 创建并启动线程 2         new Thread(runnable, "MyThread-2").start();     }           private static void print(String threadName) {         // 得到 ThreadLocal 中的值         String result = threadLocal.get();         // 打印结果         System.out.println(threadName + " 得到值:" + result);     } }

以上程序的执行结果为:

ThreadLocal好不好用

当使用了 #threadLocal.set 方法之后,initialValue 方法就不会被执行了,如下代码所示:

public class ThreadLocalByInitExample {     // 定义 ThreadLocal     private static ThreadLocal<String> threadLocal = new ThreadLocal() {         @Override         protected String initialValue() {             System.out.println("执行 initialValue() 方法");             return "默认值";         }     };      public static void main(String[] args) {         // 线程执行任务         Runnable runnable = new Runnable() {             @Override             public void run() {                 String threadName = Thread.currentThread().getName();                 System.out.println(threadName + " 存入值:" + threadName);                 // 在 ThreadLocal 中设置值                 threadLocal.set(threadName);                 // 执行方法,打印线程中设置的值                 print(threadName);             }         };         // 创建并启动线程 1         new Thread(runnable, "MyThread-1").start();         // 创建并启动线程 2         new Thread(runnable, "MyThread-2").start();     }           private static void print(String threadName) {         try {             // 得到 ThreadLocal 中的值             String result = threadLocal.get();             // 打印结果             System.out.println(threadName + "取出值:" + result);         } finally {             // 移除 ThreadLocal 中的值(防止内存溢出)             threadLocal.remove();         }     } }

以上程序的执行结果为:

ThreadLocal好不好用

为什么 set 之后,初始化代码就不执行了?

要理解这个问题,需要从 ThreadLocal.get() 方法的源码中得到答案,因为初始化方法 initialValue 在 ThreadLocal  创建时并不会立即执行,而是在调用了 get 方法只会才会执行,测试代码如下:

import java.util.Date;  public class ThreadLocalByInitExample {     // 定义 ThreadLocal     private static ThreadLocal<String> threadLocal = new ThreadLocal() {         @Override         protected String initialValue() {             System.out.println("执行 initialValue() 方法 " + new Date());             return "默认值";         }     };     public static void main(String[] args) {         // 线程执行任务         Runnable runnable = new Runnable() {             @Override             public void run() {                 // 得到当前线程名称                 String threadName = Thread.currentThread().getName();                 // 执行方法,打印线程中设置的值                 print(threadName);             }         };         // 创建并启动线程 1         new Thread(runnable, "MyThread-1").start();         // 创建并启动线程 2         new Thread(runnable, "MyThread-2").start();     }           private static void print(String threadName) {         System.out.println("进入 print() 方法 " + new Date());         try {             // 休眠 1s             Thread.sleep(1000);         } catch (InterruptedException e) {             e.printStackTrace();         }         // 得到 ThreadLocal 中的值         String result = threadLocal.get();         // 打印结果         System.out.println(String.format("%s 取得值:%s %s",                 threadName, result, new Date()));     } }

以上程序的执行结果为:

ThreadLocal好不好用

从上述打印的时间可以看出:initialValue 方法并不是在 ThreadLocal 创建时执行的,而是在调用 Thread.get  方法时才执行的。

接下来来看 Threadlocal.get 源码的实现:

public T get() {     // 得到当前的线程     Thread t = Thread.currentThread();     ThreadLocalMap map = getMap(t);     // 判断 ThreadLocal 中是否有数据     if (map != null) {         ThreadLocalMap.Entry e = map.getEntry(this);         if (e != null) {             @SuppressWarnings("unchecked")             T result = (T)e.value;             // 有 set 值,直接返回数据             return result;         }     }     // 执行初始化方法【重点关注】     return setInitialValue(); } private T setInitialValue() {     // 执行初始化方法【重点关注】     T value = initialValue();     Thread t = Thread.currentThread();     ThreadLocalMap map = getMap(t);     if (map != null)         map.set(this, value);     else         createMap(t, value);     return value; }

从上述源码可以看出,当 ThreadLocal 中有值时会直接返回值 e.value,只有 Threadlocal 中没有任何值时才会执行初始化方法  initialValue。

注意事项&mdash;类型必须保持一致

注意在使用 initialValue 时,返回值的类型要和 ThreadLoca 定义的数据类型保持一致,如下图所示:

ThreadLocal好不好用

如果数据不一致就会造成 ClassCaseException 类型转换异常,如下图所示:

ThreadLocal好不好用

② 初始化2:withInitial

import java.util.function.Supplier;  public class ThreadLocalByInitExample {     // 定义 ThreadLocal     private static ThreadLocal<String> threadLocal =             ThreadLocal.withInitial(new Supplier<String>() {                 @Override                 public String get() {                     System.out.println("执行 withInitial() 方法");                     return "默认值";                 }             });     public static void main(String[] args) {         // 线程执行任务         Runnable runnable = new Runnable() {             @Override             public void run() {                 String threadName = Thread.currentThread().getName();                 // 执行方法,打印线程中设置的值                 print(threadName);             }         };         // 创建并启动线程 1         new Thread(runnable, "MyThread-1").start();         // 创建并启动线程 2         new Thread(runnable, "MyThread-2").start();     }           private static void print(String threadName) {         // 得到 ThreadLocal 中的值         String result = threadLocal.get();         // 打印结果         System.out.println(threadName + " 得到值:" + result);     } }

以上程序的执行结果为:

ThreadLocal好不好用

通过上述的代码发现,withInitial 方法的使用好和 initialValue  好像没啥区别,那为啥还要造出两个类似的方法呢?客官莫着急,继续往下看。

③ 更简洁的 withInitial 使用

withInitial 方法的优势在于可以更简单的实现变量初始化,如下代码所示:

public class ThreadLocalByInitExample {     // 定义 ThreadLocal     private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "默认值");     public static void main(String[] args) {         // 线程执行任务         Runnable runnable = new Runnable() {             @Override             public void run() {                 String threadName = Thread.currentThread().getName();                 // 执行方法,打印线程中设置的值                 print(threadName);             }         };         // 创建并启动线程 1         new Thread(runnable, "MyThread-1").start();         // 创建并启动线程 2         new Thread(runnable, "MyThread-2").start();     }           private static void print(String threadName) {         // 得到 ThreadLocal 中的值         String result = threadLocal.get();         // 打印结果         System.out.println(threadName + " 得到值:" + result);     } }

以上程序的执行结果为:

ThreadLocal好不好用

4.ThreadLocal 版时间格式化

了解了 ThreadLocal 的使用之后,我们回到本文的主题,接下来我们将使用 ThreadLocal 来实现 1000  个时间的格式化,具体实现代码如下:

import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;  public class MyThreadLocalByDateFormat {     // 创建 ThreadLocal 并设置默认值     private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =             ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss"));      public static void main(String[] args) {         // 创建线程池执行任务         ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,                 TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));         // 执行任务         for (int i = 0; i < 1000; i++) {             int finalI = i;             // 执行任务             threadPool.execute(new Runnable() {                 @Override                 public void run() {                     // 得到时间对象                     Date date = new Date(finalI * 1000);                     // 执行时间格式化                     formatAndPrint(date);                 }             });         }         // 线程池执行完任务之后关闭         threadPool.shutdown();         // 线程池执行完任务之后关闭         threadPool.shutdown();     }          private static void formatAndPrint(Date date) {         // 执行格式化         String result = dateFormatThreadLocal.get().format(date);         // 打印最终结果         System.out.println("时间:" + result);     } }

以上程序的执行结果为:

ThreadLocal好不好用

从上述结果可以看出,使用 ThreadLocal 也可以解决线程并发问题,并且避免了代码加锁排队执行的问题。

使用场景2:跨类传递数据

除了上面的使用场景之外,我们还可以使用 ThreadLocal 来实现线程中跨类、跨方法的数据传递。比如登录用户的 User  对象信息,我们需要在不同的子系统中多次使用,如果使用传统的方式,我们需要使用方法传参和返回值的方式来传递 User  对象,然而这样就无形中造成了类和类之间,甚至是系统和系统之间的相互耦合了,所以此时我们可以使用 ThreadLocal 来实现 User 对象的传递。

确定了方案之后,接下来我们来实现具体的业务代码。我们可以先在主线程中构造并初始化一个 User 对象,并将此 User 对象存储在 ThreadLocal  中,存储完成之后,我们就可以在同一个线程的其他类中,如仓储类或订单类中直接获取并使用 User 对象了,具体实现代码如下。

主线程中的业务代码:

public class ThreadLocalByUser {     public static void main(String[] args) {         // 初始化用户信息         User user = new User("Java");         // 将 User 对象存储在 ThreadLocal 中         UserStorage.setUser(user);         // 调用订单系统         OrderSystem orderSystem = new OrderSystem();         // 添加订单(方法内获取用户信息)         orderSystem.add();         // 调用仓储系统         RepertorySystem repertory = new RepertorySystem();         // 减库存(方法内获取用户信息)         repertory.decrement();     } }

User 实体类:

 class User {     public User(String name) {         this.name = name;     }     private String name;     public String getName() {         return name;     }     public void setName(String name) {         this.name = name;     } }

ThreadLocal 操作类:

 class UserStorage {     // 用户信息     public static ThreadLocal<User> USER = new ThreadLocal();           public static void setUser(User user) {         USER.set(user);     } }

* 订单类

 class OrderSystem {          public void add() {         // 得到用户信息         User user = UserStorage.USER.get();         // 业务处理代码(忽略)...         System.out.println(String.format("订单系统收到用户:%s 的请求。",                 user.getName()));     } }

仓储类:

 class RepertorySystem {          public void decrement() {         // 得到用户信息         User user = UserStorage.USER.get();         // 业务处理代码(忽略)...         System.out.println(String.format("仓储系统收到用户:%s 的请求。",                 user.getName()));     } }

以上程序的最终执行结果:

ThreadLocal好不好用

从上述结果可以看出,当我们在主线程中先初始化了 User 对象之后,订单类和仓储类无需进行任何的参数传递也可以正常获得 User  对象了,从而实现了一个线程中,跨类和跨方法的数据传递。

总结

使用 ThreadLocal 可以创建线程私有变量,所以不会导致线程安全问题,同时使用 ThreadLocal  还可以避免因为引入锁而造成线程排队执行所带来的性能消耗;再者使用 ThreadLocal 还可以实现一个线程内跨类、跨方法的数据传递。

到此,关于“ThreadLocal好不好用”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

--结束END--

本文标题: ThreadLocal好不好用

本文链接: https://www.lsjlt.com/news/67476.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • ThreadLocal好不好用
    这篇文章主要介绍“ThreadLocal好不好用”,在日常操作中,相信很多人在ThreadLocal好不好用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”ThreadLoca...
    99+
    2022-10-19
  • 香港vps好不好用
    香港VPS是一种虚拟专用服务器,它提供了高性能和稳定的云计算资源。它具有以下优点:1. 高速互联网连接:香港具有先进的互联网基础设施...
    99+
    2023-09-15
    香港vps vps
  • html学习好不好
    HTML学习好不好?对于想要深入学习前端开发的人来说,HTML是第一步。作为前端开发语言的基础,HTML在Web页面的搭建中起着至关重要的作用。因此,学习HTML是否好,是一个有着广泛讨论的话题。首先,我们先了解一下HTML的定义和作用。H...
    99+
    2023-05-14
  • golang不好用
    Golang作为一种相对较新的编程语言,其实已经在许多领域取得了令人瞩目的成就,尤其是在高并发、分布式、网络编程等方向上被广泛使用。然而,仍有一些人认为Golang不好用,本篇文章将对其进行探讨。先来看看为什么会有人认为Golang不好用。...
    99+
    2023-05-15
  • navicat管理工具好不好用
    这篇文章将为大家详细讲解有关navicat管理工具好不好用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。  说起这几个软件,大家应该都不陌生,Navicat for My...
    99+
    2022-10-18
  • vue到底好不好学
    近年来,前端开发框架层出不穷,其中Vue.js是一款备受关注的开源JavaScript框架,Vue.js被誉为是一种渐进式框架,它极大地简化了构建Web应用程序的过程。那么,Vue.js到底好不好学?本文将通过对Vue.js的概述、学习曲线...
    99+
    2023-05-18
  • 云服务器好不好
    云服务器是一种提供存储和计算服务的平台,通常被用于在互联网上提供虚拟化云存储服务,例如Docker容器。对于个人和企业用户而言,选择云服务器有几个重要的因素: 速度:选择云服务器时,速度是一个关键因素。因为它取决于服务器的配置和网络连接...
    99+
    2023-10-26
    好不好 服务器
  • 美国虚拟主机好不好用
    美国虚拟主机通常具有以下优势:1. 稳定性:美国虚拟主机提供商通常具有先进的服务器设备和网络基础设施,能够提供稳定可靠的服务。2. ...
    99+
    2023-09-12
    美国虚拟主机 虚拟主机
  • 阿里云服务器好不好用
    阿里云服务器是一家中国公司推出的云服务器品牌,它提供了高性能、高可用性和可靠的云计算服务,可以帮助您轻松实现云存储、云备份、云安全等高级云计算功能。 不过,由于其品牌知名度和市场份额,阿里云服务器在国内云服务器市场上有一定的影响力。用户在...
    99+
    2023-10-26
    阿里 好用 服务器
  • 租用asp香港空间好不好
    租用ASP香港空间的好处和不足如下:好处:1、稳定性高香港的网络基础设施比较发达,网络带宽和稳定性相对较高,可以保证ASP香港空间的...
    99+
    2023-03-22
    asp香港空间 香港空间 空间
  • asp香港空间租用好不好
    优点:1. 地理位置优越:香港是亚洲的商业中心,ASP香港空间租用在这个地理位置上具有优越性,可以为客户提供更多商业机会。2. 设施...
    99+
    2023-05-29
    asp香港空间 香港空间 空间
  • 网吧用云服务器好不好
    云服务器可以减少企业使用的计算资源成本,并且可以随时随地访问,无论是在家中或是在任何地方都可以快速的进行数据传输,提高企业使用云服务器的效率和灵活性。 云服务器拥有多种云服务,如负载均衡、云监控等,可以帮助企业减少网络故障对业务的影响,同...
    99+
    2023-10-26
    好不好 网吧 服务器
  • win10系统怎么样好不好用
    Win10系统相较于之前的Windows系统有着许多改进和优化,所以它是非常好用的。以下是一些Win10系统的优点:1. 用户界面友...
    99+
    2023-08-30
    win10
  • 网吧用云服务器好不好用
    云服务器可以满足多人使用,多人可以共同使用一个服务器,可以更快的完成任务,比如:一起打游戏,一起建立一个网页。 在云服务器上,玩家可以共享自己的资源,比如游戏,电影等等,也可以使用云服务器的资源,来实现更加复杂的应用,比如:云游戏。 云服...
    99+
    2023-10-27
    好用 网吧 服务器
  • 发烧级显卡好不好
    发烧级显卡好,它们拥有更多的计算核心、更高的频率和更大的显存,这使得它们可以在处理图形方面表现出色。对于喜欢玩高画质游戏或者从事图形设计和视频编辑等专业工作的用户来说,发烧级显卡可以提供更流畅、更细腻的图像效果,使他们能够享受更高质量的视觉...
    99+
    2023-07-10
  • 免费ssl证书好不好
    免费SSL证书有一些优点和缺点。以下是一些讨论:优点:1. 免费:免费SSL证书不需要支付任何费用,适用于小型网站或个人使用,可以降...
    99+
    2023-09-01
    ssl证书
  • windows dll修复小助手好不好用
    本文小编为大家详细介绍“windows dll修复小助手好不好用”,内容详细,步骤清晰,细节处理妥当,希望这篇“windows dll修复小助手好不好用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,...
    99+
    2023-02-14
    windows dll
  • PHP 和 Bash 一起用到底好不好?
    PHP 和 Bash 都是非常流行的编程语言,它们各自有着自己的优点和应用场景。但是,在一些特定的情况下,使用 PHP 和 Bash 结合起来可能会更加高效和便捷。本文将探讨 PHP 和 Bash 结合使用的优缺点,并且演示一些具体的例子...
    99+
    2023-07-03
    bash 并发 面试
  • 免费在线代理ip好不好用
    这篇文章给大家分享的是有关免费在线代理ip好不好用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。1、IP质量差的免费代理,用户群大。所有使用免费代理IP的人都知道,使用免费代理IP很难。用完之后,我就有了不再使用...
    99+
    2023-06-20
  • 网吧用云服务器好不好用呀
    一、云服务器的优点 可靠性高 云服务器采用高性能的服务器硬件,具有高可靠性和高可用性的特点。云服务器可以快速地为用户提供高速、稳定的计算服务,同时还能够自动化管理和监控,大大减少了人力成本和维护成本。 可扩展性强 云服务器可以根据...
    99+
    2023-10-28
    好用 网吧 服务器
软考高级职称资格查询
推荐阅读
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作