zl程序教程

您现在的位置是:首页 >  后端

当前栏目

java 线程详解

JAVA线程 详解
2023-09-14 09:03:13 时间
进程是程序执行的一个实例,比如说,10个用户同时执行IE,那么就有10个独立的进程(尽管他们共享同一个可执行代码)。 进程的特点,每一个进程都有自己的独立的一块内存空间、一组资源系统。其内部数据和状态都是完全独立的。怎么看待多进程?进程的优点是提高CPU运行效率,在同一时间内执行多个程序,即并发执行。但是从严格上讲,也不是绝对的同一时刻执
一般来说进程消耗的内存比较大 进程切换代价很高,进程切换也像线程一样需要保持上一个进程的上下文环境 在web编程中,如果一个进程来处理一个请求的话,如果要提高并发量就要提高进程数,而进程数量受内存和切换代价限制

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

同类的多个线程共享一块内存空间和一组系统资源,线程本身的数据通常只有CPU的寄存器数据,以及一个供程序执行时的堆栈。线程在切换时负荷小,因此,线程也被称为轻负荷进程。一个进程中可以包含多个线程。

在JVM中,本地方法栈、虚拟机栈和程序计数器是线程隔离的,而堆区和方法区是线程共享的。关于JVM中的资源分配,可参考我的另一篇文章【JVM内存管理及GC】:http://blog.csdn.net/suifeng3051/article/details/48292193

1.2 进程线程的区别
地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源 线程是处理器调度的基本单位,但进程不是 二者均可并发执行

注: 关于并发与并行

 并发:多个事件在同一时间段内一起执行

 并行:多个事件在同一时刻同时执行


在一开始,一个计算机只有一个CPU,这个CPU一次也只能运行一个任务。然而随着计算机技术的发展,一个CPU也可以“同时”运行多个任务,这就诞生了多任务。但这里的同时并不是真正的同时,操作系统通过切换各个应用来实现CPU的共享,在CPU内部各个程序其实是交替执行的。


为了进一步提高CPU利用率,多线程便诞生了。一个程序中可以运行多个线程,多个线程可以同时执行,从整个应用角度上看,这个应用好像独自拥有多个CPU一样。虽然多线程进一步提高了应用的执行效率,但是由于线程之间会共享内存资源,这也会导致一些资源同步问题,另外,线程之间的切换也会对资源有所消耗(后面会讲到)。

这里需要注意的是,如果一台电脑只有一个CPU核心,那么多线程也并没有真正的“同时”运行,它们之间需要通过相互切换来共享CPU核心,所以,只有一个CPU核心的情况下,多线程不会提高应用效率。但是,现代计算机一般都会有多个CPU,并且每个CPU可能还会有多个核心,所以在现代硬件资源条件下,多线程编程可以极大的提高应用效率。
这里写图片描述


在Java程序中,JVM负责线程的调度。线程调度是值按照特定的机制为多个线程分配CPU的使用权。

调度的模式有两种:分时调度和抢占式调度。分时调度是所有线程轮流获得CPU使用权,并平均分配每个线程占用CPU的时间;抢占式调度是根据线程的优先级别来获取CPU的使用权。JVM的线程调度模式采用了抢占式模式。


更复杂的设计 : 多线程在访问共享数据时需要进行同步(在java中需要使用synchronized关键字),某些情况下需要考虑线程的执行顺序和相互配合 上下文切换: 上CPU需要从一个线程切换到另一个线程时,它需要先保存当前线程的本地数据和程序指针,然后再加载要切换线程的本地数据和程序指针 更多的系统资源:处理需要CPU时间以外,每个线程还需要额外的内存空间来保存它的本地数据栈,更需要操作系统资源来管理多个线程,所以应用程序的线程数量一定要根据实际情况合理安排

关于多线程编程中的资源同步,请参考另一篇文章【 Java synchronized 介绍】:http://blog.csdn.net/suifeng3051/article/details/48711405


注意:在实际启动进程的时候,我们直接调用的并不是Thread子类中run方法,而是调用的Thread线程的start方法,因为线程start运行需要本地操作系统支持,start启动线程会调用操作系统native函数来支持线程运行。


实现Runnable接口比继承Thread类有更多的优势,所以我推荐大家尽量使用实现runnable接口的形式,以下是其优点

- 适合多个相同的程序代码的线程去处理同一个资源

- 可以避免java中的单继承的限制

- 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。


1. 新建状态(New):新创建了一个线程对象。

2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4. 阻塞状态(Blocked):塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

其中阻塞又可能是由以下几种情况造成:


调用 sleep(毫秒数),使线程进入“睡眠”状态。在规定的时间内,这个线程是不会运行的。 用 suspend()暂停了线程的执行。除非线程收到 resume()消息,否则不会返回“可运行”状态。 用 wait()暂停了线程的执行。除非线程收到 nofify()或者 notifyAll()消息,否则不会变成“可运行“。 线程正在等候一些 IO(输入输出)操作完成。 线程试图调用另一个对象的“同步”方法,但那个对象处于锁定状态,暂时无法使用。 3.2 线程状态图

这里写图片描述


阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。Java 提供了大量方法来支持阻塞,下面让我们逐一分析。


sleep()允许指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。典型地,sleep() 被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。


两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。


yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。


两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用。初看起来它们与 suspend() 和 resume() 方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的所有方法,阻塞时都不会释放占用的锁(如果占用了的话),而这一对方法则相反。

在这里需要重点介绍下wait()和notify()

首先,前面叙述的所有方法都隶属于 Thread 类,但是这一对却直接隶属于Object 类,也就是说,所有对象都拥有这一对方法。初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

其次,前面叙述的所有方法都可在任何位置调用,但是这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized 方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的 synchronized 方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException 异常。

最后,关于 wait() 和 notify() 方法再说明两点:

1. 调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题

2. 除了 notify(),还有一个方法 notifyAll() 也可起到类似作用,唯一的区别在于,调用 notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。


把指定的线程加入到当前线程,原本两个线程可以并发执行,join之后变成了两个线程顺序执行。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。

public class TestJoin { 

public static void main(String[] args) throws InterruptedException { 

 Thread t1 = new Thread(new JoinA(),"A"); 

 Thread t2 = new Thread(new JoinB(),"B"); 

 t1.start(); //main函数所在的主线程调用了实现了run()方法的JoinA子线程

 t1.join(); //主线程获得子线程的锁,阻塞直到子线程完成

 t2.start(); 

class JoinA implements Runnable { 

private int i; 

@Override 

public void run() { 

 while (i = 10) { 

 System.out.println(Thread.currentThread().getName() + i + " "); 

 i++; 

class JoinB implements Runnable { 

private int i; 

@Override 

public void run() { 

 while (i = 10) { 

 System.out.println(Thread.currentThread().getName() + i + " "); 

 i++; 

执行上面程序从运行结果可以看出两个线程是顺序执行的。其实是当主线程调用子线程的join()方法时,主线程变获得了子线程对象的锁,因此被子线程阻塞直到子线程退出。

我们可以看一下join()的源码:

public final synchronized void join(long millis)

throws InterruptedException {

 long base = System.currentTimeMillis();

 long now = 0;

 if (millis 0) {

 throw new IllegalArgumentException("timeout value is negative");

 if (millis == 0) {

 while (isAlive()) {

 wait(0);

 } else {

 while (isAlive()) {

 long delay = millis - now;

 if (delay = 0) {

 break;

 wait(delay);

 now = System.currentTimeMillis() - base;

join方法实现是通过wait。当main线程调用t.join()时候,main线程会获得线程对象t的锁,调用该对象的wait(),直到该对象唤醒main线程,比如退出后。


for (int i = 0; i ++i) { System. out.println(Thread.currentThread().getName() + "运行" + i); public static void main(String[] args) { TestPriority tp= new TestPriority(); Thread t1= new Thread(tp,"A" ); Thread t2= new Thread(tp,"B" ); Thread t3= new Thread(tp,"C" ); t1.setPriority(1); t2.setPriority(8); t3.setPriority(3); t1.start(); t2.start(); t3.start();

注意:不要误以为优先级越高就先执行,谁先执行还是取决于谁先取得CPU资源。


Thread h1= new Thread(new TestYield(),"A"); Thread h2= new Thread(new TestYield(),"B"); h1.start(); h2.start();

线程同步问题,当各个线程共用一个资源时,有可能导致线程同步问题。在JAVA中,是没有类似于PV操作、进程互斥等相关的方法的。JAVA的进程同步是通过synchronized()来实现的,需要说明的是,JAVA的synchronized()方法类似于操作系统概念中的互斥内存块,在JAVA中的Object类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现JAVA中简单的同步、互斥操作。关于这部分内容,请参考我的另一篇文章:
Javasynchronized介绍】:http://blog.csdn.net/suifeng3051/article/details/48711405

参考文章:
http://blog.csdn.net/bzwm/article/details/3881392
http://www.cnblogs.com/techyc/p/3286678.html


【JAVA】线程和进程 虽然进程在程序执行时产生,但进程并不是程序。程序是“死”的,进程是“活”的。程序是指编译好的二进制文件,它存放在磁盘上,不占用系统资源,是具体的;而进程存在于内存中,占用系统资源,是抽象的。当一次程序执行结束时,进程随之消失,进程所用的资源被系统回收。
JAVA面试——JVM(一)线程与内存 JVM 是可运行 Java 代码的假想计算机 ,包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收,堆 和 一个存储方法域。JVM 是运行在操作系统之上的,它与硬件没有直接的交互
还不知道如何在java中终止一个线程?快来,一文给你揭秘 工作中我们经常会用到线程,一般情况下我们让线程执行就完事了,那么你们有没有想过如何去终止一个正在运行的线程呢? 今天带大家一起来看看。
Java设置和获取线程名称及线程休眠 当想在Runnable实现类的run方法中及Callable实现类的call方法中获取当前线程的名字,就得通过Thread.currentThread()获取当前线程