广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >RustAtomicsandLocks并发基础理解
  • 410
分享到

RustAtomicsandLocks并发基础理解

Rust并发基础RustAtomicsLocks 2023-02-27 11:02:37 410人浏览 独家记忆
摘要

目录Rust 中的线程线程作用域所有权共享借用和数据竞争内部可变rust 中的线程安全 Send 和 Sync线程阻塞和唤醒Rust 中的线程 在 Rust 中,线程是轻量级的执行单

Rust 中的线程

在 Rust 中,线程是轻量级的执行单元,可以并行执行多个任务。Rust 中的线程由标准库提供的 std::thread 模块支持,使用线程需要在程序中引入该模块。可以使用 std::thread::spawn() 函数创建一个新线程,该函数需要传递一个闭包作为线程的执行体。闭包中的代码将在新线程中执行,从而实现了并发执行。例如:

use std::thread;
fn main() {
    // 创建一个新线程
    let handle = thread::spawn(|| {
        // 在新线程中执行的代码
        println!("Hello from a new thread!");
    });
    // 等待新线程执行完毕
    handle.join().unwrap();
    // 主线程中的代码
    println!("Hello from the main thread!");
}

上面的代码创建了一个新线程,并在新线程中打印了一条消息。在主线程中,调用了 handle.join() 方法等待新线程执行完毕。在新线程执行完毕后,程序会继续执行主线程中的代码。

需要注意的是,Rust 的线程是“无法共享堆栈”的。也就是说,每个线程都有自己的堆栈,不能直接共享数据。如果需要在线程之间共享数据,可以使用 Rust 的线程安全原语,例如 Mutex、Arc 等。

线程作用域

在 Rust 中,std::thread::scope 是一个函数,它允许在当前作用域中创建一个新的线程作用域。在这个作用域中创建的线程将会在作用域结束时自动结束,从而避免了手动调用 join() 方法的麻烦。

std::thread::scope 函数需要传递一个闭包,该闭包中定义了线程的执行体。与 std::thread::spawn 不同的是,该闭包中可以访问其父作用域中的变量。

下面是一个简单的例子,展示了如何使用 std::thread::scope

use std::thread;
fn main() {
    let mut vec = vec![1, 2, 3];
    thread::scope(|s| {
        s.spawn(|_| {
            vec.push(4);
        });
    });
    println!("{:?}", vec);
}

在这个例子中,我们使用 thread::scope 创建了一个新的线程作用域。在这个作用域中,我们创建了一个新的线程,并在其中向 vec 向量中添加了一个新元素。由于线程作用域在闭包执行完毕时自动结束,因此在 println! 语句中打印出的 vec 向量中并没有包含新添加的元素。

需要注意的是,在使用 thread::scope 创建线程时,闭包的参数类型必须是 &mut std::thread::Scope,而不是 &mut 闭包中所访问的变量的类型。这是因为 thread::scope 函数需要传递一个可变引用,以便在作用域结束时正确释放线程的资源。

所有权共享

在 Rust 中,所有权共享是一种允许多个变量同时拥有同一值的所有权的方式。这种方式被称为“所有权共享”,因为它允许多个变量共享对同一值的所有权。这是 Rust 的一项重要特性,可以帮助避免内存泄漏和数据竞争等问题。

在 Rust 中,有三种方式可以实现所有权共享:静态变量(Statics)、内存泄漏(Leaking)和引用计数(Reference Counting)。

  • 静态变量(Statics)

静态变量是指在程序运行期间一直存在的变量。在 Rust 中,可以使用 static 关键字来定义静态变量。静态变量在程序运行期间只会被初始化一次,且只有一个实例,所以多个变量可以共享对同一静态变量的所有权。

以下是一个示例:

static mut COUNTER: i32 = 0;
fn main() {
    unsafe {
        COUNTER += 1;
        println!("Counter: {}", COUNTER);
    }
}

在这个例子中,我们定义了一个名为 COUNTER 的静态变量,并使用 static mut 来表示它是一个可变的静态变量。然后,在 main 函数中,我们通过 unsafe 代码块来访问 COUNTER 变量,并将其加一。需要注意的是,在 Rust 中,访问静态变量是不安全的操作,所以必须使用 unsafe 代码块来进行访问。

  • 内存泄漏(Leaking)

内存泄漏是指在程序运行期间分配的内存没有被释放的情况。在 Rust 中,可以使用 Box::leak 方法来实现内存泄漏。Box::leak 方法会返回一个指向堆上分配的值的指针,但不会释放这个值的内存。这样,多个变量就可以共享对同一堆分配的值的所有权。

以下是一个示例:

use std::mem::forget;
fn main() {
    let value = Box::new("Hello, world!".to_string());
    let pointer = Box::leak(value);
    let reference1 = &*pointer;
    let reference2 = &*pointer;
    forget(pointer);
    println!("{}", reference1);
    println!("{}", reference2);
}

在这个例子中,我们使用 Box::new 创建一个新的堆分配的值,并将其赋值给 value 变量。然后,我们使用 Box::leak 方法来讲 value 的所有权泄漏到堆上,并返回一个指向堆上分配的值的指针。接着,我们使用 &* 来将指针解引用,并将其赋值给 reference1reference2 变量。最后,我们使用 std::mem::forget 函数来避免释放

  • 引用计数

引用计数是一种在 Rust 中实现所有权共享的方式,它允许多个变量共享对同一值的所有权。在 Rust 中,引用计数使用 Rc<T>(“引用计数”)类型来实现。Rc<T> 类型允许多个变量共享对同一值的所有权,但是不能在运行时进行修改,因为 Rc<T> 类型不支持内部可变性。

以下是一个示例:

use std::rc::Rc;
fn main() {
    let value = Rc::new("Hello, world!".to_string());
    let reference1 = value.clone();
    let reference2 = value.clone();
    println!("{}", reference1);
    println!("{}", reference2);
}

在这个例子中,我们使用 Rc::new 创建一个新的 Rc<String> 类型的值,并将其赋值给 value 变量。然后,我们使用 value.clone() 方法来创建 value 的两个引用,并将它们分别赋值给 reference1reference2 变量。最后,我们打印 reference1reference2 变量,以显示它们都引用了同一个值。

需要注意的是,Rc<T> 类型只能用于单线程环境,因为它不是线程安全的。如果需要在多线程环境下实现引用计数,可以使用 Arc<T>(“原子引用计数”)类型。Arc<T> 类型是 Rc<T> 的线程安全版本,它使用原子操作来实现引用计数。

借用和数据竞争

在 Rust 中,借用是一种通过引用来访问值而不获取其所有权的方式。借用是 Rust 中非常重要的概念,因为它可以帮助避免数据竞争的问题。

数据竞争指的是多个线程同时访问同一个变量,且至少有一个线程正在写入该变量。如果没有采取适当的同步措施,数据竞争会导致未定义的行为,例如程序崩溃或产生意外的结果。

在 Rust 中,编译器使用所有权和借用规则来防止数据竞争。具体来说,编译器会检查每个引用的生命周期,以确保在引用仍然有效的情况下进行访问。如果编译器发现了潜在的数据竞争问题,它会在编译时发出错误。

以下是一个简单的例子,说明如何使用借用来避免数据竞争问题:

use std::thread;
fn main() {
    let mut data = vec![1, 2, 3];
    let handle1 = thread::spawn(move || {
        let reference = &data;
        println!("Thread 1: {:?}", reference);
    });
    let handle2 = thread::spawn(move || {
        let reference = &data;
        println!("Thread 2: {:?}", reference);
    });
    handle1.join().unwrap();
    handle2.join().unwrap();
}

在这个例子中,我们创建了一个可变的 Vec<i32> 类型的值,并将其赋值给 data 变量。然后,我们在两个线程中使用 thread::spawn 方法,每个线程都获取对 data 的共享引用,并打印该引用。由于我们使用了共享引用,所以不会发生数据竞争问题。

需要注意的是,如果我们尝试将 data 的可变引用传递给两个线程中的一个或多个线程,编译器将会在编译时发出错误,因为这可能会导致数据竞争。在这种情况下,我们可以使用 Mutex<T>RwLock<T>Cell<T> 等同步原语来避免数据竞争。

内部可变

在 Rust 中,内部可变性是指在拥有不可变引用的同时,可以修改被引用的值。Rust 提供了一些内部可变性的实现方式,包括 Cell<T>RefCell<T> 类型。

Cell<T> 类型提供了一种在不可变引用的情况下,修改其所持有的值的方法。它通过在不可变引用中封装值,并使用 getset 方法来实现内部可变性。以下是一个示例:

use std::cell::Cell;
fn main() {
    let number = Cell::new(42);
    let reference = &number;
    let value = reference.get();
    number.set(value + 1);
    println!("The new value is: {}", reference.get());
}

在这个例子中,我们创建了一个 Cell<i32> 类型的值,并将其赋值给 number 变量。然后,我们获取了一个 &Cell<i32> 类型的不可变引用,并通过 get 方法获取了 number 所持有的值。接着,我们通过 set 方法来修改 number 所持有的值。最后,我们打印了 number 所持有的新值。

RefCell<T> 类型提供了一种更灵活的内部可变性实现方式。它通过在可变和不可变引用中封装值,并使用 borrowborrow_mut 方法来实现内部可变性。以下是一个示例:

use std::cell::RefCell;
fn main() {
    let number = RefCell::new(42);
    let reference1 = &number.borrow();
    let reference2 = &number.borrow();
    let mut reference3 = number.borrow_mut();
    *reference3 += 1;
    println!("The new value is: {:?}", number.borrow());
}

在这个例子中,我们创建了一个 RefCell<i32> 类型的值,并将其赋值给 number 变量。然后,我们获取了两个不可变引用,并通过 borrow_mut 方法获取了一个可变引用。接着,我们通过可变引用来修改 number 所持有的值。最后,我们打印了 number 所持有的新值。

需要注意的是,Cell<T>RefCell<T> 类型都不是线程安全的。如果需要在多线程环境下使用内部可变性,可以使用 Mutex<T>RwLock<T> 等同步原语。 在 Rust 中,为了保证多线程并发访问共享数据的安全性,可以使用同步原语,例如 Mutex 和 RwLock。

Mutex 是一种互斥,它允许只有一个线程访问被保护的共享数据。在 Rust 中,可以通过标准库中的 std::sync::Mutex 类型来实现 Mutex。以下是一个示例:

use std::sync::Mutex;
fn main() {
    let data = Mutex::new(0);
    let mut handles = vec![];
    for _ in 0..10 {
        let handle = std::thread::spawn(move || {
            let mut data = data.lock().unwrap();
            *data += 1;
        });
        handles.push(handle);
    }
    for handle in handles {
        handle.join().unwrap();
    }
    println!("Result: {}", *data.lock().unwrap());
}

在这个例子中,我们创建了一个 Mutex<i32> 类型的值,并将其赋值给 data 变量。然后,我们创建了 10 个线程,并在每个线程中获取 data 的可变引用,并通过加 1 的方式修改其所持有的值。最后,我们等待所有线程执行完毕,并打印 data 所持有的值。

RwLock 是一种读写锁,它允许多个线程同时读取共享数据,但只允许一个线程写入共享数据。在 Rust 中,可以通过标准库中的 std::sync::RwLock 类型来实现 RwLock。以下是一个示例:

use std::sync::RwLock;
fn main() {
    let data = RwLock::new(0);
    let mut handles = vec![];
    for _ in 0..10 {
        let handle = std::thread::spawn(move || {
            let data = data.read().unwrap();
            println!("Thread {}: read data {}", std::thread::current().id(), *data);
        });
        handles.push(handle);
    }
    let handle = std::thread::spawn(move || {
        let mut data = data.write().unwrap();
        *data += 1;
        println!("Thread {}: write data {}", std::thread::current().id(), *data);
    });
    handles.push(handle);
    for handle in handles {
        handle.join().unwrap();
    }
}

在这个例子中,我们创建了一个 RwLock<i32> 类型的值,并将其赋值给 data 变量。然后,我们创建了 10 个线程,并在每个线程中获取 data 的不可变引用,并打印其所持有的值。接着,我们创建了一个新的线程,并获取 data 的可变引用,并通过加 1 的方式修改其所持有的值。最后,我们等待所有线程执行完毕。

需要注意的是,在使用 Mutex 和 RwLock 时,需要使用 unwrap() 方法来处理锁的获取失败的情况。如果在获取锁时发生了死锁,程序会阻塞在该位置。因此,在使用锁时需要注意避免死锁的情况。 在 Rust 中,为了保证线程安全和内存安全,访问可变的共享数据通常需要使用同步原语,例如 Mutex 和 RwLock,或者通过引用计数等方式。然而,有时候我们需要在 Rust 中使用一些类似 C 的指针操作,这时就需要使用 unsafe 关键字来打破 Rust 的内存安全限制。

Rust 标准库中提供了一个 Cell<T> 类型,它允许在不使用 Mutex 或 RwLock 的情况下,在多个线程之间共享可变数据。但是,由于 Cell<T> 不是线程安全的,因此在多线程环境下使用它会导致数据竞争和内存安全问题。

为了解决这个问题,Rust 提供了 UnsafeCell<T> 类型,它可以安全地包含不可变类型 T 或可变类型 &mut T,并且可以用于实现线程安全的数据结构。使用 UnsafeCell<T> 需要使用 unsafe 关键字,并遵循 Rust 的内存安全规则。

以下是一个示例,演示如何使用 UnsafeCell<T> 来实现一个线程安全的计数器:

rustCopy code
use std::sync::atomic::{AtomicUsize, Ordering};
use std::cell::UnsafeCell;
use std::thread;
struct Counter {
    count: UnsafeCell<usize>,
    sync: AtomicUsize,
}
impl Counter {
    fn new() -> Counter {
        Counter {
            count: UnsafeCell::new(0),
            sync: AtomicUsize::new(0),
        }
    }
    fn inc(&self) {
        let old_sync = self.sync.load(Ordering::SeqCst);
        let new_sync = old_sync.wrapping_add(1);
        while self.sync.compare_and_swap(old_sync, new_sync, Ordering::SeqCst) != old_sync {
            old_sync = self.sync.load(Ordering::SeqCst);
            new_sync = old_sync.wrapping_add(1);
        }
        let count = unsafe { &mut *self.count.get() };
        *count += 1;
        self.sync.fetch_add(1, Ordering::SeqCst);
    }
    fn get(&self) -> usize {
        let old_sync = self.sync.load(Ordering::SeqCst);
        let new_sync = old_sync.wrapping_add(1);
        while self.sync.compare_and_swap(old_sync, new_sync, Ordering::SeqCst) != old_sync {
            old_sync = self.sync.load(Ordering::SeqCst);
            new_sync = old_sync.wrapping_add(1);
        }
        let count = unsafe { &*self.count.get() };
        let result = *count;
        self.sync.fetch_add(1, Ordering::SeqCst);
        result
    }
}
fn main() {
    let counter = Counter::new();
    let mut handles = vec![];
    for _ in 0..10 {
        let handle = thread::spawn(move || {
            for _ in 0..10000 {
                counter.inc();
            }
        });
        handles.push(handle);
    }
    for handle in handles {
        handle.join().unwrap();
    }
    println!("Result: {}", counter.get());
}

在这个例子中,我们创建了一个 Counter 结构体,它包含了一个 UnsafeCell<usize> 类型的字段 count,以及一个 AtomicUsize 类型的字段 syncUnsafeCell<T> 类型的作用是允许对其内部的值进行修改,即使是在不可变引用的情况下。AtomicUsize 是一个原子类型,它可以在多个线程之间安全地共享一个整数值。

Counter 结构体实现了 inc 方法和 get 方法,分别用于增加计数器的值和获取计数器的值。这些方法通过对 sync 字段进行 CAS 操作来实现线程安全,以避免竞争条件。同时,它们也使用了 UnsafeCell 来获取计数器的可变引用。 需要注意的是,使用 UnsafeCell 时需要遵循 Rust 的内存安全规则。如果你不小心在多个线程之间访问了同一个 UnsafeCell,那么就可能会出现数据竞争和其它的内存安全问题。因此,一定要谨慎地使用 UnsafeCell,确保正确地处理内存安全问题。

rust 中的线程安全 Send 和 Sync

在 Rust 中,线程安全是一个很重要的概念,因为 Rust 的并发模型是基于线程的。为了确保线程安全,Rust 提供了两个 trait,分别是 SendSync

Send trait 表示一个类型是可以安全地在线程间传递的。具体来说,实现了 Send trait 的类型可以被移动到另一个线程中执行,而不会出现数据竞争或其它的线程安全问题。对于基本类型(如整数、浮点数、指针等)和大多数标准库类型,都是 Send 的。对于自定义类型,只要它的所有成员都是 Send 的,那么它也是 Send 的。

Sync trait 表示一个类型在多个线程间可以安全地共享访问。具体来说,实现了 Sync trait 的类型可以被多个线程同时访问,而不会出现数据竞争或其它的线程安全问题。对于大多数标准库类型,都是 Sync 的。对于自定义类型,只要它的所有成员都是 Sync 的,那么它也是 Sync 的。

需要注意的是,SendSync trait 是自动实现的,也就是说,如果一个类型的所有成员都是 SendSync 的,那么它就是 SendSync 的,无需手动实现这两个 trait。不过,如果一个类型包含了非 Send 或非 Sync 的成员,那么它就无法自动实现这两个 trait,需要手动实现。

  • 在实际使用中,SendSync trait 通常用于泛型类型约束和函数签名中,以确保类型的线程安全性。比如,一个函数的参数必须是 Send 类型的,才能被跨线程调用;一个泛型类型的参数必须是 Sync 类型的,才能被多个线程同时访问。

线程阻塞和唤醒

在 Rust 中,线程的阻塞和唤醒是通过操作系统提供的原语来实现的。操作系统提供了一些系统调用(如 pthread_cond_waitpthread_cond_signal 等),可以让线程进入睡眠状态,并在条件满足时被唤醒。这些系统调用通常被封装在 Rust 的标准库中,以便于使用。

除了操作系统提供的原语外,Rust 还提供了一个名为 parking_lot 的库,用于实现线程的阻塞和唤醒。parking_lot 库提供了两种阻塞和唤醒线程的机制,分别是 MutexCondvar

Mutex 是一种常见的同步原语,用于保护共享资源的访问。当一个线程想要获取一个被 Mutex 保护的资源时,如果该资源已经被其它线程占用,那么该线程就会被阻塞,直到该资源被释放。Mutex 的实现通常使用了操作系统提供的原语,以确保线程的阻塞和唤醒是正确的。

Condvar 是一种条件变量,用于在特定条件满足时唤醒等待的线程。当一个线程想要等待一个条件变量时,它会先获取一个 Mutex,然后调用 wait 方法等待条件变量。如果条件变量未满足,该线程就会被阻塞。当条件变量满足时,另一个线程会调用 notify_onenotify_all 方法来唤醒等待的线程。Condvar 的实现通常也使用了操作系统提供的原语,以确保线程的阻塞和唤醒是正确的。

需要注意的是,parking_lot 库虽然是 Rust 标准库的一部分,但它并不是操作系统提供的原语,而是使用了自己的算法实现的。因此,虽然 parking_lot 库提供了比标准库更高效的同步机制,但在某些特定的场景下,操作系统提供的原语可能会更加适合。在选择同步机制时,需要根据实际的需求和性能要求来进行选择。

以上就是Rust Atomics and Locks并发基础理解的详细内容,更多关于Rust 并发基础的资料请关注编程网其它相关文章!

--结束END--

本文标题: RustAtomicsandLocks并发基础理解

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

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

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

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

下载Word文档
猜你喜欢
  • RustAtomicsandLocks并发基础理解
    目录Rust 中的线程线程作用域所有权共享借用和数据竞争内部可变rust 中的线程安全 Send 和 Sync线程阻塞和唤醒Rust 中的线程 在 Rust 中,线程是轻量级的执行单...
    99+
    2023-02-27
    Rust 并发基础 Rust Atomics Locks
  • 详解Java并发编程基础之volatile
    目录一、volatile的定义和实现原理1、Java并发模型采用的方式2、volatile的定义3、volatile的底层实现原理二、volatile的内存语义1、volatile的...
    99+
    2022-11-12
  • 6. `Java` 并发基础之`ReentrantReadLock`
    前言:随着多线程程序的普及,线程同步的问题变得越来越常见。Java中提供了多种同步机制来确保线程安全,其中之一就是ReentrantLock。ReentrantLock是Java中比较常用的一种同...
    99+
    2023-09-21
    java 开发语言
  • Go语言基础并发channel
    这篇文章主要讲解了“Go语言基础并发channel”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Go语言基础并发channel”吧!为什么需要channel...
    99+
    2022-10-19
  • C++中的并行与并发基础与使用详解
    目录1. 并行基础2. 互斥量与临界区3. 期物4. 条件变量5. 原子操作与内存模型5.1原子操作5.2一致性模型5.3内存顺序1. 并行基础 std::thread 用于创建一个...
    99+
    2023-02-14
    C++并行与并发 C++并行编程 C++并发编程
  • python基础之并发编程(一)
    目录一、进程(Process)二、线程(Thread)三、并发编程解决方案:四、多线程实现 (两种)1、第一种 函数方法2、第二种 类方法包装五、守护线程与子线程1、线程在分法有:2...
    99+
    2022-11-12
  • python基础之并发编程(二)
    目录一、多进程的实现方法一方法二:二、使用进程的优缺点1、优点2、缺点三、进程的通信1、Queue 实现进程间通信2、Pipe 实现进程间通信(一边发送send(obj),一边接收(...
    99+
    2022-11-12
  • python基础之并发编程(三)
    目录一、协程定义和作用1、使用协程的优点2、使用协程的缺点二、Greenlet 的使用三、Gevent的使用四、async io 异步 IO1、asyncio中的task的使用五、总...
    99+
    2022-11-12
  • mybatis 基础理解
    1、主要的类1.1 SqlSessionFactoryBuilder     用于创建SqlSessionFactory,要通过配置文件也可以是代码。   ...
    99+
    2022-10-18
  • (1)并发编程实现的基础
    # 多道程序系统 原理,缺点 #允许多个程序同时进入内存并运行。即同时把多个程序放入内存,并允许它们交替在CPU中运行,它们共享系统中的各种硬、软件资源。当一道程序因I/O请求而暂停运行时,CPU便立即转去运行另一道程序。 # 多道技术中...
    99+
    2023-01-31
    基础
  • python基础之什么是并发编程
    本篇内容介绍了“python基础之什么是并发编程”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、协程定义和作用协程(coroutine),...
    99+
    2023-06-25
  • Go语言并发编程基础上下文概念详解
    目录前言1 Go 中的 Context2 Context 接口3 Context Tree4 创建上下文4.1 上下文创建函数4.2 Context 使用规范4.3 Context ...
    99+
    2022-11-11
  • PosegreSQL基础回顾(第 13 章 并发控制)
    来源:http://www.postgres.cn/docs/11/ 13.2.1. 读已提交隔离级别 读已提交是PostgreSQL中的默认隔离级别。 当一个事务运行使用这个隔离级别时, 一个查询(没有FOR UPDATE/...
    99+
    2017-06-28
    PosegreSQL基础回顾(第 13 并发控制)
  • Java并发基础常见面试题(总结)
    本篇文章给大家总结了一下Java并发基础常见面试题,有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1. 什么是线程和进程1.1. 何为进程进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程...
    99+
    2015-12-07
    java面试题 spring Java
  • Java基础之并发相关知识总结
    目录一、Java并发是什么?二、怎么做?三、分工四、同步五、互斥六、总结一、Java并发是什么? 用学术定义来说就是 并发:同一时间段,多个任务都在执行 (单位时间内不一定同时执行...
    99+
    2022-11-12
  • Android开发基础 事件处理
    Android事件处理 1-基于监听的事件处理 基于监听的事件处理,其实就是为UI组件绑定事件监听器。 在事件监听处理模型中,主要涉及以下三个对...
    99+
    2022-06-06
    事件 android开发 Android
  • JVM中Java和Scala并发性基础是什么
    本篇文章给大家分享的是有关JVM中Java和Scala并发性基础是什么,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。处理器速度数十年来一直持续快速发展,并在世纪交替之际走到了终...
    99+
    2023-06-17
  • Java并发编程的基础知识有哪些
    今天小编给大家分享一下Java并发编程的基础知识有哪些的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。 01、简介首...
    99+
    2023-06-16
  • java开发RocketMQ消息中间件原理基础详解
    RocketMQ 是什么 Github 上关于 RocketMQ 的介绍: RcoketMQ 是一款低延迟、高可靠、可伸缩、易于使用的消息中间件。具有以下特性: 支持发布/...
    99+
    2022-11-12
  • Java高并发编程基础之如何使用AQS
    本篇内容主要讲解“Java高并发编程基础之如何使用AQS”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java高并发编程基础之如何使用AQS”吧! 引言曾经有一道比较比较经典的面试题“...
    99+
    2023-06-15
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作