文章目录
- Java 的并发基础知识
- 1. 创建线程
- 2. 同步方法和同步代码块
- 3. 线程安全的容器
- 4. volatile 关键字
- 5. Lock 和 Condition 接口
- Java 多线程编程的基本框架
- 1. 创建和启动线程
- 2. 线程的状态转换
- 3. 线程安全
- 4. 死锁
- Java 并发编程的高级技术
- 1. 线程池
- 2. 并发集合
- 3. 原子类
- 4. 锁
Java 的并发基础知识
了解什么是线程、进程、多线程并发等概念,掌握 Java 中的 synchronized 和 volatile 关键字以及 Lock 和 Condition 接口等重要的并发工具。
下面是 Java 中并发基础知识的代码示例:
1. 创建线程
Java 中有两种方式来创建线程,一种是继承 Thread 类,另一种是实现 Runnable 接口。下面是使用继承 Thread 类的方式来创建线程的示例代码:
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread is running...");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
2. 同步方法和同步代码块
在多个线程同时访问共享资源时,需要对这些资源进行同步,以避免数据不一致或者数据污染等问题。Java 中提供了 synchronized 关键字来实现同步,可以用于修饰方法和代码块。下面是使用 synchronized 修饰方法和代码块的示例代码:
public class Counter {
private int count = 0;
// 使用 synchronized 修饰方法
public synchronized void increment() {
count++;
}
// 使用 synchronized 修饰代码块
public void decrement() {
synchronized (this) {
count--;
}
}
public int getCount() {
return count;
}
}
3. 线程安全的容器
Java 中提供了一些线程安全的容器类,比如 Vector、Hashtable 和 Collections.synchronizedXXX 等。这些容器类可以保证在多个线程同时访问容器时,不会出现数据不一致或者数据污染等问题。下面是使用 Vector 来实现线程安全的并发队列的示例代码:
public class ConcurrentQueue {
private Vector<String> queue = new Vector<>();
public void enqueue(String element) {
queue.add(element);
}
public String dequeue() {
if (queue.isEmpty()) {
return null;
} else {
return queue.remove(0);
}
}
}
4. volatile 关键字
volatile 关键字用于标记变量为“易失性变量”,即该变量可能被多个线程同时修改和访问,从而确保多个线程之间对该变量的可见性。下面是使用 volatile 修饰变量的示例代码:
public class MyThread extends Thread {
private volatile boolean running = true;
public void run() {
while (running) {
// do something here
}
}
public void shutdown() {
running = false;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
// 让主线程休眠一段时间后,停止子线程
Thread.sleep(1000);
thread.shutdown();
}
}
5. Lock 和 Condition 接口
Lock 和 Condition 接口是 Java 中提供的可重入锁和条件变量,用于实现更灵活的线程同步控制。下面是使用 Lock 和 Condition 接口来实现线程同步的示例代码:
public class Buffer {
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
private List<Integer> list = new ArrayList<>();
private int capacity;
public Buffer(int capacity) {
this.capacity = capacity;
}
public void put(int element) throws InterruptedException {
lock.lock();
try {
while (list.size() == capacity) {
notFull.await();
}
list.add(element);
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public int get() throws InterruptedException {
lock.lock();
try {
while (list.size() == 0) {
notEmpty.await();
}
int element = list.remove(0);
notFull.signalAll();
return element;
} finally {
lock.unlock();
}
}
}
以上是 Java 中并发基础知识的相关示例代码。需要注意的是,在实际开发中,多线程程序的正确性和稳定性非常重要。在编写多线程程序时,应该尽可能地避免竞态条件、死锁、饥饿和活锁等问题,并且合理地控制线程的数量和优先级,以保证多线程程序的正确运行。
此外,在实际开发中,可以使用一些辅助工具来帮助我们调试和排查多线程程序中的问题,比如 Java VisualVM、jstack、jmap、jcmd 等。
总之,学习 Java 的并发基础知识只是掌握多线程编程的第一步,需要不断地实践和探索,提高自己的多线程编程能力,才能写出高效、健壮的应用程序。
Java 多线程编程的基本框架
包括创建和启动线程、线程的状态转换、线程安全和死锁等基本概念。
下面是使用 Java 多线程编程基本框架的示例代码:
1. 创建和启动线程
Java 中有两种方式来创建线程,一种是继承 Thread 类,另一种是实现 Runnable 接口。下面是使用继承 Thread 类的方式来创建和启动线程的示例代码:
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread is running...");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
2. 线程的状态转换
Java 中的线程有以下几种状态,可以通过 getState() 方法获取线程的状态:
- NEW:新建状态,表示线程已经被创建但还没有调用 start() 方法;
- RUNNABLE:运行状态,表示线程正在执行或者等待 CPU 时间片;
- BLOCKED:阻塞状态,表示线程正在等待某个事件发生,如输入输出操作或 synchronized 代码块的监视器锁;
- WAITING:等待状态,表示线程正在等待另一个线程通知或唤醒它;
- TIMED_WAITING:计时等待状态,表示线程正在执行 sleep()、wait(long) 或 join(long) 方法;
- TERMINATED:死亡状态,表示线程已经完成了它的任务,或者发生了异常而导致线程终止。
下面是使用 getState() 方法获取线程状态的示例代码:
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread is running...");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
System.out.println(thread.getState()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE
Thread.sleep(1000);
System.out.println(thread.getState()); // TERMINATED
}
}
3. 线程安全
在多个线程同时访问共享资源时,可能会出现数据不一致或者数据污染等问题。Java 中提供了 synchronized 关键字来实现同步,可以用于修饰方法和代码块。使用 synchronized 可以确保多个线程不会同时执行同一个方法或代码块,从而保证了线程安全。下面是使用 synchronized 实现线程安全的计数器的示例代码:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
public synchronized int getCount() {
return count;
}
}
4. 死锁
死锁是指两个或多个线程都在等待对方释放资源,永久阻塞的情况。在多线程编程中,为了避免死锁问题,应该尽量避免线程间的循环依赖和资源竞争。下面是模拟死锁的示例代码:
public class DeadLockDemo {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread1 acquired lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("Thread1 acquired lock2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread2 acquired lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("Thread2 acquired lock1");
}
}
});
thread1.start();
thread2.start();
}
}
以上是使用 Java 多线程编程基本框架的相关示例代码。需要注意的是在实际开发中,需要谨慎地使用多线程编程,特别是对于涉及到共享资源的场景。以下是一些注意事项:
- 避免竞态条件
竞态条件是指多个线程访问共享资源时,由于执行顺序不确定而导致结果不确定的情况。可以通过 synchronized 关键字或者 Lock 接口来解决竞态条件问题。
- 避免死锁
死锁是指两个或多个线程都在等待对方释放资源,永久阻塞的情况。可以通过避免循环依赖和资源竞争来避免死锁问题。
- 谨慎使用 Thread.sleep()
Thread.sleep() 方法会使当前线程休眠一段时间,但它并不会释放对象监视器锁。因此,在多线程编程中,应该谨慎使用 Thread.sleep() 方法,避免出现死锁等问题。
- 使用 Executor 框架
Executor 是一个用于管理和执行线程的框架,它提供了更加灵活的线程池机制和任务队列,能够有效地管理和调度线程,避免线程数量过多或过少的问题。
- 使用 volatile 关键字
volatile 关键字可以保证变量在多个线程之间的可见性和有序性,可以避免由于线程之间不同步而导致的数据不一致问题。但是,它并不能保证原子性和互斥性,需要慎重使用。
总之,在多线程编程中,需要特别注意线程安全、死锁、性能等问题,并采用合适的方式来解决这些问题,才能写出高效、健壮的应用程序。
Java 并发编程的高级技术
如线程池、线程同步、阻塞队列、Callable 和 Future 等。
Java 并发编程的高级技术包括线程池、并发集合、原子类和锁等。下面对这些技术进行详细解释,并给出相应的示例代码:
1. 线程池
线程池是一个用于管理和调度线程的工具,它可以有效地控制线程的数量和优先级,提高多线程程序的性能和稳定性。在 Java 中,线程池主要有以下几种类型:
- FixedThreadPool:固定大小的线程池,适合处理执行时间较短的任务;
- CachedThreadPool:无限大小的线程池,适合处理执行时间较长的任务;
- ScheduledThreadPool:固定大小的线程池,适合周期性地执行任务;
- SingleThreadExecutor:单个线程的线程池,适合按顺序执行任务。
下面是使用 FixedThreadPool 创建线程池并执行任务的示例代码:
public class MyTask implements Runnable {
private String name;
public MyTask(String name) {
this.name = name;
}
public void run() {
System.out.println(name + " is running...");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
for (int i = 0; i < 5; i++) {
executor.execute(new MyTask("Task" + i));
}
executor.shutdown();
while (!executor.isTerminated()) {
Thread.sleep(1000);
}
System.out.println("All tasks have been executed.");
}
}
2. 并发集合
并发集合是一种线程安全的容器,可以在多个线程同时访问时保证数据的一致性和正确性。Java 中提供了一些并发集合类,包括 ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentLinkedQueue 等。下面是使用 ConcurrentHashMap 实现线程安全的计数器的示例代码:
public class Counter {
private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
public void increment(String key) {
map.compute(key, (k, v) -> v == null ? 1 : v + 1);
}
public void decrement(String key) {
map.compute(key, (k, v) -> v == null ? -1 : v - 1);
}
public int getCount(String key) {
return map.getOrDefault(key, 0);
}
}
3. 原子类
原子类是一种线程安全的、不可分割的操作,可以保证对数值型变量的读取和修改操作都是原子性的。Java 中提供了一些原子类,包括 AtomicInteger、AtomicLong、AtomicBoolean 等。下面是使用 AtomicInteger 实现线程安全的计数器的示例代码:
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public void decrement() {
count.decrementAndGet();
}
public int getCount() {
return count.get();
}
}
4. 锁
锁是一种用于控制多个线程对共享资源的访问的机制,可以保证同一时间只有一个线程可以访问共享资源。Java 中提供了 synchronized 关键字和 Lock 接口两种实现锁的方式。下面是使用 synchronized 实现锁的示例代码:
public class Counter {
private int count = 0;
private Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public void decrement() {
synchronized (lock) {
count--;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
以上是 Java 并发编程的高级技术及相关示例代码。在实际开发中,需要根据具体需求选择合适的技术来提高程序的性能和稳定性,并注意线程安全、死锁等问题。以下是一些注意事项:
- 避免锁竞争
在多线程编程中,锁的性能占据了很大一部分,因此需要尽量避免锁竞争。可以使用无锁算法、分段锁、读写锁等方式来减少锁竞争。
- 减少上下文切换
上下文切换是指操作系统将当前执行状态保存下来,然后切换到另一个线程或进程的执行状态的过程,会消耗额外的时间和资源。为了减少上下文切换,可以采用合适的线程数量、调整任务大小等方式。
- 谨慎使用 ThreadLocal
ThreadLocal 是一种线程本地变量,每个线程都有自己的副本,可以避免多线程访问时出现数据共享和污染的问题。但是,如果使用不当,会导致内存泄露等问题。
- 使用 ForkJoin 框架
ForkJoin 是一个用于并行计算的框架,它采用工作窃取算法来解决任务负载不均衡的问题,可以很好地提高程序的并发性能。
总之,在 Java 并发编程的高级技术中,需要注意锁竞争、上下文切换、ThreadLocal 的使用和并发集合的选择等问题,采用合适的技术来解决这些问题。