例子:你正在做饭,切菜、炒菜、煮饭,如果只能一个接一个完成,那就是串行;如果你可以同时切菜、炒菜(有两个锅),那就是并行;如果你用一个锅,先炒一会菜,然后煮饭,再炒菜,交替进行,那就是并发。
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
// 执行具体的线程任务
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
优点:
缺点:
public class RunnableTask implements Runnable {
@Override
public void run() {
System.out.println("Runnable running: " + Thread.currentThread().getName());
// 执行具体的线程任务
}
public static void main(String[] args) {
RunnableTask task = new RunnableTask();
Thread thread = new Thread(task, "MyRunnableThread");
thread.start(); // 启动线程
}
}
优点:
缺点:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableTask implements Callable {
@Override
public String call() throws Exception {
System.out.println("Callable running: " + Thread.currentThread().getName());
// 执行计算并返回结果
return "Result from callable";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableTask task = new CallableTask();
FutureTask futureTask = new FutureTask<>(task);
Thread thread = new Thread(futureTask, "MyCallableThread");
thread.start();
// 获取并打印线程执行结果
String result = futureTask.get();
System.out.println("Callable result: " + result);
}
}
优点:
缺点:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExample {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2); // 创建固定大小的线程池
executor.submit(() -> {
System.out.println("Runnable task running in thread pool: " + Thread.currentThread().getName());
// 执行具体的线程任务
});
executor.submit(new CallableTask()); // 提交 Callable 任务
// 关闭线程池
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
}
}
优点:
调用wait()方法,等待其他线程唤醒; 试图获取一个对象的同步锁,但该锁被其他线程持有; 调用sleep()或join()方法,或发出I/O请求; 其他任何导致线程暂停执行的原因。
因此,在现代Java编程中,强烈建议使用interrupt()方法而非stop()方法来关闭线程,以保证程序的稳定性和数据完整性。
public class MyClass {
public static synchronized void staticMethod() {
// 使用类对象锁
}
public synchronized void instanceMethod() {
// 使用实例对象锁
}
}
Java中的synchronized锁为了提高性能,会经历偏向锁、轻量级锁、重量级锁的升级过程。
例子
假设您经营一家只有一台收银机的小超市。为了管理顾客结账的秩序,您需要实施一种排队策略。随着超市业务的变化,您可能会调整这个策略以适应不同的情况。
Volatile是Java语言提供的一种用于修饰共享变量的关键字,它主要服务于多线程环境中的内存可见性和指令重排序控制。简单来说,volatile关键字具有以下两个核心作用:
1.保证可见性: 当一个线程修改了被volatile修饰的变量时,该更改会立即被刷新到主内存中,并且其他线程在访问该变量时,会看到最新的值,而不是从它们各自的工作内存(缓存)中获取过期的副本。这意味着使用volatile修饰的变量能够确保线程间的共享数据更新对所有线程都是立即可见的。
2.禁止指令重排序: volatile还能阻止编译器和处理器对被修饰变量的读写操作进行重排序优化。在没有volatile的情况下,为了提高性能,编译器和处理器可能会对指令进行重排,导致执行顺序与代码逻辑顺序不一致。而volatile变量的访问会按照程序的顺序语义来执行,确保多线程环境下的特定操作顺序得到保持,这对于依赖特定执行顺序的并发代码至关重要。
使用注意事项:
状态标志(flags),单例模式双重检查锁定(Double-Checked Locking),简单数据同步
理解volatile的原理需要结合Java内存模型(Java Memory Model, JMM)和硬件层面的内存交互机制。
Java内存模型(JMM)层面:
JMM定义了主内存(Main Memory)和工作内存(Working Memory)的概念。主内存是所有线程共享的存储区域,其中存放着所有实例变量、静态变量等共享数据。工作内存则是每个线程私有的,包含该线程使用的变量副本以及对主内存数据的缓存。线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,不能直接操作主内存。
当一个线程修改了volatile变量时,会发生如下过程:
硬件层面(缓存一致性协议):
在硬件层面,为了保证不同CPU核心之间缓存的一致性,现代处理器采用了诸如MESI(Modified, Exclusive, Shared, Invalid)协议这样的缓存一致性协议。当一个核心修改了volatile变量时,协议会确保:
此外,针对volatile变量的写操作,处理器还会在指令层面插入相应的内存屏障(Memory Barrier),以确保写操作的全局可见性和禁止指令重排。内存屏障是一种特殊的指令,可以确保某些内存操作的顺序性,并同步不同CPU核心的缓存视图。
综上所述,volatile关键字通过Java内存模型的规则以及硬件层面的缓存一致性协议与内存屏障,实现了对共享变量的可见性和有序性保证,为多线程编程提供了一种轻量级的同步机制。然而,需要注意的是,volatile并不能保证原子性,即对于复合操作(如递增、条件判断与更新等),仍然需要借助synchronized、Atomic类或其他锁机制来确保操作的原子性。
ThreadLocal是一个Java类,用于提供线程本地变量。它使得每个线程都拥有自己独立的变量副本,从而确保了在并发环境中,线程之间的变量操作互不影响。ThreadLocal主要用于解决以下几个问题:
ThreadLocal的核心结构是一个线程内部的ThreadLocalMap类,它是一个定制化的哈希表,用于存储ThreadLocal变量与它们对应的值。ThreadLocalMap的键是ThreadLocal对象本身,值则是用户存储的实际数据。
public class Thread implements Runnable {
...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
每个线程都有一个threadLocals属性,即一个ThreadLocalMap实例。当通过ThreadLocal的get()、set()或remove()方法操作变量时,实际上是在操作当前线程的ThreadLocalMap。
·强引用(Strong Reference):最常见的引用类型,只要强引用存在,垃圾收集器就不会回收被引用的对象。例如:
Object obj = new Object(); // obj是强引用
·软引用(Soft Reference):当系统内存不足时,即使存在软引用,也可能被垃圾收集器回收。主要用于实现内存敏感的缓存。例如:
SoftReference
·弱引用(Weak Reference):弱引用对象在垃圾收集器进行下一次回收时(无论内存是否充足),都会被回收。常用于实现弱键的映射关系。例如:
WeakReference
ThreadLocal中的key是弱引用,主要是为了避免内存泄漏。如果没有使用弱引用,ThreadLocalMap的key(即ThreadLocal实例)一旦被其他对象强引用,即使当前线程已经不再使用该ThreadLocal,由于key仍被引用,整个Entry(包括key和value)也不会被垃圾收集器回收。而value通常是由线程内部的逻辑所创建,如果线程生命周期很长,那么这些value就可能长期驻留内存,形成内存泄漏。
使用弱引用作为key,当ThreadLocal实例不再有其他强引用时,即使当前线程还在运行,key也可以被垃圾收集器回收。这样,对应的value由于失去了key的引用,也会在下一次垃圾回收时被清理,从而避免了内存泄漏。
ThreadLocalMap内部使用开放寻址法(Open Addressing)来解决Hash冲突,具体实现是线性探测(Linear Probing)。当插入一个新的Entry时,如果遇到Hash冲突(即索引位置已有Entry),则向后寻找下一个空闲位置,直到找到为止。这种策略简化了Entry的删除操作,因为不需要维护链表或其他复杂的冲突解决结构。
代码示例与介绍
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalExample {
// 定义一个ThreadLocal变量,用于存储线程的唯一标识
private static final ThreadLocal THREAD_ID = new ThreadLocal<>();
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
// 向线程池提交任务
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
// 设置当前线程的唯一标识(这放入的是你传进来的参数就是你想隔离啥就传啥)
THREAD_ID.set("Task-" + Thread.currentThread().getId());
// 使用ThreadLocal变量
doWork();
// 清理ThreadLocal变量,避免内存泄漏
THREAD_ID.remove();
});
}
executor.shutdown();
}
private static void doWork() {
String id = THREAD_ID.get(); // 获取当前线程的唯一标识(你在这用的时候就只有你这一个线程使用)
System.out.println("Thread " + id + " is doing some work.");
}
}
在这个示例中:
通过这个示例,可以看到ThreadLocal如何在多线程环境中为每个线程提供独立的变量副本,且在任务执行完毕后主动清理,以避免内存泄漏。