热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java并发入门原理教程(四)

CAS操作原理和线程新特性

CAS 操作原理

 

1. 前言

本节内容主要是对 CAS 操作原理进行讲解,由于 CAS 涉及到了并发编程包的使用,本节课程只对 CAS 的原理问题进行讲解,有助于同学后续对并发编程工具使用的学习。本节具体内容点如下:

  • 了解 CAS 的概念,这是本节内容的基础知识;

  • 了解 CAS 诞生的背景,能够更好地理解 CAS,这是本节的基础知识;

  • 了解 CAS 操作诞生的意义,这也是 CAS 操作的作用所在;

  • 了解 CAS 的操作原理,这也是本节内容的核心知识点,其他知识点都是围绕这一知识点展开的;

  • 了解 CAS 中常见的 ABA 问题,这是本节内容的重点。

 

2. 什么是 CAS

概念:CAS 是 CompareAndSwap 的简称,是一种用于在多线程环境下实现同步功能的机制。

从字面上理解就是比较并更新。简单来说,从某一内存上取值 V,和预期值 A 进行比较,如果内存值 V 和预期值 A 的结果相等,那么我们就把新值 B 更新到内存,如果不相等,那么就重复上述操作直到成功为止。

 

3. CAS 诞生的背景

synchronized 时代:在多线程中为了保持数据的准确性,避免多个线程同时操作某个变量,很多情况下利用关键字 synchronized 实现同步锁。

使用 synchronized 关键字可以使操作的线程排队等待运行,可以说是一种悲观策略,认为线程会修改数据,所以开始就把持有锁的线程锁住,其他线程只能是挂起状态,等待锁的释放,所以同步锁带来了效率问题。

synchronized 时代效率问题:在线程执行的时候,获得锁的线程在运行,其他被挂起的线程只能等待着持有锁的线程释放锁才有机会运行,在效率上都浪费在等待上。

在很多的线程切换的时候,由于有同步锁,就要涉及到锁的释放,加锁,这又是一个很大的时间开销。

volatile 时代:与锁(阻塞机制)的方式相比有一种更有效地方法,非阻塞机制,同步锁带来了线程执行时候之间的阻塞,而这种非阻塞机制在多个线程竞争同一个数据的时候不会发生阻塞的情况,这样在时间上就可以节省出很多的时间。

我们会想到用 volatile,使用 volatile 不会造成阻塞,volatile 保证了线程之间的内存可见性和程序执行的有序性可以说已经很好的解决了上面的问题。

volatile 时代原子操作问题:一个很重要的问题就是,volatile 不能保证原子性,对于复合操作,例如 i++ 这样的程序包含三个原子操作:取值,增加,赋值。

 

4. CAS 操作诞生的意义

意义:从上边 CAS 操作诞生的背景所说的,CAS(Compare And Swap 比较和交换)解决了 volatile 不能保证原子性的问题。从而 CAS 操作即能够解决锁的效率问题,也能够保证操作的原子性。

Tips:在 JDK1.5 新增的 java.util.concurrent (JUC java 并发工具包) 就是建立在 CAS 之上的。相比于 synchronized 这种堵塞算法, CAS 是非堵塞算法的一种常见实现。所以 JUC 在性能上有了很大的提升。

 

5. CAS 操作原理

CAS 主要包含三个操作数,内存位置 V,进行比较的原值 A,和新值 B。

当位置 V 的值与 A 相等时,CAS 才会通过原子方式用新值 B 来更新 V,否则不会进行任何操作。无论位置 V 的值是否等于 A,都将返回 V 原有的值。

上面说到了同步锁是一种悲观策略,CAS 是一种乐观策略,每次都开放自己,不用担心其他线程会修改变量等数据,如果其他线程修改了数据,那么 CAS 会检测到并利用算法重新计算。

CAS 也是同时允许一个线程修改变量,其他的线程试图修改都将失败,但是相比于同步锁,CAS 对于失败的线程不会将他们挂起,他们下次仍可以参加竞争,这也就是非阻塞机制的特点。

 

6. ABA 问题

ABA 问题描述

  • 假设有两个线程,线程 1 和线程 2,线程 1 工作时间需要 10 秒,线程 2 工作需要 2 秒;

  • 主内存值为 A,第一轮线程 1 和线程 2 都把 A 拿到自己的工作内存;

  • 第 2 秒,线程 2 开始执行,线程 2 工作完成把 A 改成了 B ;

  • 第 4 秒,线程 2 把 B 又改成了 A,然后就线程 2 进入休眠状态;

  • 第 10 秒,线程 1 工作完成,看到期望为 A 真实值也是 A 认为没有人动过,其实 A 已经经过了修改,只不过又改了回去,然后线程 1 进行 CAS 操作。

ABA 问题解决:为了解决这个问题,在每次进行操作的时候加上一个版本号或者是时间戳即可。

 

7. 小结

本节内容的核心知识点即了解 CAS 的操作原理,其他知识点都是围绕这一知识点展开的,对于 CAS 中所引发的 ABA 问题以及该问题的解决方式为本节内容重点。需要对这两点进行着重掌握。

Unsafe 类方法介绍

 

1. 前言

本节内容主要是对 Unsafe 类方法进行介绍,JDK jar 包中的 Unsafe 类提供了硬件级别的原子性操作,Unsafe 类中的方法都是 native 方法,它们使用 JNI 的方式访问本地 C++实现库。

本节我们来了解一下 Unsafe 提供的几个主要的方法进行介绍。为我们后续对 Unsafe 方法的使用奠定良好的基础。

 

2. 方法介绍

方法作用
objectFieldOffset(Field)返回指定的变量在所属类中的内存偏移地址,该偏移地址仅仅在该 UnSafe 函数中访问指定字段时使用。
arrayBaseOffset(Class)获取取数组中第一个元系的地址。
arrayIndexScale(Class)获取数组中一个元素占用的字节。
compareAndSwapLong(Object,long,long,long)比较对象 obj 中偏移量为 offset 的变量的值是否与 expect 相等,相等则使用 update 值更新,然后返回 true,否则返回 false。
longgetLongvolatile(Object,long)获取对象 obj 中偏移量为 offset 的变量对应 volatile 语义的值。
void putLongvolatile(Object,long,long)设置 obj 对象中 offset 偏移的类型为 long 的 field 的值为 value, 支持 volatile 语义。
putOrderedLong(Object,long,long)设置 obj 对象中 offset 偏移地址对应的 long 型 field 的值为 value。这是一个有延迟的 putLongvolatile 方法,并且不保证值修改对其他线程立刻可见。只有在变量使用 volatile 修饰并且预计会被意外修改时才使用该方法。
unpark(Object)唤醒调用 park 后阻塞的线程。

 

3. park 方法介绍

方法描述void park(booleanisAbsolute,longtime):阻塞当前线程,其中参数 isAbsolute 等于 false 且 time 等于 0 表示一直阻塞。

方法解读:time 大于 0 表示等待指定的 time 后阻塞线程会被唤醒,这个 time 是个相对值,是个增量值,也就是相对当前时间累加 time 后当前线程就会被唤醒。如果 isAbsolute 等于 true,并且 time 大于 0,则表示阻塞的线程到指定的时间点后会被唤醒。

这里 time 是个绝对时间,是将某个时间点换算为 ms 后的值。另外,当其他线程调用了当前阻塞线程的 interrupt 方法而中断了当前线程时,当前线程也会返回,而当其他线程调用了 unPark 方法并且把当前线程作为参数时当前线程也会返回。

 

4. JDK8 新增的函数

方法作用
getAndSetLong(Object, long, long)获取对象 obj 中偏移量为 offset 的变量 volaile 语义的当前值,并设置变量 volaile 语义的值为 update。
getAndAddLong(Object,long,long)方法获取 object 中偏移量为 offset 的 volatile 变量的当前值,并设置变量值为原始值加上 addValue

 

5. 小结

本节的核心内容即 Usafe 方法的了解,为下边讲解 Unsafe 方法的使用奠定一个良好的基础。

Unsafe 类的使用

 

1. 前言

本节对 Unsafe 类的使用进行讲解,上一小节内容已经对 Unsafe 类的常用方法有了大体的概括,本节主要内容点如下:Unsafe 类的简介,对 UnSafe 类有一个整体的认识;Unsafe 类的创建以及创建过程中避免的异常机制,这是开始使用 UnSafe 类的前提;了解 Unsafe 类操作对象属性方法的使用,这是本节内容的重点之一;了解 Unsafe 操作数组元素方法的使用,也是本节内容的重点之一。

本节内容意在了解并掌握 Unsafe 类的常用方法的使用。

 

2. Unsafe 类简介

Unsafe 类是 Java 整个并发包底层实现的核心,它具有像 C++ 的指针一样直接操作内存的能力,而这也就意味着其越过了 JVM 的限制。

Unsafe 类有如下的特点:

  • Unsafe 不受 JVM 管理,也就无法被自动 GC,需要手动 GC,容易出现内存泄漏;

  • Unsafe 的大部分方法中必须提供原始地址 (内存地址) 和被替换对象的地址,偏移量需自行计算,一旦出现问题必然是 JVM 崩溃级别的异常,会导致整个应用程序直接 crash;

  • 直接操作内存,也意味着其速度更快,在高并发的条件之下能够很好地提高效率。

 

3. Unsafe 类的创建

Unsafe 类是不可以通过 new 关键字直接创建的。Unsafe 类的构造函数是私有的,而对外提供的静态方法 Unsafe.getUnsafe () 又对调用者的 ClassLoader 有限制 ,如果这个方法的调用者不是由 Boot ClassLoader 加载的,则会报错。

实例:通过 main 方法进行调用,报错。



import sun.misc.Unsafe;

import sun.misc.VM;

import sun.reflect.Reflection;

public class DemoTest {

   public static void main(String[] args) {

       getUnsafe();

  }

   public static Unsafe getUnsafe() {

       Class unsafeClass = Reflection.getCallerClass();

       if (!VM.isSystemDomainLoader(unsafeClass.getClassLoader())) {

           throw new SecurityException("Unsafe");

      } else {

           return null;

      }

  }

}

运行结果


Exception in thread "main" java.lang.InternalError: CallerSensitive annotation expected at frame 1

at sun.reflect.Reflection.getCallerClass(Native Method)

at leeCode.DemoTest.getUnsafe(DemoTest.java:12)

at leeCode.DemoTest.main(DemoTest.java:9)

报错原因: Java 源码中由开发者自定义的类都是由 Appliaction ClassLoader 加载的,也就是说 main 函数所依赖的 jar 包都是 ClassLoader 加载的,所以会报错。

所以正常情况下我们无法直接使用 Unsafe ,如果需要使用它,则需要利用反射

实例:通过反射,成功加载 Unsafe 类。


import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class DemoTest {

   public static void main(String[] args) {

       Unsafe unsafe = getUnsafe();

       System.out.println("Unsafe 加载成功:"+unsafe);

  }

   public static Unsafe getUnsafe() {

       Unsafe unsafe = null;

       try {

           Field field = Unsafe.class.getDeclaredField("theUnsafe");

           field.setAccessible(true);

           unsafe = (Unsafe) field.get(null);

      } catch (Exception e) {

           e.printStackTrace();

      }

       return unsafe;

  }

}

结果验证


Unsafe 加载成功:sun.misc.Unsafe@677327b6

代码块1

总结:Unsafe 类的加载必须使用反射进行,否则会报错。

 

4. Unsafe 类操作对象属性

操作对象属性的常用方法有

  • public native Object getObject(Object o, long offset):获取一个 Java 对象中偏移地址为 offset 的属性的值,此方法可以突破修饰符的限制,类似的方法有 getInt ()、getDouble () 等,同理还有 putObject () 方法;

  • public native Object getObjectVolatile(Object o, long offset):强制从主存中获取目标对象指定偏移量的属性值,类似的方法有 getIntVolatile (),getDoubleVolatile () 等,同理还有 putObjectVolatile ();

  • public native void putOrderedObject(Object o, long offset, Object x):设置目标对象中偏移地址 offset 对应的对象类型属性的值为指定值。这是一个有序或者有延迟的 putObjectVolatile () 方法,并且不保证值的改变被其他线程立即看到。只有在属性被 volatile 修饰并且期望被修改的时候使用才会生效,类似的方法有 putOrderedInt () 和 putOrderedLong ();

  • public native long objectFieldOffset(Field f):返回给定的非静态属性在它的类的存储分配中的位置 (偏移地址),然后可根据偏移地址直接对属性进行修改,可突破属性的访问修饰符限制。

实例


import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class DemoTest {

   private String name;

   public static void main(String[] args) {

       Unsafe unsafe = getUnsafe();

       try {

           DemoTest directMemory = (DemoTest) unsafe.allocateInstance(DemoTest.class);

           //获取name属性

           long nameOffset = unsafe.objectFieldOffset(DemoTest.class.getDeclaredField("name"));

           //设置name属性

           unsafe.putObject(directMemory, nameOffset, "并发编程");

           System.out.println("属性设置成功:"+ directMemory.getName());

      } catch (Exception e) {

           e.printStackTrace();

      }

  }

   public static Unsafe getUnsafe() {

       Unsafe unsafe = null;

       try {

           Field field = Unsafe.class.getDeclaredField("theUnsafe");

           field.setAccessible(true);

           unsafe = (Unsafe) field.get(null);

      } catch (Exception e) {

           e.printStackTrace();

      }

       return unsafe;

  }

   public void setName(String name) {

       this.name = name;

  }

   public String getName() {

       return name;

  }

}

结果验证:


属性设置成功:并发编程

代码块1

 

5. Unsafe 操作数组元素

Unsafe 操作数组元素主要有如下两个方法:

  • public native int arrayBaseOffset(Class arrayClass):返回数组类型的第一个元素的偏移地址 (基础偏移地址);

  • public native int arrayIndexScale(Class arrayClass):返回数组中元素与元素之间的偏移地址的增量,配合 arrayBaseOffset () 使用就可以定位到任何一个元素的地址。

实例


import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class DemoTest {

   private static String[] names = {"多线程", "Java", "并发编程"};

   public static void main(String[] args) {

       Unsafe unsafe = getUnsafe();

       try {

           Class a = String[].class;

           int base = unsafe.arrayBaseOffset(a);

           int scale = unsafe.arrayIndexScale(a);

           // base + i * scale 即为字符串数组下标 i 在对象的内存中的偏移地址

           System.out.println(unsafe.getObject(names, (long) base + 2 * scale));

      } catch (Exception e) {

           e.printStackTrace();

      }

  }

   public static Unsafe getUnsafe() {

       Unsafe unsafe = null;

       try {

           Field field = Unsafe.class.getDeclaredField("theUnsafe");

           field.setAccessible(true);

           unsafe = (Unsafe) field.get(null);

      } catch (Exception e) {

           e.printStackTrace();

      }

       return unsafe;

  }

}

结果验证


并发编程

代码块1

通过对数组的元素的地址进行内存偏移,最后得到的结果为最后一个元素,并发编程。base + 2 * scale 表示字符串数组下标 i 在对象的内存中的偏移地址,偏移两个元素,得到最后一个元素。

 

6. 小结

本节内容主要对 Unsafe 类的常用方法的使用进行介绍,使学习者能够在使用 Unsafe 类操作对象和数组时,能够快速的使用课程中提供的实例思路。其实 Unsafe 类还可以操作内存地址,操作 CAS,对于初学者来说就比较晦涩了。

此处对操作对象和操作数组常用的方法的讲解,是本节的核心知识,掌握 Unsafe 类的常用方法的使用,非常重要。

9、JAVA 多线程锁介绍

 

1. 前言

本节内容主要是对 Java 多线程锁进行介绍,是对锁的一个全方位的概述,为我们对后续深入学习不同的锁的使用方法奠定一个良好的基础。本节内容的知识点如下:

  • 乐观锁与悲观锁的概念,以及两种锁之间的区别,这是并发编程中经常涉及到的知识点,这是本节课程的核心知识点,是热度很高的必须要掌握的知识,后续还会有专门的小节进行详细讲解;

  • 公平锁与非公平锁的介绍,并发编程中经常涉及到的知识点,需要掌握其概念与区别;

  • 独占锁与共享锁的介绍,并发编程中经常涉及到的知识点,需要掌握其概念与区别;

  • 自旋锁的介绍,对于自旋锁,了解其概念即可。

 

2. 悲观锁

定义:悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改(很悲观),所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态。

悲观锁的实现:开发中常见的悲观锁实现往往依靠数据库提供的锁机制,即在数据库中,在对数据记录操作前给记录加排它锁。如果获取锁失败,则说明数据正在被其他线程修改,当前线程则等待或者抛出异常。如果获取锁成功,则对记录进行操作,然后提交事务后释放排它锁。

实例:Java 中的 synchronized 关键字就是一种悲观锁,一个线程在操作时,其他的线程必须等待,直到锁被释放才可进入方法进行执行,保证了线程和数据的安全性,同一时间,只能有一条线程进入执行。

我们用一段熟悉的代码进行悲观锁的展示。



public class Student {

   private String name;

   public synchronized String getName() {

       return name;

  }

   public synchronized void setName(String name) {

       this.name = name;

  }

}

代码分析 :假设有 3 条线程,如下图,线程 3 正在操作 Student 类,此时线程 1 和线程 2 必须要等待线程 3 执行完毕方可进入,这就是悲观锁。

 

3. 乐观锁

定义:乐观锁是相对悲观锁来说的,它认为数据在一般情况下不会造成冲突,所以在访问记录前不会加排它锁,而是在进行数据提交更新的时候,才会正式对数据冲突与否进行检测。

乐观锁的实现:依旧拿数据库的锁进行比较介绍,乐观锁并不会使用数据库提供的锁机制, 一般在表中添加 version 宇段或者使用业务状态来实现。 乐观锁直到提交时才锁定,所以不会产生任何死锁。

Java 中的乐观锁:我们之前所学习的 CAS 原理即是乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

Tips:我们这里所说的对于乐观锁,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败。注意失败两字,失败意味着有操作,而悲观锁是等待,意味着不能同时操作。

 

4. 悲观锁机制存在的问题

  • 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题;

  • 一个线程持有锁会导致其它所有需要此锁的线程挂起;

  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

对比于悲观锁的这些问题,另一个更加有效的锁就是乐观锁。其实乐观锁就是:每次不加锁而是假设没有并发冲突而去完成某项操作,如果因为并发冲突失败就重试,直到成功为止。

 

5. 公平锁与非公平锁

分类:根据线程获取锁的抢占机制,锁可以分为公平锁和非公平锁。

公平锁:表示线程获取锁的顺序是按照线程请求锁的时间早晚来决定的,也就是最早请求锁的线程将最早获取到锁。

非公平锁:非公平锁则在运行时闯入,不遵循先到先执行的规则。

ReentrantLock:ReentrantLock 提供了公平和非公平锁的实现。我们本节只做介绍,后续章节会对 ReentrantLock 进行深入的讲解。

 

6. 独占锁与共享锁

分类:根据锁只能被单个线程持有还是能被多个线程共同持有,锁可以分为独占锁和共享锁。

独占锁:保证任何时候都只有一个线程能得到锁,ReentrantLock 就是以独占锁方式实现的。

共享锁:则可以同时由多个线程持有,例如 ReadWriteLock 读写锁,它允许一个资源可以被多线程同时进行读操作。

独占锁是一种悲观锁,由于每次访问资源都先加上互斥锁,这限制了并发性,因为读操作并不会影响数据的一致性,而独占锁只允许在同一时间由一个线程读取数据,其他线程必须等待当前线程释放锁才能进行读取。

共享锁则是一种乐观锁,它放宽了加锁的条件,允许多个线程同时进行读操作。

 

7. 自旋锁

由于 Java 中的线程是与操作系统中的线程相互对应的,所以当一个线程在获取锁(比如独占锁)失败后,会被切换到内核状态而被挂起。

当该线程获取到锁时又需要将其切换到内核状态而唤醒该线程。而从用户状态切换到内核状态的开销是比较大的,在一定程度上会影响并发性能。

自旋锁:自旋锁则是当前线程在获取锁时,如果发现锁已经被其他线程占有,它不马上阻塞自己,在不放弃 CPU 使用权的情况下,多次尝试获取(默认次数是 10,可以使用-XX:PreBlockSpinsh 参数设置该值)。

很有可能在后面几次尝试中其他线程己经释放了锁。如果尝试指定的次数后仍没有获取到锁则当前线程才会被阻塞挂起。由此看来自旋锁是使用 CPU 时间换取线程阻塞与调度的开销,但是很有可能这些 CPU 时间白白浪费了。

 

8. 小结

本节内容为锁的基础概念介绍,其中对于悲观锁和乐观锁的讲解,为本节核心知识点,因为这两种锁在数据库的领域也是应用十分广泛。对于本节提到的具体的锁实现,如 ReentrantLock ,我们在后续章节中会有详细的讲解。

本节内容为基础介绍,掌握本节内容的基础上,能够更好地学习后续的章节知识。




推荐阅读
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • 为什么多数程序员难以成为架构师?
    探讨80%的程序员为何难以晋升为架构师,涉及技术深度、经验积累和综合能力等方面。本文将详细解析Tomcat的配置和服务组件,帮助读者理解其内部机制。 ... [详细]
  • 本文详细介绍了 Java 网站开发的相关资源和步骤,包括常用网站、开发环境和框架选择。 ... [详细]
  • MySQL初级篇——字符串、日期时间、流程控制函数的相关应用
    文章目录:1.字符串函数2.日期时间函数2.1获取日期时间2.2日期与时间戳的转换2.3获取年月日、时分秒、星期数、天数等函数2.4时间和秒钟的转换2. ... [详细]
  • 机器学习算法:SVM(支持向量机)
    SVM算法(SupportVectorMachine,支持向量机)的核心思想有2点:1、如果数据线性可分,那么基于最大间隔的方式来确定超平面,以确保全局最优, ... [详细]
  • 本文节选自《NLTK基础教程——用NLTK和Python库构建机器学习应用》一书的第1章第1.2节,作者Nitin Hardeniya。本文将带领读者快速了解Python的基础知识,为后续的机器学习应用打下坚实的基础。 ... [详细]
  • 三角测量计算三维坐标的代码_双目三维重建——层次化重建思考
    双目三维重建——层次化重建思考FesianXu2020.7.22atANTFINANCIALintern前言本文是笔者阅读[1]第10章内容的笔记,本文从宏观的角度阐 ... [详细]
  • 本文详细介绍了如何在 Linux 系统上安装 JDK 1.8、MySQL 和 Redis,并提供了相应的环境配置和验证步骤。 ... [详细]
  • oracle c3p0 dword 60,web_day10 dbcp c3p0 dbutils
    createdatabasemydbcharactersetutf8;alertdatabasemydbcharactersetutf8;1.自定义连接池为了不去经常创建连接和释放 ... [详细]
  • 在JavaWeb开发中,文件上传是一个常见的需求。无论是通过表单还是其他方式上传文件,都必须使用POST请求。前端部分通常采用HTML表单来实现文件选择和提交功能。后端则利用Apache Commons FileUpload库来处理上传的文件,该库提供了强大的文件解析和存储能力,能够高效地处理各种文件类型。此外,为了提高系统的安全性和稳定性,还需要对上传文件的大小、格式等进行严格的校验和限制。 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 在配置Nginx的SSL证书后,虽然HTTPS访问能够正常工作,但HTTP请求却会遇到400错误。本文详细解析了这一问题,并提供了Nginx配置的具体示例。此外,还深入探讨了DNS服务器证书、SSL证书的申请与安装流程,以及域名注册、查询方法和CDN加速技术的应用,帮助读者全面了解相关技术细节。 ... [详细]
  • 在安装 iOS 开发所需的 CocoaPods 时,用户可能会遇到多种问题。其中一个常见问题是,在执行 `pod setup` 命令后,系统无法连接到 GitHub 以更新 CocoaPods/Specs 仓库。这可能是由于网络连接不稳定、GitHub 服务器暂时不可用或本地配置错误等原因导致。为解决此问题,建议检查网络连接、确保 GitHub API 限制未被触发,并验证本地配置文件是否正确。 ... [详细]
  • 线程能否先以安全方式获取对象,再进行非安全发布? ... [详细]
  • Amoeba 通过优化 MySQL 的读写分离功能显著提升了数据库性能。作为一款基于 MySQL 协议的代理工具,Amoeba 能够高效地处理应用程序的请求,并根据预设的规则将 SQL 请求智能地分配到不同的数据库实例,从而实现负载均衡和高可用性。该方案不仅提高了系统的并发处理能力,还有效减少了主数据库的负担,确保了数据的一致性和可靠性。 ... [详细]
author-avatar
追麾
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有