为什么需要多线程
节约时间,提高效率
继承Thread类
通过继承Thread
类来创建线程是最简单的一种方法,继承类重写run()
方法,然后通过线程对象实例去调用start()
方法即可启动线程。
public class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "在运行!");
}
}
MyThread thread = new MyThread();
thread.start();
注意:1. 加上 @Override
注解,会让系统自动检查 public void run()
方法定义有没有写错。2.重写父类的 run()
方法,注意必须是修饰为 public void。3.
线程需要调用 start()
方法才能启动。
实现 Runnable 接口
通过实现Runnable
接口来创建线程也是最简单的一种方法,同时也是最常用的一种方式。
开发者只需要实现Runnable
接口,然后通过一个Thread
类来启动。
public class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "在运行!");
}
}
Thread thread = new Thread(new MyThread());
thread.start();
线程安全
既然是线程安全问题,那么毫无疑问所有的隐患都是出现在多个线程访问的情况下产生的,也就是我们要确保在多条线程访问的时候,我们的程序还能按照我们预期的行为去执行。
多个线程 操作 同一个资源 的时候,发生了冲突的现象,就叫做 线程不安全。
在 Java 中,可以用 synchronized
关键字来解决余量错乱的问题。synchronized
加载方法上,紧跟着 public
:
public synchronized void add(){
i++;
}
注:synchronized
也叫线程 同步锁 ,表示此方法是锁定的,同一时刻只能由一个线程执行此方法。
注意:使用 synchronized
的方法意味着满足了两个线程安全的特性:
- 原子性:方法全部执行并且执行的过程不会被任何因素打断。
- 可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
悲观锁和乐观锁
乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。
因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。
因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。
public class Test {
//value1:线程不安全
private static int value1 = 0;
//value2:使用乐观锁
private static AtomicInteger value2 = new AtomicInteger(0);
//value3:使用悲观锁
private static int value3 = 0;
private static synchronized void increaseValue3() {
value3++;
}
public static void main(String[] args) throws Exception {
//开启1000个线程,并执行自增操作
for (int i = 0; i < 1000; ++i) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
value1++;
value2.getAndIncrement();
increaseValue3();
}
}).start();
}
//查看活跃线程 ,因守护线程的原因[基于工具问题windows:idea run 启动用 >2,debug 用>1]
while (Thread.activeCount() > 2) {
//Thread.currentThread().getThreadGroup().list();
Thread.yield();//让出cpu
}
//打印结果
Thread.sleep(1000);
System.out.println("线程不安全:" + value1);
System.out.println("乐观锁(AtomicInteger):" + value2);
System.out.println("悲观锁(synchronized):" + value3);
}
}
注:
AtomicInteger
虽然是一个类,但等同于一个整数(就像 Integer 是 int 的对象)。调用 new AtomicInteger()
构造函数实例化对象的时候,可以指定任意的整数值。
AtomicInteger
类的 incrementAndGet()
和 decrementAndGet()
方法就是典型的乐观锁实现。
并发容器
CompletableFuture 应用
CompletableFuture
是一个异步任务编排、调度框架,以更优雅的方式实现组合式异步编程。
CompletableFuture的使用具有以下优势和特点:
- 异步执行:CompletableFuture允许任务在后台线程中异步执行,不会阻塞主线程,提高了应用程序的响应性和性能。
- 链式操作:通过CompletableFuture提供的方法,可以方便地对任务进行链式操作,构建复杂的任务依赖关系,实现高效的任务调度和执行。
- 异常处理:CompletableFuture提供了丰富的异常处理方法,可以处理任务执行过程中可能发生的异常,并实现灵活的错误处理和回退机制。
- 多任务组合:CompletableFuture支持多个任务的并发执行和结果组合。可以轻松地实现多任务并发处理的场景,提高应用程序的效率和并发性。
-
thenApply()
方法签名:thenApply(Function<? super T, ? extends U> fn)
- 输入参数:上一阶段的任务结果类型为 T。
- 返回值:新阶段的任务结果类型为 U。
- 功能:对上一阶段的任务结果进行转换操作,并返回一个新的 CompletableFuture 对象。
-
thenAccept()
方法签名:thenAccept(Consumer<? super T> action)
- 输入参数:上一阶段的任务结果类型为 T。
- 返回值:CompletableFuture,没有返回值。
- 功能:对上一阶段的任务结果进行消费操作,没有返回值。
-
thenRun()
方法签名:thenRun(Runnable action)
- 输入参数:无。
- 返回值:CompletableFuture,没有返回值。
- 功能:在上一阶段任务完成后执行给定的 Runnable 任务,没有输入参数和返回值。
supplyAsync()
用于开头,thenAccept()
用于末尾,各自调用一次即可。中间有多个步骤,可以调用多次 thenApply()
。
supplyAsync()
是静态方法,返回值是 CompletableFuture
实例对象,再调用 thenApply()
或 thenAccept()
实例方法,返回的也是 CompletableFuture
实例对象。
线程池
线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。在JAVA中主要是使用ThreadPoolExecutor类来创建线程池,并且JDK中也提供了Executors工厂类来创建线程池(不推荐使用)。
线程池的优点:
降低资源消耗,复用已创建的线程来降低创建和销毁线程的消耗。
提高响应速度,任务到达时,可以不需要等待线程的创建立即执行。
提高线程的可管理性,使用线程池能够统一的分配、调优和监控。
线程池创建
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import java.util.concurrent.*;
public class StudentIDTest {
// 线程工厂
private static final ThreadFactory namedThreadFactory = new BasicThreadFactory.Builder()
.namingPattern("studentReg-pool-%d")
.daemon(true)
.build();
// 等待队列
private static final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(1024);
// 线程池服务
private static final ThreadPoolExecutor EXECUTOR_SERVICE = new ThreadPoolExecutor(
20,
200,
30,
TimeUnit.SECONDS,
workQueue,
namedThreadFactory,
new ThreadPoolExecutor.AbortPolicy()
);
public static void main(String[] args) {
}
}
工具库:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
创建线程工厂实例
new BasicThreadFactory.Builder()
.namingPattern("定义线程名字-pool-%d")
.daemon(true)
.build();
创建线程等待队列实例
new LinkedBlockingQueue<Runnable>(数字)
数字=2048(性能好)1024(适中)512(性能一般)
ThreadPoolExecutor