在此之前,我们先要明白什么是并发?为什么要并发编程?
在计算机中,同一时刻,只能有一条指令,在一个CPU上执行 后面的指令必须等到前面指令执行完才能执行,就是串行。在早年CPU核心数还少的时候倒是没什么。但是现如今,CPU性能(核心数和频率)已经不同往昔,为了充分利用CPU性能,我们引入并发。就好比银行只有5个窗口,有5个人要办事,就可以一起处理,第六个人到来才需要排队。
Java如何进行并发编程
在 Java 中进行并发编程可以利用语言和库提供的特性,如线程、线程池、同步机制等。Java 为并发编程提供了许多有用的工具和库,包括基本的 Thread
类、并发集合、锁、条件变量等。在 Java 7 及以上版本中,java.util.concurrent
包还提供了更高层次的并发编程工具,包括线程池、并发队列、异步任务(Future
和 CompletableFuture
)等。
1. 开辟新线程
Thread
类:Java 提供了Thread
类来创建和管理线程。你可以通过继承Thread
类或实现Runnable
接口来定义线程。
public class MyThread extends Thread {@Overridepublic void run() {// 在这个方法中定义线程要执行的任务System.out.println("Thread is running");}public static void main(String[] args) {// 创建 MyThread 类的实例MyThread myThread = new MyThread();// 启动线程myThread.start();}
}
Runnable
接口:通过实现Runnable
接口,并将其传递给Thread
类的构造函数,来定义线程的执行逻辑。
public class MyRunnable implements Runnable {@Overridepublic void run() {// 在这个方法中定义线程要执行的任务System.out.println("Runnable is running");}public static void main(String[] args) {// 创建 MyRunnable 类的实例MyRunnable myRunnable = new MyRunnable();// 将 Runnable 对象传递给 Thread 构造函数Thread thread = new Thread(myRunnable);// 启动线程thread.start();}
}
2. 同步与锁
synchronized
关键字:用于在方法或代码块上加锁,确保在同一时间只有一个线程可以执行受保护的代码。Lock
接口:提供了synchronized
的替代方法,更灵活的锁定机制。ReadWriteLock
:提供了读写锁,用于优化读多写少的场景。
3. 线程池
ExecutorService
:用于管理线程池,可以通过Executors
类提供的方法来创建不同类型的线程池。ScheduledExecutorService
:用于安排定时或周期性的任务执行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class ConcurrencyDemo {public static void main(String[] args) {// 创建一个固定大小的线程池ExecutorService executor = Executors.newFixedThreadPool(3);// 提交任务给线程池Future<?> future1 = executor.submit(() -> {System.out.println("任务 1 开始");// 执行任务逻辑System.out.println("任务 1 结束");});Future<?> future2 = executor.submit(() -> {System.out.println("任务 2 开始");// 执行任务逻辑System.out.println("任务 2 结束");});// 等待任务执行完成try {future1.get(); // 阻塞直到任务 1 完成future2.get(); // 阻塞直到任务 2 完成} catch (Exception e) {e.printStackTrace();}// 关闭线程池executor.shutdown();}
}
创建了一个固定大小为 3 的线程池,然后提交了两个任务,并等待它们执行完毕。最后关闭线程池。
4. 并发集合
java.util.concurrent
包中的并发集合:例如ConcurrentHashMap
、CopyOnWriteArrayList
、ConcurrentLinkedQueue
等,适合在多线程环境下使用。
5. 异步任务与回调
Future
接口:代表异步计算的结果。可以通过ExecutorService
提供的方法提交任务,得到Future
对象。CompletableFuture
类:更高级的异步任务工具,提供链式回调、组合和异常处理等功能。
6. 其他并发工具
CountDownLatch
:用于线程同步,让线程等待直到某些操作完成。CyclicBarrier
:一个同步点,允许多个线程等待彼此到达一个状态。Semaphore
:用于控制资源的访问数量。
Go语言怎么并发
Go 语言以其强大的并发编程特性而闻名。Go 语言提供了一些基本概念和机制来处理并发,包括 goroutines、channels 和 select 语句。这些工具使得 Go 在处理并发任务时非常高效且易于编写。
1. Goroutine
- Goroutine 是 Go 语言中的轻量级线程。通过使用
go
关键字,可以在后台启动一个新的 goroutine 来执行任务。 - Goroutine 是非常轻量级的,可以同时启动大量 goroutine,而不会对系统资源产生很大的负担。
package mainimport ("fmt"
)func sayHello() {fmt.Println("Hello!")
}func main() {go sayHello() // 启动一个新的 goroutine 执行 sayHello 函数fmt.Println("Main goroutine")
}
2. Channels
- Channels 是 Go 语言中用于 goroutine 之间通信的工具。通过 channels,可以在不同的 goroutine 之间传递数据。
- 可以使用
make
函数创建 channels,并通过<-
运算符发送和接收数据。
package mainimport ("fmt"
)func producer(ch chan int) {for i := 0; i < 5; i++ {ch <- i // 发送数据到 channelfmt.Printf("Produced: %d\n", i)}close(ch) // 关闭 channel
}func consumer(ch chan int) {for value := range ch { // 从 channel 接收数据fmt.Printf("Consumed: %d\n", value)}
}func main() {ch := make(chan int) // 创建一个整型 channelgo producer(ch) // 启动生产者 goroutinego consumer(ch) // 启动消费者 goroutine// 让主程序等待 goroutine 结束// 可以使用 time.Sleep() 或 wait group 来实现
}
3. Select 语句
select
语句用于在多个 channel 上等待,并选择其中一个准备好的 channel 进行通信。select
语句类似于switch
语句,但它是针对 channels 的。
package mainimport ("fmt""time"
)func main() {ch1 := make(chan int)ch2 := make(chan int)go func() {time.Sleep(2 * time.Second)ch1 <- 1}()go func() {time.Sleep(1 * time.Second)ch2 <- 2}()select {case value := <-ch1:fmt.Printf("Received %d from ch1\n", value)case value := <-ch2:fmt.Printf("Received %d from ch2\n", value)}
}
select
语句等待两个 channel 之一发送数据,然后接收数据并执行相应的分支。
这些是 Go 语言中并发编程的基本概念和用法。通过组合这些特性,Go 语言可以处理复杂的并发任务。
Java的虚拟线程
Java 21 中引入了 虚拟线程(Virtual Threads) ,它是一种新的线程实现,旨在提高并发应用程序的性能和可扩展性。虚拟线程与传统线程不同,它们是一种轻量级的线程实现,可以在 Java 虚拟机(JVM)上更有效地处理大量并发任务。
以下是虚拟线程的主要特点和优势:
1. 轻量级:
- 虚拟线程是轻量级的,创建和销毁的开销很小,因此可以在应用程序中创建大量虚拟线程。
- 与传统线程相比,虚拟线程的资源占用较小,这使得在大量并发任务的场景下更加高效。
2. 阻塞友好:
- 虚拟线程可以友好地阻塞在 I/O 操作或其他同步操作上,而不会影响其他虚拟线程的执行。
- JVM 可以将阻塞的虚拟线程切换到其他可运行的虚拟线程,保持高效的并发执行。
3. 无缝集成:
- 虚拟线程与现有的 Java 代码无缝集成,这意味着开发者可以在现有的代码基础上直接使用虚拟线程。
- 开发者可以继续使用现有的并发 API,如
Thread
、ExecutorService
等,只需将其替换为虚拟线程的实现即可。
4. 简单的编程模型:
- 虚拟线程提供了一个更简单的编程模型,因为开发者可以像使用普通线程一样使用虚拟线程。
- 开发者可以更专注于业务逻辑,而不必担心底层线程管理的复杂性。
5. 高性能:
- 虚拟线程的调度和执行更高效,可以充分利用多核 CPU 的优势。
- 对于需要处理大量并发任务的应用程序,虚拟线程可以显著提高性能。
使用示例:
使用虚拟线程非常简单,只需在 Thread
类的实例化中指定 Thread.ofVirtual()
作为工厂方法即可:
public class VirtualThreadExample {public static void main(String[] args) {Runnable task = () -> {System.out.println("Virtual thread is running");};// 创建一个虚拟线程Thread virtualThread = Thread.ofVirtual().start(task);// 等待虚拟线程执行完毕try {virtualThread.join();} catch (InterruptedException e) {e.printStackTrace();}}
}
我们使用 Thread.ofVirtual()
创建了一个虚拟线程,并启动它来执行指定的任务。与传统线程的使用类似,但虚拟线程在性能和资源效率方面有更大的优势。
虚拟线程是 Java 21 的一个重要特性,为开发者提供了处理大量并发任务的强大工具。
往期推荐
Java与Go:字符串转IP地址
Java与Go:文件IO
Java vs. Go:时间函数
Java与Go:字符串方法
Java与Go:方法和接口
Java与Go:引用和指针
Java与Go:对象
Java与Go:Map
Java 与 Go:可变数组
Java 与 Go:数组