Java线程
Java线程
线程相关概念
程序
程序通常指的是计算机程序,是一系列指示计算机执行任务的命令或语句。这些命令按照特定的顺序排列,以解决某个问题或执行特定的运算。程序是软件的重要组成部分,它告诉计算机如何操作。
进程
进程指的是一个正在执行的程序实例,它不仅包含程序的代码和数据,还包括程序执行时所需的系统资源和管理信息。操作系统负责进程的创建、调度、同步、通信和终止。
以下是对进程的定义:
1.进程代表了一个正在执行的程序,包括程序计数器、寄存器集合和变量状态。
2.进程拥有独立的执行上下文,包括程序代码、数据、打开的文件和设备等资源。
3.进程是操作系统进行资源分配和调度的基本单位,操作系统为进程分配处理器时间、内存空间等资源。
4.进程具有动态的生命周期,可以创建、运行、阻塞、恢复和终止。
5.进程可以通过进程间通信(IPC)与其他进程交互。
线程
线程是进程中的一个实体,是轻量级的进程,具有以下特性:
1.线程是进程中的一个实体,可以执行程序代码、访问数据、使用系统资源。
2.线程是CPU调度的基本单位,线程之间可以并发执行,共享进程的资源。
3.线程拥有自己的程序计数器、寄存器集合和栈空间,但与同一进程中的其他线程共享内存空间、文件描述符和其他进程资源。
4.线程可以独立与其他线程进行创建、调度、阻塞、恢复和终止。
5.线程可以用于实现多任务处理,提高程序的并发性和相应性。
单线程与多线程
单线程:
单线程是指程序在执行时只有一个线程在运行。程序按照顺序执行,一次只能处理一个任务。
多线程:
多线程是指程序在执行时可以同时允许多个线程,每个线程执行不同任务或者协作完成同一任务的不同部分。
对比:
1.性能: 多线程可以在多核处理器上提供更好的性能,因为它可以并行执行任务。单线程在单核处理器上可能更高效,因为线程切换的开销不存在。
2.复杂性: 多线程程序更复杂,因为需要管理线程之间的交互和同步
3.稳定性: 单线程程序通常更稳定,因为它不会遇到多线程环境中常见的并发问题。
并发(Concurrency)与并行(Parallelism)
并发(Concurrency):
并发是指操作系统能够处理多个任务在同一时间段内发生的能力。在并发的情况下,任务可能不是真正同时执行的,而是交替进行的。例如:单核CPU执行多个任务。
并行(Parallelism):
并行是指多个任务在同一时刻真正同时执行的能力。并行通常需要多个处理器或处理器核心,以及相应的支持硬件和软件。例如:多核CPU执行多个任务。
线程的使用
创建线程
Java创建线程有两种方式:
1.通过继承Thread类,重写run方法(由于类本身继承了Thread类,不能再继承其他类)
2.实现Runnable接口,重写run方法(此方法线程创建的底层使用设计模式中的代理模式-静态代理)
相比之下,更推荐使用实现Runnable接口的方式进行创建,由于它避免了类单继承的局限性,更好地分离了线程代码和业务逻辑。
继承Thread类方式创建线程示例
import lombok.extern.slf4j.Slf4j;
/**
* 创建线程方法一:继承Thread类
* 通过命令行使用jconsole监控可以发现,进程中主线程结束后不一定就结束,取决于所有线程是否都已结束
*/
@Slf4j
public class ThreadCreateDemoOne {
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
threadTest.start();
int num = 0;
do {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.warn("sleep exception : ", e);
Thread.currentThread().interrupt();
}
log.info("this is main {} times test, thread name is {}", ++num, Thread.currentThread().getName());
} while (num < 20);
}
static class ThreadTest extends Thread {
private int num = 0;
@Override
public void run() {
do {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.warn("sleep exception : ", e);
Thread.currentThread().interrupt();
}
log.info("this is thread {} times test, thread name is {}", ++num, Thread.currentThread().getName());
} while (num < 30);
}
}
}
实现Runnable接口方式创建线程示例
import lombok.extern.slf4j.Slf4j;
/**
* 创建线程方法二:实现Runnable接口
* 底层使用了设计模式中的代理模式-静态代理
*/
@Slf4j
public class ThreadCreateDemoTwo {
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
Thread thread = new Thread(threadTest);
thread.start();
int num = 0;
do {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.warn("sleep exception : ", e);
Thread.currentThread().interrupt();
}
log.info("this is main {} times test, thread name is {}", ++num, Thread.currentThread().getName());
} while (num < 20);
//代理模式示例
ProxyThread proxyThread = new ProxyThread(threadTest);
proxyThread.start();
}
static class ThreadTest implements Runnable {
private int num = 0;
@Override
public void run() {
do {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.warn("sleep exception : ", e);
Thread.currentThread().interrupt();
}
log.info("this is thread {} times test, thread name is {}", ++num, Thread.currentThread().getName());
} while (num < 30);
}
}
/**
* 代理模式-静态代理
*/
static class ProxyThread implements Runnable {
private final Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
public ProxyThread() {
this.target = null;
}
public ProxyThread(Runnable runnable) {
this.target = runnable;
}
public void start() {
this.start0();
}
public void start0() {
this.run();
}
}
}
线程终止
当线程完成任务后,会自动退出,除此之外,通过标志位来控制run方法退出也是最简单、最安全的方式。
线程终止示例
import lombok.extern.slf4j.Slf4j;
/**
* 线程终止
*/
@Slf4j
public class ThreadExitDemo {
public static void main(String[] args) throws InterruptedException {
ThreadTest threadTest = new ThreadTest();
Thread thread = new Thread(threadTest);
thread.start();
log.info("主线程:[{}]睡眠10s后,停止子线程", Thread.currentThread().getName());
Thread.sleep(10000);
threadTest.setThreadSwitch(false);
}
static class ThreadTest implements Runnable {
private boolean threadSwitch = true;
@Override
public void run() {
while (threadSwitch) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.warn("sleep exception : ", e);
Thread.currentThread().interrupt();
}
log.info("this is thread test, thread name is {}", Thread.currentThread().getName());
}
}
public void setThreadSwitch(boolean threadSwitch) {
this.threadSwitch = threadSwitch;
}
}
}
线程常用方法
在Java中Thread类有很多方法,这里介绍一些常用的方法
1.设置线程名
public final synchronized void setName(String name)
2.获取线程名
public final String getName()
3.使线程开始执行(底层代用native方法start0())
public synchronized void start()
4.线程开始后需要执行的逻辑
public void run()
5.修改线程的优先级(一共有三种优先级:MIN_PRIORITY(1),NORM_PRIORITY(5),MAX_PRIORITY(10))
public final void setPriority(int newPriority)
6.获取线程的优先级
public final int getPriority()
7.让正在执行的线程休眠n毫秒
public static native void sleep(long millis) throws InterruptedException
8.中断线程,并非结束线程,用于唤醒正在休眠的线程
public void interrupt()
9.线程礼让,告诉调度器当前线程愿意放弃当前对处理器的使用,但决定权在于调度器,不一定礼让成功
public static native void yield()
10.线程插队,线程一旦被其他线程插队,将会失去处理器资源,直到插队的线程执行完
public final void join() throws InterruptedException
常用方法示例
示例1:线程睡眠与打断睡眠
import lombok.extern.slf4j.Slf4j;
/**
* 线程方法使用
*/
@Slf4j
public class ThreadMethodDemoOne {
public static void main(String[] args) throws InterruptedException {
ThreadTest threadTest = new ThreadTest();
Thread thread = new Thread(threadTest);
thread.setName("test");
thread.setPriority(1);
thread.start();
log.info("默认的优先级为: {}",Thread.currentThread().getPriority());
Thread.sleep(1000);
thread.interrupt();
}
static class ThreadTest implements Runnable {
private int num = 0;
@Override
public void run() {
do {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
log.warn("sleep interrupt: ", e);
}
log.info("this is thread {} times test, thread name is {}, priority is {}", ++num, Thread.currentThread().getName(),Thread.currentThread().getPriority());
} while (num < 30);
}
}
}
示例2: 线程插队
import lombok.extern.slf4j.Slf4j;
/**
* 线程方法使用
*/
@Slf4j
public class ThreadMethodDemoTwo {
public static final int FIVE_SIZE = 5;
public static void main(String[] args) throws InterruptedException {
ThreadTest threadTest = new ThreadTest();
Thread thread = new Thread(threadTest);
thread.start();
int count = 0;
do {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.warn("sleep interrupt: ", e);
Thread.currentThread().interrupt();
}
log.info("this is main thread {} times test, thread name is {}", ++count, Thread.currentThread().getName());
if (count == FIVE_SIZE) {
log.info("main thread reach five times, child thread first");
thread.join();
}
} while (count < 20);
}
static class ThreadTest implements Runnable {
private int num = 0;
@Override
public void run() {
do {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.warn("sleep interrupt: ", e);
Thread.currentThread().interrupt();
}
log.info("this is child thread {} times test, thread name is {}", ++num, Thread.currentThread().getName());
} while (num < 20);
}
}
}
用户线程(User Thread)和守护线程(Daemon Thread)
在Java中线程分为两类:用户线程(User Thread)和守护线程(Daemon Thread)
用户线程:
用户线程是系统的工作线程,它会完成程序指定的任务。当Java程序中创建一个线程时,如果没有明确设置,它默认是用户线程。用户线程有以下特点:
用户线程创建通常是为了执行程序中的业务逻辑。
用户线程可以创建其他线程。一般情况下,用户线程执行完毕后,JVM会正常退出。
守护线程:
守护线程是一种特殊的线程,它的生命周期不受程序控制 ,一般用于为用户线程提供服务。守护线程有以下特点:
守护线程通常是执行一些后台任务,比如垃圾回收,JIT编译等。
守护线程不能单独存在,当其他用户线程结束时,即使守护线程正在运行,JVM也会退出
守护线程示例
守护线程通过Thread类中的setDaemon(true)方法设置,但必须在线程启动之前调用
import lombok.extern.slf4j.Slf4j;
/**
* 守护线程使用
*/
@Slf4j
public class DaemonThreadDemo {
public static void main(String[] args) throws InterruptedException {
DaemonThreadTest threadTest = new DaemonThreadTest();
//将threadTest设置为守护线程
threadTest.setDaemon(true);
threadTest.start();
for (int i = 0; i <= 5; i++) {
Thread.sleep(1000);
log.info("main thread {} times thread test:{}", i, Thread.currentThread().getName());
}
}
static class DaemonThreadTest extends Thread {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.warn("sleep exception: ", e);
Thread.currentThread().interrupt();
}
log.info("this is a test by {}", Thread.currentThread().getName());
}
}
}
}
线程的生命周期
从Thread类中的State枚举类中可以看到线程有6种状态,但通常会把Runnable状态(可运行状态)细分为ready(就绪状态)和running(运行状态),也就是7种状态。
线程状态示例
import lombok.extern.slf4j.Slf4j;
/**
* 查看线程状态
*/
@Slf4j
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
ThreadTest threadTest = new ThreadTest();
log.info("{} thread state is {}", threadTest.getName(), threadTest.getState());
threadTest.start();
while (Thread.State.TERMINATED != threadTest.getState()) {
log.info("{} thread state is {}", threadTest.getName(), threadTest.getState());
Thread.sleep(500);
}
log.info("{} thread state is {}", threadTest.getName(), threadTest.getState());
}
static class ThreadTest extends Thread {
private int num = 0;
@Override
public void run() {
do {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.warn("sleep exception : ", e);
Thread.currentThread().interrupt();
}
log.info("this is thread {} times test, thread name is {}", ++num, Thread.currentThread().getName());
} while (num < 5);
}
}
}
线程同步
线程同步是一种机制,用于控制多个线程对共享资源的访问,确保同一时刻只有一个线程可以操作共享资源,以避免并发错误和数据不一致的情况。
线程同步的目的
- 互斥:确保同一时间只有一个线程可以访问共享资源。
- 可见性:确保一个线程对共享资源的修改可以被其他线程看到。
- 有序性:确保程序执行的顺序符合代码的顺序。
对象互斥锁
对象互斥锁是一种同步机制,确保同一时间只有一个线程可以访问某个对象的同步代码块或同步方法。这种锁是基于对象的,每个Java对象都可以作为锁。其中synchronized关键字是最为常见的实现。
synchronized
同步代码块
synchronized(对象) {
//需要同步的逻辑
}
对象的锁可以是this或者其他对象,但该对象必须是同一个对象。
同步方法
public synchronized void methodName() {
//需要同步的逻辑
}
对于静态方法的锁对象是当前类本身(当前类.class)。
对于非静态方法的锁对象是this或者其他对象,但该对象必须是同一个对象。默认锁对象为this。
线程同步示例-解决多个售票站同时售票问题
import lombok.extern.slf4j.Slf4j;
/**
* 方法同步解决同时售票问题(对象互斥锁)
*/
@Slf4j
public class SynchronizedThreadDemo {
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
Thread threadOne = new Thread(threadTest);
Thread threadTwo = new Thread(threadTest);
Thread threadThree = new Thread(threadTest);
threadOne.start();
threadTwo.start();
threadThree.start();
}
public static class ThreadTest implements Runnable {
private int ticketNum = 100;
private boolean runSwitch = true;
//一个对象
private final Object object = new Object();
//如果是静态方法的锁对象是当前类本身
public static synchronized void staticSellTicket() {
log.info("test");
}
public static void staticSellTicket2() {
synchronized (ThreadTest.class){
log.info("test");
}
}
//如果是非静态方法的锁对象可以是this或其他对象(对象要求是同一个)
//同步方法
public synchronized void sellTicket() {
if (ticketNum <= 0) {
log.info("tickets sell out");
runSwitch = false;
return;
}
log.info("{} 售票口 卖出一张车票,还剩{}张", Thread.currentThread().getName(), --ticketNum);
}
//同步代码块
public void sellTicket2() {
//this锁
// synchronized (this) {
// if (ticketNum <= 0) {
// log.info("tickets sell out");
// runSwitch = false;
// return;
// }
// log.info("{} 售票口 卖出一张车票,还剩{}张", Thread.currentThread().getName(), --ticketNum);
// }
//同一个对象锁
synchronized (object) {
if (ticketNum <= 0) {
log.info("tickets sell out");
runSwitch = false;
return;
}
log.info("{} 售票口 卖出一张车票,还剩{}张", Thread.currentThread().getName(), --ticketNum);
}
}
@Override
public void run() {
while (runSwitch) {
sellTicket2();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
log.info("thread interrupt :", e);
Thread.currentThread().interrupt();
}
}
}
}
}
线程死锁
线程死锁是指一个多线程程序中,两个或多个线程永久地阻塞,每个线程等待其他线程释放锁,但这些锁又被其他线程持有,导致没有任何线程能够继续执行,这种情况就称为线程死锁。
造成死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个线程使用。
- 持有和等待条件:一个线程至少持有一个资源,并且正在等待获取额外的资源,而该资源又被其他线程持有。
- 非抢占条件:线程所获得的资源在未使用完毕前不能被其他线程强行抢占。
- 循环等待条件:多个线程形成一种头尾相连的循环等待资源关系。
解决线程死锁的方法
- 预防死锁:资源分配策略
- 避免死锁:银行家算法
- 检测死锁:定时检测
- 解除死锁:剥夺资源
死锁示例
import lombok.extern.slf4j.Slf4j;
/**
* 线程死锁模拟
*/
@Slf4j
public class ThreadDeadLockDemo {
public static void main(String[] args) {
ThreadTest threadTestA = new ThreadTest();
ThreadTest threadTestB = new ThreadTest();
threadTestA.setFlag(true);
threadTestA.setName("A线程");
threadTestB.setFlag(false);
threadTestB.setName("B线程");
threadTestA.start();
threadTestB.start();
}
static class ThreadTest extends Thread {
//保证线程公用同一个对象,成员属性设置为static
static Object object1 = new Object();
static Object object2 = new Object();
private boolean flag = true;
@Override
public void run() {
if (flag) {
// 申请获取object1对象锁后申请获取object2对象锁
synchronized (object1) {
log.info("{} get object1 lock", Thread.currentThread().getName());
synchronized (object2) {
log.info("{} get object2 lock", Thread.currentThread().getName());
}
}
} else {
// 申请获取object2对象锁后申请获取object1对象锁
synchronized (object2) {
log.info("{} get object2 lock", Thread.currentThread().getName());
synchronized (object1) {
log.info("{} get object1 lock", Thread.currentThread().getName());
}
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
}
释放锁
在多线程编程中,释放锁是一个重要操作,它允许其他线程获取之前被某个线程持有的锁。
下面的操作会释放锁:
1.当前线程的同步方法、同步代码块执行结束
2.当前线程的同步方法、同步代码块出现异常结束
3.当前线程的同步方法、同步代码块中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
注:上述情况主要是针对synchronized的锁,对于Lock接口,需要在finally块中显示释放锁。
下面操作不会释放锁:
1.当前线程的同步方法、同步代码块调用Thread.sleep()、Thread.yield()方法,暂停当前线程的执行。
2.其他线程调用了当前线程的suspend()方法将当前线程挂起。