个人主页: 【⭐️个人主页】
需要您的【 点赞+关注】支持
进程是系统中正在运行的一个程序,程序一旦运行就是进程。
进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。
一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。
线程与进程的一个主要区别是, 同一进程内的多个线程会共享部分状态,多个线程可以读写同一块内存(一个进程无法直接访问另一进程的内存)。同时,每个线程还拥有自己的寄存器和栈,其他线程可以读写这些栈内存。
线程是进程的一个实体,是进程的一条执行路径。
线程是进程的一个特定执行路径。当一个线程修改了进程的资源,它的兄弟线程可以立即看到这种变化。
1.需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程的代价是很大的。
2.线程的切换速度快,所以在需要大量计算,切换频繁时使用线程,还有耗时的操作时用使用线程可提高应用程序的响应。
3.因为对CPU系统的效率使用上线程更占优势,所以可能要发展到多机分布的用进程,多核分布用线程。
4.并行操作时用线程,如C/S架构的服务器端并发线程响应用户的请求。
5.需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY
) - 10 (Thread.MAX_PRIORITY
)。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY
(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
当Java虚拟机启动时,通常会有一个非守护
进程线程(通常调用某个指定类的名为main的方法)。 Java虚拟机继续执行线程,直到发生以下任一情况:
- 已调用类Runtime的exit方法,并且安全管理器已允许执行退出操作。
- 通过调用run方法返回或抛出超出run方法传播的异常,所有非守护程序线程的线程都已死亡。
用户线程 :是指在程序中通过Thread类创建的线程,默认情况下是用户线程。用户线程的生命周期和程序主线程相同,当程序主线程结束时,所有的用户线程也会自动结束,用户线程会组织应用程序的退出
守护线程 :是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
将线程转换为守护线程可以通过调用Thread
对象的setDaemon(true)
方法来实现。在使用守护线程时需要注意一下几点:
Thread Api | 说明 |
---|---|
start() | 启动线程并调用 run() 方法。在 start() 方法被调用后,新线程会在一个单独的执行路径上运行。 |
sleep(long millis) | 暂停当前线程的执行,让其他线程有机会执行。这是一个静态方法,可以在任何地方使用。当线程被暂停时,它将放弃 CPU 控制权,但它的锁 保持不会释放。 |
yield() | 告诉当前线程放弃CPU资源,以便其他线程可以运行。如果没有其他线程等待执行,当前线程将继续运行,不释放锁 资源,由运行状态变为就绪状态.作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证 yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield() 不会导致阻塞 |
join()/join(long millis) | 等待线程执行终止,当前线程里调用其它线程的join 方法后,当前线程进入WAITING/TIMED_WAITING 状态,直到被调用线程执行完毕或者 millis 时间到,当前线程进入就绪状态注意:在 Java 中,join() 方法不会释放锁。如果一个线程在获得了某个对象的锁之后调用了该对象的join() 方法,那么它仍然持有该对象的锁。 |
wait() 和 notify() | wait() 和 notify() :用于实现线程之间的通信。当一个线程等待某些条件时,可以调用 wait() 方法暂停自己的执行,等待其他线程调用 notify() 方法来通知它恢复执行。注意: wait() 和 notify() 必须在 synchronized 块中使用,以确保线程安全;当前线程调用对象的 wait() 方法,当前线程释放对象锁 |
interrupt() | 中断线程,例如,当线程A运行时,线程B可以调用线程A的interrupt() 方法来设置线程A的中断标志为true并立即返回。设置标志仅仅是设置标志,线程A实际并没有被中断,它会继续往下执行。如果线程A因为调用了wait 系列函数、join 方法或者sleep 方法而被阻塞挂起,这时候若线程B调用线程A的interrupt( )方法,线程A会在调用这些方法的地方抛出InterruptedException 异常而返回。 |
isInterrupted() | 检测当前线程是否被中断,如果被中断(中断标志位为true), 则返回true,否则返回false。 |
interrupted() | 作者:喝咖啡的工匠 链接:https://juejin.cn/post/7210216031536578618 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。检测当前线程是否被中断,如果被中断,则返回true,否则返回false。与isInterrupted 不同的是,该方法如果发现当前线程被中断,则会清除中断标志,并且该方法是static方法,可以通过Thread 类直接调用。 |
Java 提供了三种创建线程的方法:
public class RunnableThread implements Runnable{
@Override
public void run() {
System.out.println("Runnable 实现: 线程 Runnable");
}
}
public static void main(String[] args) {
// runnable
new Thread(new RunnableThread()).start();
}
public class AThread extends Thread {
@Override
public void run() {
System.out.println("hello : 我是A Thread");
}
}
public static void main(String[] args) {
// extends Thread
AThread aThread = new AThread();
aThread.start();
}
Callable 接口是一个具有类型参数的泛型接口,它的 call() 方法可以返回一个结果,并且可能会抛出异常。Future 接口则是一个表示异步计算的结果的接口,它提供了检查计算是否完成、等待计算完成和获取计算结果的方法:
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(3000);
return "Hello, Callable!";
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
try {
String result = future.get();
System.out.println("Callable result: " + result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}
多线程能够帮助我们有效利用CPU的性能处理任务。提高了处理能力和并发能力。但是多线程(并发)会引起线程数据安全访问问题。需要我们对安全问题有足够认识,该文章只是介绍了Java中的多线程编程的理论基础知识。线程安全我们下一章再讨论~