1. run()
VS start()
run()
方法:
run()
方法是java.lang.Runnable
接口中定义的一个方法。当一个类实现了Runnable
接口,并创建了一个线程对象时,你需要覆盖run()
方法来定义线程要执行的任务。run()
方法定义了线程的主体逻辑,当线程被启动后,run()
方法会被调用,线程会执行run()
方法中的代码。- 如果直接调用
run()
方法,那么线程的执行就相当于普通的方法调用,不会创建新的线程,而是在当前线程中执行run()
方法。
start()
方法:
start()
方法是java.lang.Thread
类中定义的一个方法。当你创建一个线程对象后,通过调用start()
方法来启动线程。- 调用
start()
方法后,会创建一个新的线程,并且自动调用线程对象的run()
方法来执行线程的主体逻辑。 start()
方法会在后台启动一个新线程,并让该线程执行run()
方法中的代码。
简而言之,run()
方法用于定义线程的任务逻辑,而start()
方法用于启动线程并执行run()
方法中的任务逻辑。直接调用run()
方法只是普通的方法调用,不会创建新线程;而调用start()
方法会创建新线程并执行其中的任务逻辑。
public class Test {
static class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
System.out.println("==================myThread.run():==================");
MyThread myThread = new MyThread();
myThread.run(); // 只有 run(),没有 start()
System.out.println("==================thread.run():==================");
Thread thread = new Thread(myThread);
thread.run();
System.out.println("==================thread.start():==================");
thread.start();
}
}
输出:
==================myThread.run():==================
main: 0
main: 1
main: 2
main: 3
main: 4
main: 5
main: 6
main: 7
main: 8
main: 9
==================thread.run():==================
main: 0
main: 1
main: 2
main: 3
main: 4
main: 5
main: 6
main: 7
main: 8
main: 9
==================thread.start():==================
Thread-0: 0
Thread-0: 1
Thread-0: 2
Thread-0: 3
Thread-0: 4
Thread-0: 5
Thread-0: 6
Thread-0: 7
Thread-0: 8
Thread-0: 9
进程已结束,退出代码 0
2. 程序
、进程
、线程
程序是静态的代码集合,进程是程序的执行实例,而线程是进程内部的执行单元。
进程是操作系统进行资源分配和调度的基本单位,而线程是操作系统进行(CPU)调度和执行的基本单位。
- 程序(Program):
- 程序是一组指令的集合,用于完成特定的任务或实现特定的功能。
- 程序可以是编程语言中的源代码,也可以是已编译或已解释的可执行文件。
- 在计算机中,程序通常被存储在磁盘上,并在需要时加载到内存中执行。
- 进程(Process):
- 进程是计算机中运行的一个程序的实例。
- 它是操作系统进行资源分配和调度的基本单位,包括内存空间、文件和设备的分配。
- 每个进程都有独立的内存空间,可以在其中执行程序代码和保存数据。
- 进程之间相互独立,通过进程间通信(IPC)来进行数据交换和协作。
- 线程(Thread):
- 线程是进程内的一个执行单元,它共享了进程的内存空间和资源。
- 一个进程可以包含多个线程,这些线程共享进程的上下文,但每个线程有自己的执行路径和栈。
- 线程可以看作是轻量级的进程,它可以更高效地完成并发任务,提高系统的响应速度和资源利用率。
- 多线程编程可以使程序更灵活、更高效,但也需要考虑线程同步和资源竞争等问题。
3. 单核多线程
VS 多核多线程
- 单核多线程(Single-Core Multi-Threading):
- 在单核处理器上,多线程可以通过时间分片的方式实现并发执行。
- 单核处理器通过在不同的线程之间快速切换来模拟并发执行,每个线程在一段时间内执行一小部分任务,然后切换到另一个线程。
- 单核多线程可以提高系统的响应速度和资源利用率,但由于线程共享单一的处理器核心,实际上并不能同时执行多个线程。
- 多核多线程(Multi-Core Multi-Threading):
- 多核处理器具有多个物理处理器核心,每个核心都可以独立执行指令。
- 在多核处理器上,可以通过同时在多个核心上执行多个线程来实现并行处理。
- 多核多线程可以实现真正的并行执行,每个核心都可以同时执行一个线程,从而加速整体计算速度。
- 多核多线程可以更好地利用硬件资源,提高系统的性能和吞吐量,特别是对于需要大量计算的任务或对并行处理有较高要求的应用程序来说,具有明显的优势。
4. 注意
- 线程就是独立的执行路径。
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,比如主线程,GC线程。
- main()称之为主线程,为系统的入口,用于执行整个程序。
- 在一个进程中,如果开辟了多个线程,线程的运行是由调度器(cpu)安排调度的,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
- 对同一份资源操作时mm会存在资源抢夺的问题,需要加入并发控制。
- 线程会带来额外的开销,如CPU调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
5. 线程状态以及他们之间的转换
-
在 Java 中,当一个线程在持有 synchronized 同步块的锁的情况下,从运行态切换到
WAITING
或TIMED_WAITING
状态时,会释放锁。 -
但Lock锁需要手动 unlock()。
public class TestThreadState {
public static void main(String[] args) throws InterruptedException {
//线程的六种状态
test1();
//线程状态间的状态转换:NEW->RUNNABLE->TIME_WAITING->RUNNABLE->TERMINATED
test2();
//线程状态间的状态转换:NEW->RUNNABLE->WAITING->RUNNABLE->TERMINATED
test3();
//线程状态间的状态转换:NEW->RUNNABLE->BLOCKED->RUNNABLE->TERMINATED
test4();
}
private static void test4() throws InterruptedException {
System.out.println("======线程状态间的状态转换NEW->RUNNABLE->BLOCKED->RUNNABLE->TERMINATED======");
//定义一个对象,用来加锁和解锁
AtomicBoolean obj2 = new AtomicBoolean(false);
//定义一个线程,先抢占了obj2对象的锁
new Thread(() -> {
synchronized (obj2) {
try {
//第一个线程要持有锁100毫秒
Thread.sleep(100);
//然后通过wait()方法进行等待状态,并释放obj2的对象锁
obj2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//定义目标线程,获取等待获取obj2的锁
Thread thread3 = new Thread(() -> {
System.out.println("2.执行thread.start()之后,线程的状态:" + Thread.currentThread().getState());
synchronized (obj2) {
try {
//thread3要持有对象锁100毫秒
Thread.sleep(100);
//然后通过notify()方法唤醒所有在ojb2上等待的线程继续执行后续操作
obj2.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("4.阻塞结束后,线程的状态:" + Thread.currentThread().getState());
});
//获取start()之前的状态
System.out.println("1.通过new初始化一个线程,但是还没有thread.start()之前,线程的状态:" + thread3.getState());
//启动线程
thread3.start();
//先等100毫秒
Thread.sleep(50);
//第一个线程释放锁至少需要100毫秒,所以在第50毫秒时,thread3正在因等待obj的对象锁而阻塞
System.out.println("3.因为等待锁而阻塞时,线程的状态:" + thread3.getState());
//再等300毫秒
Thread.sleep(300);
//两个线程的执行时间加上之前等待的50毫秒以供250毫秒,所以第300毫秒,所有的线程都已经执行完毕
System.out.println("5.线程执行完毕之后,线程的状态:" + thread3.getState());
}
private static void test3() throws InterruptedException {
System.out.println("======线程状态间的状态转换NEW->RUNNABLE->WAITING->RUNNABLE->TERMINATED======");
//定义一个对象,用来加锁和解锁
AtomicBoolean obj = new AtomicBoolean(false);
//定义一个内部线程
Thread thread1 = new Thread(() -> {
System.out.println("2.执行thread.start()之后,线程的状态:" + Thread.currentThread().getState());
synchronized (obj) {
try {
//thread1需要休眠100毫秒
Thread.sleep(100);
//thread1100毫秒之后,通过wait()方法释放obj对象是锁
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("4.被object.notify()方法唤醒之后,线程的状态:" + Thread.currentThread().getState());
});
//获取start()之前的状态
System.out.println("1.通过new初始化一个线程,但是还没有start()之前,线程的状态:" + thread1.getState());
//启动线程
thread1.start();
//main线程休眠150毫秒
Thread.sleep(150);
//因为thread1在第100毫秒进入wait等待状态,所以第150秒肯定可以获取其状态
System.out.println("3.执行object.wait()时,线程的状态:" + thread1.getState());
//声明另一个线程进行解锁
new Thread(() -> {
synchronized (obj) {
//唤醒等待的线程
obj.notify();
}
}).start();
//main线程休眠10毫秒等待thread1线程能够苏醒
Thread.sleep(10);
//获取thread1运行结束之后的状态
System.out.println("5.线程执行完毕之后,线程的状态:" + thread1.getState() + "\n");
}
private static void test2() throws InterruptedException {
System.out.println("======线程状态间的状态转换NEW->RUNNABLE->TIME_WAITING->RUNNABLE->TERMINATED======");
//定义一个内部线程
AtomicBoolean obj = new AtomicBoolean(true);
Thread thread = new Thread(() -> {
System.out.println("2.执行thread.start()之后,线程的状态:" + Thread.currentThread().getState()); // RUNNABLE
synchronized (obj) {
try {
//休眠100毫秒
// Thread.sleep(100);
obj.wait(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("4.执行Thread.sleep(long)完成之后,线程的状态:" + Thread.currentThread().getState()); // RUNNABLE
});
//获取start()之前的状态
System.out.println("1.通过new初始化一个线程,但是还没有start()之前,线程的状态:" + thread.getState()); // NEW
//启动线程
thread.start();
//休眠50毫秒
Thread.sleep(50);
//因为thread1需要休眠100毫秒,所以在第50毫秒,thread1处于sleep状态
System.out.println("3.执行Thread.sleep(long)时,线程的状态:" + thread.getState()); // TIME_WAITING
//thread1和main线程主动休眠150毫秒,所以在第150毫秒,thread1早已执行完毕
Thread.sleep(100);
System.out.println("5.线程执行完毕之后,线程的状态:" + thread.getState() + "\n"); // TERMINATED
}
private static void test1() {
System.out.println("======线程的六种状态======");
System.out.println("线程-初始状态:" + Thread.State.NEW);
System.out.println("线程-就绪状态:" + Thread.State.RUNNABLE);
System.out.println("线程-阻塞状态:" + Thread.State.BLOCKED);
System.out.println("线程-等待状态:" + Thread.State.WAITING);
System.out.println("线程-限时等待状态:" + Thread.State.TIMED_WAITING);
System.out.println("线程-终止状态:" + Thread.State.TERMINATED + "\n");
}
}
Java并发10:线程的状态Thread.State及其线程状态之间的转换 - 姚春辉 - 博客园
6. 四种创建线程的方法
-
继承 Thread 类
继承Thread类,重写 run() 方法,实例化该类并调用 start() 方法启动线程
public class ExtendThread extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName()); } public static void main(String[] args) { ExtendThread extendThread = new ExtendThread(); extendThread.start(); } }
-
实现 Runnable 接口
实现 Runnable 接口,并实现 run() 方法,将该类的实例传递给 Thread 类的构造函数,调用Thread 类的实例的 start() 方法启动线程
public class ImplementsRunnable implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } public static void main(String[] args) { ImplementsRunnable runnable = new ImplementsRunnable(); Thread thread = new Thread(runnable, "Thread name"); thread.start(); } }
-
实现 Callable 接口
实现 Callable 接口,并实现 call() 方法(带返回值),将该类的实例传递给 ExecutorService 实例的 submit() 方法,可以获得 Future 对象,通过这个对象可以获得线程执行结果
public class ImplementsCallable implements Callable<String> { @Override public String call() { return Thread.currentThread().getName(); } public static void main(String[] args) throws ExecutionException, InterruptedException { ImplementsCallable callable1 = new ImplementsCallable(); ExecutorService executorService = Executors.newFixedThreadPool(3); Future<String> submit1 = executorService.submit(callable1); String result1 = submit1.get(); System.out.println(result1); } }
-
线程池
Java 提供了
java.util.concurrent.Executor
接口和java.util.concurrent.ExecutorService
接口,以及它们的实现类ThreadPoolExecutor
和ScheduledThreadPoolExecutor
。可以通过工厂方法
Executors
来创建不同类型的线程池,如newFixedThreadPool(int)
、newCachedThreadPool()
、newSingleThreadExecutor()
等public class ThreadPool { public static void main(String[] args) { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(9, 17, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10)); for (int i = 0; i < 50; i++) { threadPoolExecutor.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } threadPoolExecutor.shutdown(); } }
7. 静态代理与动态代理;基于接口/类的动态代理
- 静态代理:
- 在静态代理中,代理类和目标类在编译期间就已经确定。代理类负责将请求转发给目标对象,并可以在转发请求前后执行额外的操作。静态代理的一个典型应用场景是在不修改目标对象的情况下,增加目标对象的功能或者控制目标对象的访问。
- 静态代理的缺点是每个代理类只能代理一个接口或类,如果需要代理多个类或接口,就需要创建多个代理类,导致代码重复和维护成本增加。
- 动态代理:
- 动态代理是在运行时动态生成代理类,而不是在编译时确定。Java 中的动态代理主要是通过
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口实现的。 - 动态代理可以在运行时动态地创建代理类,根据需要代理的接口或者类动态生成代理对象,无需为每个接口或类创建单独的代理类。这样可以减少代码的重复性,提高代码的灵活性和可维护性。
- 通过动态代理,可以在运行时动态地处理目标对象的方法调用,包括在调用目标对象方法前后进行一些操作,比如记录日志、性能监控、事务管理等。
基于接口的动态代理:这种动态代理是针对接口实现的代理,它要求被代理的类必须实现一个或多个接口。Java 中的
java.lang.reflect.Proxy
就是基于接口的动态代理实现方式。基于类的动态代理:这种动态代理是针对类的继承关系实现的代理,它可以代理类的所有方法,包括继承自父类的方法。在 Java 中,常用的基于类的动态代理库是 CGLIB。
代码演示:
-
基类:
Interface:
Marry.java
public interface Marry{ void happyMary(); }
接口的实现类:
You.java
public class You implements Marry{ @Override public void happyMary() { System.out.println("fatfish 结婚了……"); } }
-
静态代理:
静态代理类:
public class WeddingCompany implements Marry { private Marry target; // 代理--->真实目标角色,帮谁结婚 public WeddingCompany(Marry target){ this.target = target; } @Override public void happyMary() { before(); this.target.happyMary(); after(); } private void after(){ System.out.println("结婚后,收尾款!"); } private void before(){ System.out.println("结婚前,布置现场!"); } }
静态代理测试类:
public class TestStaticProxy { public static void main(String[] args) { new Thread(()-> System.out.println("因为爱情")).start(); WeddingCompany weddingCompany = new WeddingCompany(new You()); weddingCompany.happyMary(); } }
输出:
因为爱情 结婚前,布置现场! fatfish 结婚了…… 结婚后,收尾款!
-
JDK 动态代理
JDK 动态代理类:
public class JDKDynamicProxyHandler implements InvocationHandler { private Object target; public JDKDynamicProxyHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(target, args); after(); return result; } private void after(){ System.out.println("结婚后,收尾款!"); } private void before(){ System.out.println("结婚前,布置现场!"); } }
JDK 动态代理测试类:
public class JDKTestDynamicProxy { public static void main(String[] args) { new Thread(()-> System.out.println("因为爱情")).start(); Marry you = new You(); // 接口 ClassLoader classLoader = you.getClass().getClassLoader(); Class<?>[] interfaces = you.getClass().getInterfaces(); JDKDynamicProxyHandler jDKDynamicProxyHandler = new JDKDynamicProxyHandler(you); Marry proxy = (Marry) Proxy.newProxyInstance(classLoader, interfaces, jDKDynamicProxyHandler); proxy.happyMary(); } }
-
CGLIB 动态代理
CGLIB 动态代理类:
public class CglibDynamicProxyHandler implements MethodInterceptor { private Object target; public CglibDynamicProxyHandler(Object target) { this.target = target; } @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invokeSuper(o, args); after(); return result; } private void after(){ System.out.println("结婚后,收尾款!"); } private void before(){ System.out.println("结婚前,布置现场!"); } }
CGLIB 动态代理测试类:
public class TestCglibDynamicProxy { public static void main(String[] args) { new Thread(()-> System.out.println("因为爱情")).start(); You you = new You(); // 类 CglibDynamicProxyHandler proxyHandler = new CglibDynamicProxyHandler(you); You proxyInstance = (You) Enhancer.create(You.class, proxyHandler); proxyInstance.happyMary(); } }
8. join(), join(long millis), yield()
join()
方法用于等待目标线程执行完毕,然后再继续执行当前线程。
join(long millis)
方法是join()
方法的一个重载版本,它允许设置等待的最大时间。如果目标线程在指定的时间内未执行完毕,当前线程会继续执行。
yield()
方法使当前执行的线程让出 CPU 执行权,从而让线程调度器重新选择其他线程来执行。yield()
方法通常用于在同一优先级的线程中,让出执行权给其他线程。
public class Test {
static class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "完成");
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new MyThread(), "线程A").start();
Thread thread = new Thread(new MyThread(), "线程B");
thread.start();
thread.join(2000); // 主线程等待thread执行2秒钟
System.out.println("主线程执行完毕");
}
}
线程A开始执行
线程A完成
线程B开始执行
线程B完成
主线程执行完毕
9. stop(), interrupt()
-
stop()
方法被用于停止线程的执行。它是一个过时的方法,不推荐在实际开发中使用,因为它可能会导致线程不可预料的状态,比如无法释放的锁,数据不一致等问题。这是因为stop()
方法会直接终止线程,而不会进行清理工作。因此,一般情况下应该避免使用stop()
方法。当调用
stop()
方法时,线程的状态会直接转变为TERMINATED
,即终止状态。改进:标志位:
/** * 测试stop * 1.建议线程正常停止-->利用次数,不建议死循环 * 2.建议使用标志位-->设置一个标志位 * 3.不要使用stop或者destroy等过时或者JDK不建议使用的方法 */ public class TestStopThread implements Runnable{ // 1.设置一个标志位 private boolean flag = true; public static void main(String[] args) { TestStopThread stop = new TestStopThread(); new Thread(stop).start(); for (int i = 0; i < 1000; i++) { System.out.println("main...." + i); if(i==900){ // 调用stop()切换标志位,让线程终止 stop.stop(); System.out.println("线程该停止了"); } } } @Override public void run() { int i = 0; while(flag){ System.out.println("run……Thread" + i++); } } // 2.设置一个公开的方法停止线程,转换标志位 public void stop(){ this.flag = false; } }
-
interrupt()
方法用于中断线程的执行。它不会直接停止线程,而是向线程发送一个中断信号,线程可以在合适的时机检测到这个信号并作出相应的处理。通常情况下,被中断的线程会抛出InterruptedException
异常,从而退出执行。调用
interrupt()
方法会将线程的中断状态设置为true
,但并不意味着线程会立即停止执行。线程需要在适当的时机检查中断状态,并根据情况自行决定是否终止执行。
10. 守护线程
在Java中,守护线程(Daemon Thread)是一种在后台提供服务的线程,它的存在不会阻止 JVM 退出。当所有的非守护线程结束时,JVM 会退出,不会等待守护线程执行完毕。
要将一个线程设置为守护线程,可以通过调用 setDaemon(true)
方法来实现。默认情况下,线程是非守护线程。
由于守护线程是后台线程,当主线程结束后,守护线程也会随之结束
11. 锁升级
synchronized
关键字在内部会根据竞争情况自动进行锁升级。具体来说,当一个线程尝试获取锁时,会经历以下阶段:
- 无锁状态(无锁): 初始状态,对象没有被锁定,任何线程都可以访问。
- 偏向锁(偏向锁): 当只有一个线程访问同步块时,锁会升级为偏向锁,这个线程将作为偏向线程持有锁。其他线程进入同步块时,不需要竞争,可以直接获取锁。这个过程是为了优化同步操作,减少无竞争情况下的开销。
- 轻量级锁(轻量级锁): 当多个线程尝试竞争同一个锁时,偏向锁会升级为轻量级锁。线程会尝试使用CAS(Compare and Swap)操作来获取锁,如果获取失败,则会膨胀为重量级锁。
- 重量级锁(重量级锁): 当多个线程竞争同一个锁并且无法获取到锁时,锁会升级为重量级锁。此时,线程会进入阻塞状态,会有较大的性能开销,因为涉及到操作系统层面的线程阻塞和唤醒。
12. 线程安全的容器
不安全:
List list = new ArrayList<>();
list.add(“”);
-
使用线程安全的容器,如
Vector
(synchronized 方法)、CopyOnWriteArrayList
(ReentrantLock) 或者通过Collections.synchronizedList()
(Synchronized 代码块) 方法包装ArrayList
。Vector list = new Vector<>();
CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
List list = Collections.synchronizedList(new ArrayList<>());
-
使用显式的同步控制,比如在访问
ArrayList
时使用synchronized
关键字保证线程安全。synchronized (list) {
list.add(Thread.currentThread().getName());
} -
使用并发集合类,如
java.util.concurrent
包下的ConcurrentHashMap
、ConcurrentSkipListSet
等。
13. 死锁
- 产生死锁的四个必要条件
- 1.互斥条件:一个资源毎次只能被一个进程使用。
- 2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 3.不剥夺条件∶进程已获得的资源,在末使用完之前,不能强行剥夺。
- 4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
14. synchroized与Lock对比
- Lock是显式锁 (手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁, 出了作用域自动释放。
- Lock只有代码块锁, synchronized有代码块锁和方法锁。
- 使用Lock锁,JVM将花费较少的时间来调度线程, 性能更好。并且具有更好的扩展性 (提供更多的子类)。
- 优先使用顺序:
- Lock > 同步代码块 (已经进入了方法体,分配了相应资源 ) > )> )> 同步方法 (在方法体之外)
15. 线程通信方法
- 共享内存:
- 共享内存是最常见的线程间通信方式。多个线程共享同一块内存区域,它们通过读写共享内存来进行通信。在 Java 中,共享内存通常通过共享对象的方式实现,例如共享一个对象的属性或集合。
- 通过共享内存通信时,需要确保对共享数据的访问是线程安全的,可以使用 synchronized 关键字、Lock、volatile 等机制来实现线程安全。
public class SharedMemoryExample {
private static int sharedData = 0;
public static void main(String[] args) {
Runnable producer = () -> {
synchronized (SharedMemoryExample.class) {
for (int i = 0; i < 5; i++) {
sharedData++;
System.out.println("Producer produced: " + sharedData);
}
}
};
Runnable consumer = () -> {
synchronized (SharedMemoryExample.class) {
for (int i = 0; i < 5; i++) {
sharedData--;
System.out.println("Consumer consumed: " + sharedData);
}
}
};
new Thread(producer).start();
new Thread(consumer).start();
}
}
- 管道通信:
- 管道通信是一种基于 I/O 流的线程间通信方式。在 Java 中,可以使用
PipedInputStream
和PipedOutputStream
或者PipedWriter
和PipedReader
来实现管道通信。 - 管道通信适用于在两个线程之间传输数据,其中一个线程充当数据的生产者,另一个线程充当数据的消费者。
import java.io.*;
public class PipeExample {
public static void main(String[] args) throws IOException {
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream();
inputStream.connect(outputStream);
Runnable producer = () -> {
try {
for (int i = 0; i < 5; i++) {
outputStream.write(i);
System.out.println("Producer produced: " + i);
Thread.sleep(1000);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
};
Runnable consumer = () -> {
try {
int data;
while ((data = inputStream.read()) != -1) {
System.out.println("Consumer consumed: " + data);
}
} catch (IOException e) {
e.printStackTrace();
}
};
new Thread(producer).start();
new Thread(consumer).start();
}
}
- wait() 和 notify() / notifyAll() 方法:
Object
类提供了wait()
、notify()
和notifyAll()
方法,用于在多线程环境下进行等待和通知。wait()
方法使当前线程进入等待状态,并释放对象的锁,直到其他线程调用相同对象的notify()
或notifyAll()
方法来唤醒等待的线程。notify()
方法用于唤醒等待在该对象上的一个线程,而notifyAll()
方法则会唤醒所有等待在该对象上的线程。
public class WaitNotifyExample {
private static final Object lock = new Object();
private static boolean isReady = false;
public static void main(String[] args) {
Runnable waiter = () -> {
synchronized (lock) {
while (!isReady) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Waiter: Received notification");
}
};
Runnable notifier = () -> {
synchronized (lock) {
System.out.println("Notifier: Sending notification");
isReady = true;
lock.notify();
}
};
new Thread(waiter).start();
new Thread(notifier).start();
}
}
- Condition 条件:
java.util.concurrent.locks.Condition
接口提供了更灵活的线程通信机制,它通常与Lock
对象一起使用。- 使用
Condition
,可以通过调用await()
方法使线程等待某个条件,而其他线程可以通过调用signal()
或signalAll()
方法来通知等待的线程。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionExample {
private static final Lock lock = new ReentrantLock();
private static final Condition condition = lock.newCondition();
private static boolean isReady = false;
public static void main(String[] args) {
Runnable waiter = () -> {
lock.lock();
try {
while (!isReady) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Waiter: Received notification");
} finally {
lock.unlock();
}
};
Runnable notifier = () -> {
lock.lock();
try {
System.out.println("Notifier: Sending notification");
isReady = true;
condition.signal();
} finally {
lock.unlock();
}
};
new Thread(waiter).start();
new Thread(notifier).start();
}
}
- CountDownLatch、CyclicBarrier、Semaphore 等并发工具:
- Java 并发包中提供了一些并发工具类,如
CountDownLatch
、CyclicBarrier
、Semaphore
等,它们可以帮助线程协调和通信。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
Runnable task = () -> {
try {
System.out.println("Task is waiting...");
latch.await();
System.out.println("Task is running...");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
Thread.sleep(2000); // Simulating some work
latch.countDown(); // Signaling the task to start
}
}
16. AQS
, ReentrantLock
参考
狂神说笔记——多线程05 - subeiLY - 博客园