[8w字] | Java多线程全套功法
Posted 结构化思维wz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[8w字] | Java多线程全套功法相关的知识,希望对你有一定的参考价值。
介绍:CSDN统计字数:
77153
字,Java多线程从入门到精通,由浅入深。[建议收藏!]
❤️❤️❤️❤️❤️❤️ 点击主页发现更多好文❤️❤️❤️❤️❤️❤️
文章目录
引言
什么是进程,线程?
进程 :资源分配的最小单位
,cpu从磁盘中读取一段程序到内存中,该执行程序的实例就叫做进程。一个程序如果被cpu多次读取到内存中,则变成多个独立的进程。
线程 : 线程是程序执行的最小单位,在一个进程中可以有多个不同的线程。
线程的应用实例:
同一个应用程序中(进程),更好的并行处理。
例子:手写一个文本编辑器需要多少个线程?
为什么需要使用多线程?
采用多线程的形式执行代码,目的是为了提高程序开发的效率。
串行、并行的区别
CPU分时间片交替执行,宏观并行,微观串行,由OS负责调度。如今的CPU已经发展到了多核CPU,真正存在并行。
CPU调度算法
多线程是不是一定提高效率? 不一定,需要了解cpu调度的算法。
CPU调度算法:
如果在生产环境中,开启很多线程,但是我们的服务器核数很低,我们这么多线程会在cpu上做上下文切换,反而会降低效率。
使用线程池来限制线程数和cpu数相同会比较好。
一、Java多线程基础
创建线程的方式
- 继承
Thread
类创建线程 - 实现
Runnable
接口创建线程 - 使用匿名内部类形式创建线程
- 使用
Lambda
表达式创建 - 使用
Callable
和Future
创建线程 - 使用线程池创建
- Spring中的
@Async
创建
1.继承Thread
类创建线程
public class ThreadTest extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程,线程一");
}
}
/* 创建对象进入初始状态,调用start()进入就绪状态。直接调用run()方法,相当于在main中执行run。并不是新线程*/
public static void main(String[] args) {
new ThreadTest().start();
}
}
2.实现Runnable
接口创建线程
public class Thread02 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"我是子线程");
}
public static void main(String[] args) {
new Thread(new Thread02()).start();
}
}
3.使用匿名内部类形式创建线程
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"我是子线程");
}
}).start();
}
4.使用Lambda
表达式创建
public class Thread02 {
public static void main(String[] args) {
new Thread(() -> System.out.println(Thread.currentThread().getName()+"我是子线程")).start();
}
}
5.使用Callable
和 Future
创建线程
Callable和Future线程可以获取到返回结果,底层基于LockSupport
。 (这里只是略写,后面有详细介绍)
Runnable的缺点:
1. run没有返回值
2. 不能抛异常
Callable接口允许线程有返回值,也允许线程抛出异常
Future接口用来接受返回值
public class Thread03 implements Callable<Integer> {
/**
* 当前线程需要执行的代码,返回结果
* @return 1
* @throws Exception
*/
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"返回1");
return 1;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Thread03 callable = new Thread03();
FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(callable);
new Thread(integerFutureTask).start();
//通过api获取返回结果,主线程需要等待子线程返回结果
Integer result = integerFutureTask.get();
System.out.println(Thread.currentThread().getName()+","+result); // main,1
}
6.使用线程池创建
public class ThreadExecutor {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"我是子线程1");
}
});
executorService.submit(new Thread03()); //submit一个线程到线程池
}
}
7.Spring中的@Async
创建
第一步:在入口类中开启异步注解
@SpringBootApplication
@EnableAsync
第二步:在当前方法上加上@Async
@Component
@Slf4j
public class Thread01 {
@Async
public void asyncLog(){
try {
Thread.sleep(3000);
log.info("<2>");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
第三步:验证测试
@RestController
@Slf4j
public class Service {
@Autowired
private Thread01 thread01;
@RequestMapping("test")
public String Test(){
log.info("<1>");
thread01.asyncLog();
log.info("<3>");
return "test";
}
}
访问localhost:8080/test
查看日志为:
Thread
中的常用的方法
1.Thread.currentThread() 方法可以获得当前线程
java中的任何一段代码都是执行在某个线程当中的,执行当前代码的线程就是当前线程。
2.setName()/getName
thread.setName(线程名称) //设置线程名称
thread.getName() //返回线程名称
通过设置线程名称,有助于调试程序,提高程序的可读性,建议为每个线程都设置一个能够体现线程功能的名称。
3.isAlive()
thread.isAlive() //判断当前线程是否处于活动状态
4.sleep()
Thread.sleep(millis); //让当前线程休眠指定的毫秒数
实例:计时器(一分钟倒计时)
package se.high.thread;
/**
* @author 王泽
* 使用线程休眠,实现一个简单的计数器。
*/
public class SimpleTimer {
public static void main(String[] args) {
int remaining = 10 ; //从60秒开始计时
while(true){
try {
System.out.println("时间: " + remaining);
if (remaining >= 0){ remaining--; }
Thread.sleep(1000); //线程休眠
if(remaining == -1){break;}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5.getId()
java中的线程都有一个唯一编号
6.yield()
放弃当前的cpu资源
Thread.yield(); //可以让线程由运行转为就绪状态
7.setPriority()
设置线程的优先级
thread.setPriority(num); 设置线程的优先级,取值为1-10,如果超过范围会抛出异常 IllegalArugumentExption;
优先级越高的线程,获得cpu资源的概率越大。
优先级本质上只是给线程调度器一个提示信息,以便于线程调度器决定先调度哪些线程。不能保证优先级高的线程先运行。
java优先级设置不当,可能导致某些线程永远无法得到运行,产生了线程饥饿。
线程的优先级并不是设置的越高越好,在开发时不必设置线程的优先级。
8.interrupt()
中断线程 (Thread中的方法。)
因为interrupt()方法只能中断阻塞过程中的线程而不能中断正在运行过程中的线程。
在运行中的线程使用:
注意调用此方法仅仅是在当前线程打一个停止标志,并不是真正的停止线程。
例如在线程1中调用线程b的interrupt(),在b线程中监听b线程的中断标志,来处理结束。
package se.high.thread;
/**
* @author 王泽
*/
public class YieldTest extends Thread {
@Override
public void run() {
for (int i = 1; i < 1000; i++) {
// 判断中断标志
if (this.isInterrupted()){
//如果为true,结束线程
//break;
return;
}
System.out.println("thread 1 --->"+i);
}
}
}
package se.high.thread;
/**
* @author 王泽
*/
public class Test {
public static void main(String[] args) {
YieldTest t1 = new YieldTest();
t1.start(); //开启子线程
//当前线程main线程
for (int i = 1; i < 100; i++) {
System.out.println("main --->" + i);
}
//打印完main线程中100个后,中断子线程,仅仅是个标记,必须在线程中处理
t1.interrupt();
}
}
9.setDaemon()
守护线程
//线程启动前
thread.setDaemon(true);
thread.start();
java中的线程分为用户线程与守护线程
守护线程是为其他线程提供服务的线程,如垃圾回收(GC)就是一个典型的守护线程。
守护线程不能单独运行,当jvm中没有其他用户线程,只有守护线程时,守护线程会自动销毁,jvm会自动退出。
线程的状态
线程的状态:getState()
阶段案例:手写@Async
异步注解
思路:通过Aop拦截只要在我们方法上有使用到我们自己定义的异步注解,我们就单独的开启一个异步线程去执行目标方法。
1.自定义一个注解
/**
* @author 王泽
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAsync {
String value() default "";
}
2.Aop编程
@Aspect 用来类上,代表这个类是一个切面
@Before 用在方法上代表这个方法是一个前置通知方法
@After 用在方法上代表这个方法是一个后置通知方法 @Around 用在方法上代表这个方法是一个环绕的方法
@Around 用在方法上代表这个方法是一个环绕的方法
*/
@Component
@Aspect
@Slf4j
public class ExtThreadAsyncAop {
@Around(value ="@annotation(org.spring.annotation.MyAsync)")
public Object around(ProceedingJoinPoint joinPoint){
try {
log.info(">环绕通知开始执行<");
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
joinPoint.proceed();//目标方法
}
}).start();
log.info(">环绕通知结束执行<");
return "环绕通知";
}catch (Throwable throwable){
return "系统错误";
}
}
}
3.使用自定义注解
@Component
@Slf4j
public class Thread01 {
@MyAsync
public void asyncLog(){
try {
log.info("目标方法正在执行...阻塞3s");
Thread.sleep(3000);
log.info("<2>");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.测试
/**
* @author 王泽
*/
@RestController
@Slf4j
public class Service {
@Autowired
private Thread01 thread01;
@RequestMapping("test")
public String Test(){
log.info("<1>");
thread01.asyncLog();
log.info("<3>");
return "test";
}
}
5.结果:
二、线程安全原理篇
多线程的好处:
- 提高了系统的
吞吐量
,多线程编程可以使一个进程有多个并发(concurrent)。
- 提高
响应性
,服务器会采用一些专门的线程负责用户的请求处理,缩短了用户的等待时间。 充分利用多核处理器资源
,通过多线程可以充分的利用CPU资源。
多线程的问题:
-
线程安全
问题,多线程共享数据时,如果没有采取正确的并发访问控制措施,就可能产生数据一致性问题,如读取脏数据(过期的数据),如丢失数据更新。 -
线程活性(thread liveness)
问题。由于程序自身的缺陷或者由资源稀缺性导致的线程一直处于非RUNNABLE状态
,这就是线程活性问题。常见的线程活性问题:
1.
死锁 (DeadLock)
鹬蚌相争
2.锁死 (Lockout)
睡美人故事中王子挂啦
3.活锁 (LiveLock)
类似小猫一直咬自己的尾巴,但是咬不到
4.饥饿 (Starvation)
类似于健壮的雏鸟总是从母鸟嘴中抢食物。 -
上下文切换(Context Switch)
处理器从执行一个线程切换到执行另外一个线程。 -
可靠性
可能会由一个线程导致JVM意外终止,其他线程也无法执行。
线程安全问题
什么是线程安全问题?
当多个线程对同一个对象的实例变量,做写(修改)的操作时,可能会受到其他线程的干扰,发生线程安全的问题。
原子性(Atomic):
不可分割
,访问(读,写)某个共享变量的时候,从其他线程来看,该操作要么已经执行完毕,要么尚未发生。其他线程看不到当前操作的中间结果。 访问同一组共享变量
的原子操作是不能够交错的,如现实生活中从ATM取款。
java中有两种方式实现原子性:
1.锁 :锁具有排他性,可以保证共享变量某一时刻只能被一个线程访问。
2.CAS指令 :直接在硬件层次上实现,看做是一个硬件锁。
可见性(visbility):
在多线程环境中,一个线程对某个共享变量
更新之后,后续其他的线程可能无法立即读到这个更新的结果。
如果一个线程对共享变量更新之后,后续访问该变量的其他线程可以读到更新的结果,称这个线程对共享变量的更新对其他线程可见。否则称这个线程对共享变量的更新对其他线程不可见。
以上是关于[8w字] | Java多线程全套功法的主要内容,如果未能解决你的问题,请参考以下文章 iOS多线程全套:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用 iOS多线程全套:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用(上)多线程程序因为可见性问题可能会导致其他线程读取到旧数据(