热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

Java并发编程之线程之间的共享和协作

这篇文章主要介绍了Java并发编程之线程之间的共享和协作,文中有非常详细的代码示例,对正在学习java的小伙伴们有一定的帮助,需要的朋友可以参考下

一、线程间的共享

1.1 ynchronized内置锁

用处

  • Java支持多个线程同时访问一个对象或者对象的成员变量
  • 关键字synchronized可以修饰方法或者以同步块的形式来进行使用
  • 它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中
  • 它保证了线程对变量访问的可见性和排他性(原子性、可见性、有序性),又称为内置锁机制。

对象锁和类锁

  • 对象锁是用于对象实例方法,或者一个对象实例上的
  • 类锁是用于类的静态方法或者一个类的class对象上的
  • 类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁
  • 注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,类锁其实锁的是每个类的对应的class对象
  • 类锁和对象锁之间也是互不干扰的。

1.2 volatile关键字

  • 最轻量的同步机制,保证可见性,不保证原子性
  • volatile保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  • volatile最适用的场景:只有一线程写,多个线程读的场景

1.3 ThreadLocal

  • ThreadLocal 和 Synchonized 都用于解决多线程并发访问。
  • 可是ThreadLocal与synchronized有本质的差别:
  • synchronized是利用锁的机制,使变量或代码块在某一时该仅仅能被一个线程访问。
  • 而ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。
  • Spring的事务就借助了ThreadLocal类。

1.4 Spring的事务借助ThreadLocal类

Spring会从数据库连接池中获得一个connection,然会把connection放进ThreadLocal中,也就和线程绑定了,事务需要提交或者回滚,只要从ThreadLocal中拿到connection进行操作。

1.4.1 为何Spring的事务要借助ThreadLocal类?

以JDBC为例,正常的事务代码可能如下:
dbc = new DataBaseConnection();//第1行
Connection con = dbc.getConnection();//第2行
con.setAutoCommit(false);// //第3行
con.executeUpdate(...);//第4行
con.executeUpdate(...);//第5行
con.executeUpdate(...);//第6行
con.commit();第7行
上述代码,可以分成三个部分:
事务准备阶段:第1~3行
业务处理阶段:第4~6行
事务提交阶段:第7行 
  • 不管我们开启事务还是执行具体的sql都需要一个具体的数据库连接。
  • 开发应用一般都采用三层结构,我们的Service会调用一系列的DAO对数据库进行多次操作,那么,这个时候我们就无法控制事务的边界了,因为实际应用当中,我们的Service调用的DAO的个数是不确定的,可根据需求而变化,而且还可能出现Service调用Service的情况。
  • 如果不使用ThreadLocal,如何让三个DAO使用同一个数据源连接呢?我们就必须为每个DAO传递同一个数据库连接,要么就是在DAO实例化的时候作为构造方法的参数传递,要么在每个DAO的实例方法中作为方法的参数传递。
Connection cOnn= getConnection();
Dao1 dao1 = new Dao1(conn);
dao1.exec();
Dao2 dao2 = new Dao2(conn);
dao2.exec();
Dao3 dao3 = new Dao3(conn);
dao3.exec();
conn.commit();
  • 为了让这个数据库连接可以跨阶段传递,又不显式的进行参数传递,就必须使用别的办法。
  • Web容器中,每个完整的请求周期会由一个线程来处理。因此,如果我们能将一些参数绑定到线程的话,就可以实现在软件架构中跨层次的参数共享(是隐式的共享)。而JAVA中恰好提供了绑定的方法–使用ThreadLocal。
  • 结合使用Spring里的IOC和AOP,就可以很好的解决这一点。
  • 只要将一个数据库连接放入ThreadLocal中,当前线程执行时只要有使用数据库连接的地方就从ThreadLocal获得就行了。

1.4.2 ThreadLocal的使用

void set(Object value)

  • 设置当前线程的线程局部变量的值。

public Object get()

  • 该方法返回当前线程所对应的线程局部变量。

public void remove()

  • 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。
  • 需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收
  • 所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

protected Object initialValue()

  • 返回该线程局部变量的初始值
  • 该方法是一个protected的方法,显然是为了让子类覆盖而设计的。
  • 这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。
  • ThreadLocal中的缺省实现直接返回一个null。

public final static ThreadLocal RESOURCE = new ThreadLocal()

  • RESOURCE代表一个能够存放String类型的ThreadLocal对象。
  • 此时不论任何一个线程能够并发访问这个变量,对它进行写入、读取操作,都是线程安全的。

1.4.3 ThreadLocal实现解析

2.ThreadLocal原理

public class ThreadLocal {
    //get方法,其实就是拿到每个线程独有的ThreadLocalMap
    //然后再用ThreadLocal的当前实例,拿到Map中的相应的Entry,然后就可以拿到相应的值返回出去。
    //如果Map为空,还会先进行map的创建,初始化等工作。
    public T get() {
        //先取到当前线程,然后调用getMap方法获取对应线程的ThreadLocalMap
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
    // Thread类中有一个 ThreadLocalMap 类型成员,所以getMap是直接返回Thread的成员
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    // ThreadLocalMap是ThreadLocal的静态内部类
    static class ThreadLocalMap {
        ThreadLocalMap(ThreadLocal<&#63;> firstKey, Object firstValue) {
            // 用数组保存 Entry , 因为可能有多个变量需要线程隔离访问,即声明多个 ThreadLocal 变量
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            // Entry 类似于 map 的 key-value 结构
            // key 就是 ThreadLocal, value 就是需要隔离访问的变量
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
        ...
    }
    
    //Entry内部静态类,它继承了WeakReference,
    //总之它记录了两个信息,一个是ThreadLocal<&#63;>类型,一个是Object类型的值
    static class Entry extends WeakReference> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<&#63;> k, Object v) {
            super(k);
            value = v;
        }
    }
    
    //getEntry方法则是获取某个ThreadLocal对应的值
    private Entry getEntry(ThreadLocal<&#63;> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }
    
    //set方法就是更新或赋值相应的ThreadLocal对应的值
    private void set(ThreadLocal<&#63;> key, Object value) {
        ...
    }
    ...
}

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    ...
}

1.5引用基础知识

1.5.1 引用

  • 创建对象 Object o = new Object();
  • 这个o,我们可以称之为对象引用,而new Object()我们可以称之为在内存中产生了一个对象实例。
  • 当 o=null 时,只是表示o不再指向堆中Object的对象实例,不代表这个对象实例不存在了。

1.5.2 强引用

  • 指在程序代码之中普遍存在的,类似“Object obj=new Object()
  • 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象实例。

1.5.3 软引用

  •  用来描述一些还有用但并非必需的对象。
  • 对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象实例列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
  • 在JDK 1.2之后,提供了SoftReference类来实现软引用。

1.5.4 弱引用

  • 用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象实例只能生存到下一次垃圾收集发生之前。
  • 当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。
  • 在JDK 1.2之后,提供了WeakReference类来实现弱引用。

1.5.5 虚引用

  •  也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。
  • 一个对象实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。
  • 为一个对象设置虚引用关联的唯一目的就是能在这个对象实例被收集器回收时收到一个系统通知。
  • 在JDK 1.2之后,提供了PhantomReference类来实现虚引用。

1.6 使用 ThreadLocal 引发内存泄漏

1.6.1 准备

将堆内存大小设置为-Xmx256m

启用一个线程池,大小固定为5个线程

//5M大小的数组
private static class LocalVariable {
    private byte[] value = new byte[1024*1024*5];
}

// 创建线程池,固定为5个线程
private static ThreadPoolExecutor poolExecutor
        = new ThreadPoolExecutor(5,5,1, TimeUnit.MINUTES,new LinkedBlockingQueue<>());
        
//ThreadLocal共享变量
private ThreadLocal data;

@Override
public void run() {
    //场景1:不执行任何有意义的代码,当所有的任务提交执行完成后,查看内存占用情况,占用 25M 左右
    //System.out.println("hello ThreadLocal...");

    //场景2:创建 数据对象,执行完成后,查看内存占用情况,与场景1相同
    //new LocalVariable();

    //场景3:启用 ThreadLocal,执行完成后,查看内存占用情况,占用 100M 左右
    ThreadLocalOOM obj = new ThreadLocalOOM();
    obj.data = new ThreadLocal<>();
    obj.data.set(new LocalVariable());
    System.out.println("update ThreadLocal data value..........");

    //场景4: 加入 remove(),执行完成后,查看内存占用情况,与场景1相同
    //obj.data.remove();

    //分析:在场景3中,当启用了ThreadLocal以后确实发生了内存泄漏
}

场景1:

  • 首先任务中不执行任何有意义的代码,当所有的任务提交执行完成后,可以看见,我们这个应用的内存占用基本上为25M左右

场景2:

  • 然后我们只简单的在每个任务中new出一个数组,执行完成后我们可以看见,内存占用基本和场景1相同

场景3:

  • 当我们启用了ThreadLocal以后,执行完成后我们可以看见,内存占用变为了100多M

场景4:

  • 我们加入一行代码 obj.data.remove(); ,再执行,看看内存情况,可以看见,内存占用基本和场景1相同。

场景分析:

  • 这就充分说明,场景3,当我们启用了ThreadLocal以后确实发生了内存泄漏。

1.6.2 内存泄漏分析

  •  通过对ThreadLocal的分析,我们可以知道每个Thread 维护一个 ThreadLocalMap,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object,也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。
  • 仔细观察ThreadLocalMap,这个map是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

2.ThreadLocal原理

  • 图中的虚线表示弱引用。
  • 当把threadlocal变量置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收
  • 这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value
  • 如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而这块value永远不会被访问到了,所以存在着内存泄露。
  • 可以通过Debug模式,查看变量 poolExecutor->workers->0->thread->threadLocals,会发现线程的成员变量 threadLocals 的 size=1,map 中存放了一个 referent=null, value=data对象
  • 只有当前thread结束以后,current thread就不会存在栈中,强引用断开,Current Thread、Map value将全部被GC回收。
  • 最好的做法是在不需要使用ThreadLocal变量后,都调用它的remove()方法,清除数据。

场景3分析:

  • 在场景3中,虽然线程池里面的任务执行完毕了,但是线程池里面的5个线程会一直存在直到JVM退出,我们set了线程的localVariable变量后没有调用localVariable.remove()方法,导致线程池里面的5个线程的threadLocals变量里面的new LocalVariable()实例没有被释放。

从表面上看内存泄漏的根源在于使用了弱引用。为什么使用弱引用而不是强引用?下面我们分两种情况讨论:

  •  key 使用强引用:对ThreadLocal对象实例的引用被置为null了,但是ThreadLocalMap还持有这个ThreadLocal对象实例的强引用,如果没有手动删除,ThreadLocal的对象实例不会被回收,导致Entry内存泄漏。
  • key 使用弱引用:对ThreadLocal对象实例的引用被被置为null了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal的对象实例也会被回收。value在下一次ThreadLocalMap调用set,get,remove都有机会被回收。
  • 比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障。

因此,ThreadLocal内存泄漏的根源是:

  • 由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

总结:

  • JVM利用设置ThreadLocalMap的Key为弱引用,来避免内存泄露。
  • JVM利用调用remove、get、set方法的时候,回收弱引用。
  • 当ThreadLocal存储很多Key为null的Entry的时候,而不再去调用remove、get、set方法,那么将导致内存泄漏。
  • 使用线程池 + ThreadLocal时要小心,因为这种情况下,线程是一直在不断的重复运行的,从而也就造成了value可能造成累积的情况。

错误使用ThreadLocal导致线程不安全:

  • 仔细考察ThreadLocal和Thead的代码,我们发现ThreadLocalMap中保存的其实是对象的一个引用,这样的话,当有其他线程对这个引用指向的对象实例做修改时,其实也同时影响了所有的线程持有的对象引用所指向的同一个对象实例。
  • 这也就是为什么上面的程序为什么会输出一样的结果:5个线程中保存的是同一个Number对象的引用,因此它们最终输出的结果是相同的。
  • 正确的用法是让每个线程中的ThreadLocal都应该持有一个新的Number对象。 线程间的协作

二、线程间的协作

  • 线程之间相互配合,完成某项工作;
  • 比如一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作;
  • 前者是生产者,后者就是消费者,这种模式隔离了“做什么”(what)和“怎么做”(How);
  • 常见的方法是让消费者线程不断地循环检查变量是否符合预期在while循环中设置不满足的条件,如果条件满足则退出while循环,从而完成消费者的工作。

存在如下问题:

  • 1)难以确保及时性;
  • 2)难以降低开销。如果降低睡眠的时间,比如休眠1毫秒,这样消费者能更加迅速地发现条件变化,但是却可能消耗更多的处理器资源,造成了无端的浪费。

2.1等待和通知机制

是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。

上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

notify():

通知一个在对象上等待的线程,使其从wait方法返回,而返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入WAITING状态。

notifyAll():

通知所有等待在该对象上的线程。

wait():

调用该方法的线程进入 WAITING状态,只有等待另外线程的通知或被中断才会返回.需要注意,调用wait()方法后,会释放对象的锁。

wait(long):

超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回;

wait (long,int):

对于超时时间更细粒度的控制,可以达到纳秒;

2.2等待和通知的标准范式

等待方遵循如下原则:

  •  1.获取对象的锁
  • 2.循环里判断条件是否满足,如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
  • 条件满足则执行对应的逻辑。
synchronized(对象){
    while(条件不满足){
        对象.wait();
    }
    对应的逻辑
}

通知方遵循如下原则:

  •  1.获取对象的锁。
  • 2.改变条件。
  • 3.通知所有等待在对象上的线程。
synchronized(对象){
    改变条件
    对象.notifyAll();
}

在调用wait()、notify()系列方法之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait() 方法、notify()系列方法;

  • 进入wait() 方法后,当前线程释放锁,在从wait() 返回前,线程与其他线程竞争重新获得锁,执行notify()系列方法的线程退出synchronized代码块的时候后,他们就会去竞争。
  • 如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

notify() 和 notifyAll() 应该用谁?

  • 尽量用 notifyAll()
  • 谨慎使用notify(),因为notify()只会唤醒一个线程,我们无法确保被唤醒的这个线程一定就是我们需要唤醒的线程;

2.3等待超时模式实现一个连接池

调用场景:

  •  调用一个方法时等待一段时间(一般来说是给定一个时间段),如果该方法能够在给定的时间段之内得到结果,那么将结果立刻返回,反之,超时返回默认结果。
  • 假设等待时间段是T,那么可以推断出在当前时间now+T之后就会超时
  • 等待持续时间:REMAINING=T ;
  • 超时时间:FUTURE=now+T ;
  • 客户端获取连接的过程被设定为等待超时的模式,也就是在1000毫秒内如果无法获取到可用连接,将会返回给客户端一个null。
  • 设定连接池的大小为10个,然后通过调节客户端的线程数来模拟无法获取连接的场景。
  • 通过构造函数初始化连接的最大上限,通过一个双向队列来维护连接,调用方需要先调用fetchConnection(long)方法来指定在多少毫秒内超时获取连接,当连接使用完成后,需要调用releaseConnection(Connection)方法将连接放回线程池

调用yield() 、sleep()、wait()、notify()等方法对锁有何影响?

  • yield() 、sleep()被调用后,都不会释放当前线程所持有的锁。
  • 调用wait()方法后,会释放当前线程持有的锁,而且当前被唤醒后,会重新去竞争锁,锁竞争到后才会执行wait方法后面的代码。
  • 调用notify()系列方法后,对锁无影响,线程只有在synchronized同步代码执行完后才会自然而然的释放锁,所以notify()系列方法一般都是synchronized同步代码的最后一行。

到此这篇关于Java并发编程之线程之间的共享和协作的文章就介绍到这了,更多相关java线程之间的共享和协作内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 本题库精选了Java核心知识点的练习题,旨在帮助学习者巩固和检验对Java理论基础的掌握。其中,选择题部分涵盖了访问控制权限等关键概念,例如,Java语言中仅允许子类或同一包内的类访问的访问权限为protected。此外,题库还包括其他重要知识点,如异常处理、多线程、集合框架等,全面覆盖Java编程的核心内容。 ... [详细]
  • 掌握PHP框架开发与应用的核心知识点:构建高效PHP框架所需的技术与能力综述
    掌握PHP框架开发与应用的核心知识点对于构建高效PHP框架至关重要。本文综述了开发PHP框架所需的关键技术和能力,包括但不限于对PHP语言的深入理解、设计模式的应用、数据库操作、安全性措施以及性能优化等方面。对于初学者而言,熟悉主流框架如Laravel、Symfony等的实际应用场景,有助于更好地理解和掌握自定义框架开发的精髓。 ... [详细]
  • 进程(Process)是指计算机中程序对特定数据集的一次运行活动,是系统资源分配与调度的核心单元,构成了操作系统架构的基础。在早期以进程为中心的计算机体系结构中,进程被视为程序的执行实例,其状态和控制信息通过任务描述符(task_struct)进行管理和维护。本文将深入探讨进程的概念及其关键数据结构task_struct,解析其在操作系统中的作用和实现机制。 ... [详细]
  • Ceph API微服务实现RBD块设备的高效创建与安全删除
    本文旨在实现Ceph块存储中RBD块设备的高效创建与安全删除功能。开发环境为CentOS 7,使用 IntelliJ IDEA 进行开发。首先介绍了 librbd 的基本概念及其在 Ceph 中的作用,随后详细描述了项目 Gradle 配置的优化过程,确保了开发环境的稳定性和兼容性。通过这一系列步骤,我们成功实现了 RBD 块设备的快速创建与安全删除,提升了系统的整体性能和可靠性。 ... [详细]
  • 如何在Ubuntu系统中直接使用Snap安装软件
    Canonical与Opera Software近日宣布,基于Chromium的Opera浏览器现已作为Snap包提供给Ubuntu用户,显著提升了在Linux操作系统上的安装便捷性和兼容性。通过Snap,用户可以在Ubuntu系统中轻松安装和更新Opera浏览器,享受更流畅的浏览体验。此外,Snap的容器化特性还确保了应用的安全性和稳定性,为用户提供更加可靠的软件环境。 ... [详细]
  • 2019年后蚂蚁集团与拼多多面试经验详述与深度剖析
    2019年后蚂蚁集团与拼多多面试经验详述与深度剖析 ... [详细]
  • Jedis接口分类详解与应用指南
    本文详细解析了Jedis接口的分类及其应用指南,重点介绍了字符串数据类型(String)的接口功能。作为Redis中最基本的数据存储形式,字符串类型支持多种操作,如设置、获取和更新键值对等,适用于广泛的应用场景。 ... [详细]
  • 本文详细介绍了HDFS的基础知识及其数据读写机制。首先,文章阐述了HDFS的架构,包括其核心组件及其角色和功能。特别地,对NameNode进行了深入解析,指出其主要负责在内存中存储元数据、目录结构以及文件块的映射关系,并通过持久化方案确保数据的可靠性和高可用性。此外,还探讨了DataNode的角色及其在数据存储和读取过程中的关键作用。 ... [详细]
  • Java 8 引入了 Stream API,这一新特性极大地增强了集合数据的处理能力。通过 Stream API,开发者可以更加高效、简洁地进行集合数据的遍历、过滤和转换操作。本文将详细解析 Stream API 的核心概念和常见用法,帮助读者更好地理解和应用这一强大的工具。 ... [详细]
  • 探索 PHP 8.0 的重大更新:轻松获取年度月份数据
    PHP 8.0 引入了多项重要更新,包括增强的类型系统、全新的 JIT 编译器以及联合类型等特性。这些改进不仅提升了性能,还简化了开发流程。本文将重点介绍如何利用 PHP 8.0 的新功能轻松获取年度和月份数据,为开发者提供更高效、更简洁的解决方案。 ... [详细]
  • 在软件开发领域,“池”技术被广泛应用,如数据库连接池、线程池等。本文重点探讨Java中的线程池ThreadPoolExecutor,通过详细解析其内部机制,帮助开发者理解如何高效利用线程池管理任务执行。线程池不仅能够显著减少系统资源的消耗,提高响应速度,还能通过合理的配置,如饱和策略,确保在高负载情况下系统的稳定性和可靠性。文章还将结合实际案例,展示线程池在不同应用场景下的具体实现与优化技巧。 ... [详细]
  • 如何解决Windows 7系统中电脑公司遇到的文件夹访问权限问题
    如何解决Windows 7系统中电脑公司遇到的文件夹访问权限问题 ... [详细]
  • Django新手指南:第三步——构建你的首个项目
    在本教程中,我们将引导你完成创建第一个Django应用的步骤。通过实际操作,你将逐步了解Django框架的核心概念和基本功能。从项目结构到视图和模板的实现,我们将详细介绍每个环节,帮助你快速上手并构建出一个功能完整的Web应用。 ... [详细]
  • 为何Serverless将成为未来十年的主导技术领域?
    为何Serverless将成为未来十年的主导技术领域? ... [详细]
  • Docker网络基础探讨了如何通过高效的技术手段实现跨主机容器间的顺畅通信与访问。本文深入分析了Docker网络架构,特别是其在多主机环境下的应用,为Go语言开发者提供了宝贵的实践指导和理论支持。 ... [详细]
author-avatar
厄祖EQ
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有