【多线程与高并发】- 线程基础与状态

简介: 所谓线程就是操作系统(OS)能够进行运算调度的最小单位,是一个基本的CPU执行单元,也是执行程序流的最小单元。能够提高OS的并发性能,减小程序在并发执行时所付出的时空开销。线程是进程的一个实体,是被系统独立调度和分派的基本单位。

线程基础与状态

多线程1.jpg

前言

好久没坚持学习了,所以,这次要好好下定决心学习。多线程与高并发不是一天两天就能弄懂的,需要不断的学习、实践,本次笔者将最近学习的内容知识记录下来。多线程也是一项比较重要的内容,虽然CRUD不太会接触到,但是,在一些相关场景可能会有某些问题是由于线程导致的。

线程的概念

要了解线程的概念,就需要知道什么是进程。简单理解就是一个进程中包含了许多个线程。现在就简单介绍,后续若是有对操作系统进行研究的话会慢慢介绍,具体关于线程进程的内容可以去看看王道的操作系统,里面讲述得特别清楚。

什么是进程?

是系统进行资源分配的基本单位,是操作系统结构的基础,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

什么是线程?

所谓线程就是操作系统(OS)能够进行运算调度的最小单位,是一个基本的CPU执行单元,也是执行程序流的最小单元。能够提高OS的并发性能,减小程序在并发执行时所付出的时空开销。线程是进程的一个实体,是被系统独立调度和分派的基本单位。线程本身是不拥有系统资源的,但是它能够使用同属进程的其他线程共享进程所拥有的全部资源。

在Java线程中是怎样的呢?

在Java中,最常见得就是继承Thread类或者实现Runnable接口,再通过run或者start方法去执行线程。

如以下代码,这是一段很简单得代码块,通过继承Thread类,重写run方法来创建线程,并且通过run和start来运行。

publicclassThread_demo01 {
privatestaticclassThread1extendsThread {
@Overridepublicvoidrun() {
for (inti=0; i<5; i++) {
try {
Thread.sleep(1);
                } catch (InterruptedExceptione) {
thrownewRuntimeException(e);
                }
System.out.println("Thread1-"+i);
            }
        }
    }
publicstaticvoidmain(String[] args) {
newThread1().run(); // 顺序执行//        new Thread1().start(); // 线程同时执行for (inti=0; i<5; i++) {
try {
Thread.sleep(1);
            } catch (InterruptedExceptione) {
thrownewRuntimeException(e);
            }
System.out.println("main");
        }
    }
}

这两种不同得启动方式,出现得现象也是不同的。

  • 使用run方法启动线程

  • 使用start方法启动线程

关于这两种方式启动线程可以看一下下面的流程图。

run方法是会让线程T1先执行完毕之后,再继续执行主线程,而start方法他是同时执行两个线程。

Java线程的Sleep、Yield、Join方法

1、sleep方法

sleep()需要提供一个时间参数(毫秒),会使得线程在一定的时间内被暂停执行,在sleep的过程中,线程是不会释放锁的,只会进入阻塞状态,让出cpu给其他线程去执行。如下代码演示,此处不做锁的探究。

publicclassT3_Thread_Sleep {
publicstaticvoidmain(String[] args) {
Threadt1=newThread(newThread1());
Threadt2=newThread(newThread2());
t1.start();
t2.start();
    }
staticclassThread1implementsRunnable {
@Overridepublicvoidrun() {
System.out.println("T1 is running");
try {
Thread.sleep(1000);
            } catch (InterruptedExceptione) {
thrownewRuntimeException(e);
            }
System.out.println("T1 is end");
        }
    }
staticclassThread2implementsRunnable {
@Overridepublicvoidrun() {
System.out.println("T2 is running");
try {
Thread.sleep(2000);
            } catch (InterruptedExceptione) {
thrownewRuntimeException(e);
            }
System.out.println("T2 is end");
        }
    }
}

运行之后在T1线程休眠的时候会让出cpu资源给T2线程,T1线程会睡眠1秒,T2睡眠2秒,最终结果如图

2、Yield方法

yield()与sleep()都是让线程暂停执行,也是不会释放锁资源。但是yield并不是让进程进入阻塞态,而是回到就绪态,等待重新获取CPU资源。此时,其他的线程有机会获得cpu资源,也有可能在yield方法进入就绪态后立马变成执行态。如以下代码,同样不考虑锁的问题。


/

publicclassT4_Thread_Yield {
staticclassThread1implementsRunnable {
@Overridepublicvoidrun() {
System.out.println("T1 is running");
Thread.yield();
System.out.println("T1 is end");
        }
    }
staticclassThread2implementsRunnable {
@Overridepublicvoidrun() {
System.out.println("T2 is running");
System.out.println("T2 is end");
        }
    }
publicstaticvoidmain(String[] args) {
Threadt1=newThread(newThread1());
Threadt2=newThread(newThread2());
t1.start();
t2.start();
    }
}

经过不同的测试,结果都是不同的。

3、Join方法

join()方法是暂停当前线程,调用执行另一个线程,等待join的线程执行完毕后才能够继续执行当前线程。如以下例子,T1,T2同时开始,在T2线程中join了T1,就会导致T1要先执行完毕之后,才会去执行T2。

publicclassT5_Thread_Join {
publicstaticvoidmain(String[] args) {
ThreadT1=newThread(() -> {
System.out.println("T1开始");
for (inti=0; i<5; i++) {
System.out.println("线程T1执行中: "+i);
            }
System.out.println("T1结束");
        });
ThreadT2=newThread(() -> {
System.out.println("T2开始");
for (inti=0; i<5; i++) {
System.out.println("线程T2执行中: "+i);
if (i==3) {
try {
T1.join();
                    } catch (InterruptedExceptione) {
thrownewRuntimeException(e);
                    }
                }
             }
System.out.println("T2结束");
        });
T1.start();
T2.start();
    }
}

结果如下,不管怎么测试,都会是T2最后执行结束。

Java的线程状态

线程具有最基本的三态(就绪、运行、阻塞)。线程与进程一样,各线程之间也存着共享资源和互相合作的制约关系,致使线程运行时具有间断性。接下来看一下如图,这是五种状态的转化。

线程的不同状态

在Java线程中有6中状态,从线程的创建到线程的终止。线程创建为NEW创建态,通过start启动线程,线程内部会从就绪态转成运行态,在Java线程中统称为“运行态”,线程由于被挂起、调用yeild等方法能够使线程从运行态转成就绪态,也能够通过线程的其他方法或者锁阻塞线程,直到时间结束或者是获得锁等,从而回到RUNABLE状态。

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):Java线程中将就绪(Ready)和运行中(Running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(Ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(Running)。
  3. 阻塞(BLOCKED):表示线程阻塞于锁。
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  6. 终止(TERMINATED):表示该线程已经执行完毕。

Java线程的状态转化

如线程状态转换图,以下就是Java线程状态的转换流程。线程可以通过实现Runnable接口或者继承Threa类,然后去实例化Java的线程对象。在线程被执行之前都是属于创建态(NEW),在调用start方法后,线程就会转成RUNABLE状态,在RUNABLE中,当线程处于就绪态(Ready)的时候,经过调度分配了cpu资源,这时转成了运行态,当线程被挂起、线程执行了yield,线程将会退回就绪态。在运行态(RUNABLE),也会通过一些处理而被阻塞或者等待。终止状态(TERMINATED)是线程执行完毕退出,此时,终止态的线程不会直接转成创建态。

Java线程状态代码

Java中线程的状态都是在java.lang.Thread.State的枚举类中。

可以看一下以下枚举代码,分别为(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)六种。

publicenumState {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}

接下来看一下演示代码

publicclassT6_Thread_State {
staticclassThread1extendsThread {
@Overridepublicvoidrun() {
System.out.println("run - 当前线程的状态: "+this.getState());
for (inti=0; i<5; i++) {
try {
System.out.println("sleep前 - 当前线程的状态: "+this.getState());
Thread.sleep(1000);
System.out.println("sleep后 - 当前线程的状态: "+this.getState());
                } catch (InterruptedExceptione) {
thrownewRuntimeException(e);
                }
            }
        }
    }
publicstaticvoidmain(String[] args) throwsInterruptedException {
Thread1t1=newThread1();
System.out.println("main - 当前线程的状态: "+t1.getState());
t1.start();
System.out.println("join前 - 当前线程的状态: "+t1.getState());
t1.join();
System.out.println("join后 - 当前线程的状态: "+t1.getState());
    }
}

运行结果,可以只管看到线程在运行过程种的状态切换。

博文推荐

这里推荐给各位一篇很不错的博客文章,是针对Java线程的状态转换的详细介绍

Java线程的6种状态及切换(透彻讲解)_潘建南的博客-CSDN博客_线程的5种状态

线程池 waiting on condition_Java线程生命周期与状态切换_有所不知的博客-CSDN博客

相关文章
|
13天前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
32 1
C++ 多线程之初识多线程
|
13天前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
36 6
|
10天前
|
存储 运维 NoSQL
Redis为什么最开始被设计成单线程而不是多线程
总之,Redis采用单线程设计是基于对系统特性的深刻洞察和权衡的结果。这种设计不仅保持了Redis的高性能,还确保了其代码的简洁性、可维护性以及部署的便捷性,使之成为众多应用场景下的首选数据存储解决方案。
22 1
|
13天前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
16 0
C++ 多线程之线程管理函数
|
16天前
|
Java Linux
【网络】高并发场景处理:线程池和IO多路复用
【网络】高并发场景处理:线程池和IO多路复用
33 2
|
17天前
|
设计模式 Java 物联网
【多线程-从零开始-玖】内核态,用户态,线程池的参数、使用方法详解
【多线程-从零开始-玖】内核态,用户态,线程池的参数、使用方法详解
34 0
|
17天前
|
安全 Java 程序员
【多线程-从零开始-肆】线程安全、加锁和死锁
【多线程-从零开始-肆】线程安全、加锁和死锁
32 0
|
28天前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
1月前
|
Java Spring
spring多线程实现+合理设置最大线程数和核心线程数
本文介绍了手动设置线程池时的最大线程数和核心线程数配置方法,建议根据CPU核数及程序类型(CPU密集型或IO密集型)来合理设定。对于IO密集型,核心线程数设为CPU核数的两倍;CPU密集型则设为CPU核数加一。此外,还讨论了`maxPoolSize`、`keepAliveTime`、`allowCoreThreadTimeout`和`queueCapacity`等参数的设置策略,以确保线程池高效稳定运行。
139 10
spring多线程实现+合理设置最大线程数和核心线程数
|
1月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
74 15
一个Android App最少有几个线程?实现多线程的方式有哪些?