- 浏览: 3966 次
- 性别:
- 来自: 上海
最新评论
一.线程的概念
线程的概念来源于计算机的操作系统的进程的概念。进程是一个程序关于某个数据集的一次运行。也就是说,进程是运行中的程序,是程序的一次运行活动。
线程和进程的相似之处在于,线程和运行的程序都是单个顺序控制流。有些教材将线程称为轻量级进程(light weight process)。线程被看作是轻量级进程是因为它运行在一个程序的上下文内,并利用分配给程序的资源和环境。
作为单个顺序控制流,线程必须在运行的程序中得到自己运行的资源,如必须有自己的执行栈和程序计数器。线程内运行的代码只能在该上下文内。因此还有些教程将执行上下文(execution context)作为线程的同义词。
所有的程序员都熟悉顺序程序的编写,如我们编写的名称排序和求素数的程序就是顺序程序。顺序程序都有开始、执行序列和结束,在程序执行的任何时刻,只有一个执行点。线程(thread)则是进程中的一个单个的顺序控制流。单线程的概念很简单,如图9.1所示。
多线程(multi-thread)是指在单个的程序内可以同时运行多个不同的线程完成不同的任务,图9.2说明了一个程序中同时有两个线程运行。
有些程序中需要多个控制流并行执行。例如,
for(int i = 0; i < 100; i++)
System.out.println("Runner A = " + i);
for(int j = 0; j < 100; j++ )
System.out.println("Runner B = "+j);
上面的代码段中,在只支持单线程的语言中,前一个循环不执行完不可能执行第二个循环。要使两个循环同时执行,需要编写多线程的程序。
二.线程的实现
多线程是一个程序中可以有多段代码同时运行,那么这些代码写在哪里,如何创建线程对象呢?
首先,我们来看Java语言实现多线程编程的类和接口。在java.lang包中定义了Runnable接口和Thread类。
Runnable接口中只定义了一个方法,它的格式为:
• public abstract void run()
这个方法要由实现了Runnable接口的类实现。Runnable对象称为可运行对象,一个线程的运行就是执行该对象的run()方法。
Thread类实现了Runnable接口,因此Thread对象也是可运行对象。同时Thread类也是线程类,该类的构造方法如下:
• public Thread()
• public Thread(Runnable target)
• public Thread(String name)
• public Thread(Runnable target, String name)
• public Thread(ThreadGroup group, Runnable target)
• public Thread(ThreadGroup group, String name)
• public Thread(ThreadGroup group, Runnable target, String name)
target为线程运行的目标对象,即线程调用start()方法启动后运行那个对象的run()方法,该对象的类型为Runnable,若没有指定目标对象,则以当前类对象为目标对象;name为线程名,group指定线程属于哪个线程组(有关线程组的概念请参考9.6节)。
Thread类的常用方法有:
• public static Thread currentThread() 返回当前正在执行的线程对象的引用。
• public void setName(String name) 设置线程名。
• public String getName() 返回线程名。
• public static void sleep(long millis) throws InterruptedException
• public static void sleep(long millis, int nanos) throws InterruptedException
使当前正在执行的线程暂时停止执行指定的毫秒时间。指定时间过后,线程继续执行。该方法抛出InterruptedException异常,必须捕获。
• public void run() 线程的线程体。
• public void start() 由JVM调用线程的run()方法,启动线程开始执行。
• public void setDaemon(boolean on) 设置线程为Daemon线程。
• public boolean isDaemon() 返回线程是否为Daemon线程。
• public static void yield() 使当前执行的线程暂停执行,允许其他线程执行。
• public ThreadGroup getThreadGroup() 返回该线程所属的线程组对象。
• public void interrupt() 中断当前线程。
• public boolean isAlive() 返回指定线程是否处于活动状态。
1.线程的创建
本节介绍如何创建和运行线程的两种方法。线程运行的代码就是实现了Runnable接口的类的run()方法或者是Thread类的子类的run()方法,因此构造线程体就有两种方法:
• 继承Thread类并覆盖它的run()方法;
• 实现Runnable接口并实现它的run()方法。
1.创建线程
继承Thread类创建线程
通过继承Thread类,并覆盖run()方法,这时就可以用该类的实例作为线程的目标对象。下面的程序定义了SimpleThread类,它继承了Thread类并覆盖了run()方法。
SimpleThread.java public class SimpleThread extends Thread{ public SimpleThread(String str){ super(str); } public void run(){ for(int i=0; i<100; i++){ System.out.println(getName()+" = "+ i); try{ sleep((int)(Math.random()*100)); }catch(InterruptedException e){} } System.out.println(getName()+ " DONE"); } }
SimpleThread类继承了Thread类,并覆盖了run()方法,该方法就是线程体。
ThreadTest.java public class ThreadTest{ public static void main(String args[]){ Thread t1 = new SimpleThread("Runner A"); Thread t2 = new SimpleThread("Runner B"); t1.start(); t2.start(); } }
在ThreadTest类的main()方法中创建了两个SimpleThread类的线程对象并调用线程类的start()方法启动线程。构造线程时没有指定目标对象,所以线程启动后执行本类的run()方法。
注意,实际上ThreadTest程序中有三个线程同时运行。请试着将下段代码加到main()方法中,分析程序运行结果。
for(int i=0; i<100; i++){ System.out.println(Thread.currentThread().getName()+"="+ i); try{ Thread.sleep((int)(Math.random()*500)); }catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+ " DONE"); }
从上述代码执行结果可以看到,在应用程序的main()方法启动时,JVM就创建一个主线程,在主线程中可以创建其他线程。
再看下面的程序:
MainThreadDemo.java public class MainThreadDemo{ public static void main(String args[]){ Thread t = Thread.currentThread(); t.setName("MyThread"); System.out.println(t); System.out.println(t.getName()); System.out.println(t.getThreadGroup().getName()); } }
该程序输出结果为:
Thread[MyThread, 5, main]
MyThread
main
上述程序在main()方法中声明了一个Thread对象t,然后调用Thread类的静态方法currentThread()获得当前线程对象。然后重新设置该线程对象的名称,最后输出线程对象、线程组对象名和线程对象名。
2.实现Runnable接口创建线程
可以定义一个类实现Runnable接口,然后将该类对象作为线程的目标对象。实现Runnable接口就是实现run()方法。
下面程序通过实现Runnable接口构造线程体。
ThreadTest.java class T1 implements Runnable{ public void run(){ for(int i=0;i<15;i++) System.out.println("Runner A="+i); } } class T2 implements Runnable{ public void run(){ for(int j=0;j<15;j++) System.out.println("Runner B="+j); } } public class ThreadTest{ public static void main(String args[]){ Thread t1=new Thread(new T1(),"Thread A"); Thread t2=new Thread(new T2(),"Thread B"); t1.start(); t2.start(); } } 下面是一个小应用程序,利用线程对象在其中显示当前时间。 ThreadTest.java //<applet code="ClockDemo.class" height="200" width="300"> //</applet> import java.awt.*; import java.util.*; import javax.swing.*; import java.text.DateFormat; public class ClockDemo extends JApplet{ private Thread clockThread = null; private ClockPanel cp=new ClockPanel(); public void init(){ getContentPane().add(cp); } public void start() { if (clockThread == null) { clockThread = new Thread(cp, "Clock"); clockThread.start(); } } public void stop() { clockThread = null; } } class ClockPanel extends JPanel implements Runnable{ public void paintComponent(Graphics g) { super.paintComponent(g); Calendar cal = Calendar.getInstance(); Date date = cal.getTime(); DateFormat dateFormatter = DateFormat.getTimeInstance(); g.setColor(Color.BLUE); g.setFont(new Font("TimesNewRoman",Font.BOLD,36)); g.drawString(dateFormatter.format(date), 50, 50); } public void run() { while (true) { repaint(); try { Thread.sleep(1000); } catch (InterruptedException e){ } } } }
3.现成的生命周期
线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。线程的状态如图所示:
下面以前面的Java小程序为例说明线程的状态:
1. 新建状态(New Thread)
当Applet启动时调用Applet的start()方法,此时小应用程序就创建一个Thread对象clockThread。
public void start() { if (clockThread == null) { clockThread = new Thread(cp, "Clock"); clockThread.start(); } }
当该语句执行后clockThread就处于新建状态。处于该状态的线程仅仅是空的线程对象,并没有为其分配系统资源。当线程处于该状态,你仅能启动线程,调用任何其他方法是无意义的且会引发IllegalThreadStateException异常(实际上,当调用线程的状态所不允许的任何方法时,运行时系统都会引发IllegalThreadStateException异常)。
注意cp作为线程构造方法的第一个参数,该参数必须是实现了Runnable接口的对象并提供线程运行的run()方法,第二个参数是线程名。
2. 就绪状态(Runnable)
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,如clockThread.start(); 语句就是启动clockThread线程。start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
3. 运行状态(Running)
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法,这里run()方法中是一个循环,循环条件是true。
public void run() { while (true) { repaint(); try { Thread.sleep(1000); } catch (InterruptedException e){} }
4. 阻塞状态(Blocked)
线程运行过程中,可能由于各种原因进入阻塞状态。所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。有关阻塞状态在后面详细讨论。
5. 死亡状态(Dead)
线程的正常结束,即run()方法返回,线程运行就结束了,此时线程就处于死亡状态。本例子中,线程运行结束的条件是clockThread为null,而在小应用程序的stop()方法中,将clockThread赋值为null。即当用户离开含有该小应用程序的页面时,浏览器调用stop()方法,将clockThread赋值为null,这样在run()的while循环时条件就为false,这样线程运行就结束了。如果再重新访问该页面,小应用程序的start()方法又会重新被调用,重新创建并启动一个新的线程。
public void stop() { clockThread = null; }
程序不能像终止小应用程序那样通过调用一个方法来结束线程(小应用程序通过调用stop()方法结束小应用程序的运行)。线程必须通过run()方法的自然结束而结束。通常在run()方法中是一个循环,要么是循环结束,要么是循环的条件不满足,这两种情况都可以使线程正常结束,进入死亡状态。
例如,下面一段代码是一个循环:
public void run(){ int i = 0; while(i<100){ i++; System.out.println("i = " + i ); } }
当该段代码循环结束后,线程就自然结束了。注意一个处于死亡状态的线程不能再调用该线程的任何方法。
4.线程的优先级与调度
Java的每个线程都有一个优先级,当有多个线程处于就绪状态时,线程调度程序根据线程的优先级调度线程运行。
可以用下面方法设置和返回线程的优先级。
• public final void setPriority(int newPriority) 设置线程的优先级。
• public final int getPriority() 返回线程的优先级。
newPriority为线程的优先级,其取值为1到10之间的整数,也可以使用Thread类定义的常量来设置线程的优先级,这些常量分别为:Thread.MIN_PRIORITY、Thread.NORM_PRIORITY、Thread.MAX_PRIORITY,它们分别对应于线程优先级的1、5和10,数值越大优先级越高。当创建Java线程时,如果没有指定它的优先级,则它从创建该线程那里继承优先级。
一般来说,只有在当前线程停止或由于某种原因被阻塞,较低优先级的线程才有机会运行。
前面说过多个线程可并发运行,然而实际上并不总是这样。由于很多计算机都是单CPU的,所以一个时刻只能有一个线程运行,多个线程的并发运行只是幻觉。在单CPU机器上多个线程的执行是按照某种顺序执行的,这称为线程的调度(scheduling)。
大多数计算机仅有一个CPU,所以线程必须与其他线程共享CPU。多个线程在单个CPU是按照某种顺序执行的。实际的调度策略随系统的不同而不同,通常线程调度可以采用两种策略调度处于就绪状态的线程。
(1) 抢占式调度策略
Java运行时系统的线程调度算法是抢占式的 (preemptive)。Java运行时系统支持一种简单的固定优先级的调度算法。如果一个优先级比其他任何处于可运行状态的线程都高的线程进入就绪状态,那么运行时系统就会选择该线程运行。新的优先级较高的线程抢占(preempt)了其他线程。但是Java运行时系统并不抢占同优先级的线程。换句话说,Java运行时系统不是分时的(time-slice)。然而,基于Java Thread类的实现系统可能是支持分时的,因此编写代码时不要依赖分时。当系统中的处于就绪状态的线程都具有相同优先级时,线程调度程序采用一种简单的、非抢占式的轮转的调度顺序。
(2) 时间片轮转调度策略
有些系统的线程调度采用时间片轮转(round-robin)调度策略。这种调度策略是从所有处于就绪状态的线程中选择优先级最高的线程分配一定的CPU时间运行。该时间过后再选择其他线程运行。只有当线程运行结束、放弃(yield)CPU或由于某种原因进入阻塞状态,低优先级的线程才有机会执行。如果有两个优先级相同的线程都在等待CPU,则调度程序以轮转的方式选择运行的线程。
5.线程状态的改变
一个线程在其生命周期中可以从一种状态改变到另一种状态,线程状态的变迁如图所示:
1.控制线程的启动和结束
当一个新建的线程调用它的start()方法后即进入就绪状态,处于就绪状态的线程被线程调度程序选中就可以获得CPU时间,进入运行状态,该线程就开始运行run()方法。
控制线程的结束稍微复杂一点。如果线程的run()方法是一个确定次数的循环,则循环结束后,线程运行就结束了,线程对象即进入死亡状态。如果run()方法是一个不确定循环,早期的方法是调用线程对象的stop()方法,然而由于该方法可能导致线程死锁,因此从1.1版开始,不推荐使用该方法结束线程。一般是通过设置一个标志变量,在程序中改变标志变量的值实现结束线程。请看下面的例子:
import java.util.*; class Timer implements Runnable{ boolean flag=true; public void run(){ while(flag){ System.out.print("\r\t"+new Date()+"..."); try{ Thread.sleep(1000); }catch(InterruptedException e){} } System.out.println("\n"+Thread.currentThread().getName()+" Stop"); } public void stopRun(){ flag = false; } } public class ThreadStop{ public static void main(String args[]){ Timer timer = new Timer(); Thread thread = new Thread(timer); thread.setName("Timer"); thread.start(); for(int i=0;i<100;i++){ System.out.print("\r"+i); try{ Thread.sleep(100); }catch(InterruptedException e){} } timer.stopRun(); } }
该程序在Timer类中定义了一个布而变量flag,同时定义了一个stopRun()方法,在其中将该变量设置为false。在主程序中通过调用该方法,从而改变该变量的值,使得run()方法的while循环条件不满足,从而实现结束线程的运行。
说明 在Thread类中除了stop()方法被标注为不推荐(deprecated) 使用外,suspend()方法和resume()方法也被标明不推荐使用,这两个方法原来用作线程的挂起和恢复。
2.线程阻塞条件
处于运行状态的线程除了可以进入死亡状态外,还可能进入就绪状态和阻塞状态。下面分别讨论这两种情况:
(1) 运行状态到就绪状态
处于运行状态的线程如果调用了yield()方法,那么它将放弃CPU时间,使当前正在运行的线程进入就绪状态。这时有几种可能的情况:如果没有其他的线程处于就绪状态等待运行,该线程会立即继续运行;如果有等待的线程,此时线程回到就绪状态状态与其他线程竞争CPU时间,当有比该线程优先级高的线程时,高优先级的线程进入运行状态,当没有比该线程优先级高的线程时,但有同优先级的线程,则由线程调度程序来决定哪个线程进入运行状态,因此线程调用yield()方法只能将CPU时间让给具有同优先级的或高优先级的线程而不能让给低优先级的线程。
一般来说,在调用线程的yield()方法可以使耗时的线程暂停执行一段时间,使其他线程有执行的机会。
(2) 运行状态到阻塞状态
有多种原因可使当前运行的线程进入阻塞状态,进入阻塞状态的线程当相应的事件结束或条件满足时进入就绪状态。使线程进入阻塞状态可能有多种原因:
① 线程调用了sleep()方法,线程进入睡眠状态,此时该线程停止执行一段时间。当时间到时该线程回到就绪状态,与其他线程竞争CPU时间。
Thread类中定义了一个interrupt()方法。一个处于睡眠中的线程若调用了interrupt()方法,该线程立即结束睡眠进入就绪状态。
② 如果一个线程的运行需要进行I/O操作,比如从键盘接收数据,这时程序可能需要等待用户的输入,这时如果该线程一直占用CPU,其他线程就得不到运行。这种情况称为I/O阻塞。这时该线程就会离开运行状态而进入阻塞状态。Java语言的所有I/O方法都具有这种行为。
③ 有时要求当前线程的执行在另一个线程执行结束后再继续执行,这时可以调用join()方法实现,join()方法有下面三种格式:
• public void join() throws InterruptedException 使当前线程暂停执行,等待调用该方法的线程结束后再执行当前线程。
• public void join(long millis) throws InterruptedException 最多等待millis毫秒后,当前线程继续执行。
• public void join(long millis, int nanos) throws InterruptedException 可以指定多少毫秒、多少纳秒后继续执行当前线程。
上述方法使当前线程暂停执行,进入阻塞状态,当调用线程结束或指定的时间过后,当前线程线程进入就绪状态,例如执行下面代码:
t.join();
将使当前线程进入阻塞状态,当线程t执行结束后,当前线程才能继续执行。
④ 线程调用了wait()方法,等待某个条件变量,此时该线程进入阻塞状态。直到被通知(调用了notify()或notifyAll()方法)结束等待后,线程回到就绪状态。
⑤ 另外如果线程不能获得对象锁,也进入就绪状态。
后两种情况在下一节讨论
说明:
sleep与 wait区别
1.sleep是线程的方法,wait是object中的方法。
2.sleep是线程被调用时,占着cpu去睡觉,其他线程不能占用cpu,os认为该线程正在工作,不会让出系统资源,wait是进入等待池等待,让出系统资源,其他线程可以占用cpu,一般wait不会加时间限制,因为如果wait的线程运行资源不够,再出来也没用,要等待其他线程调用notifyall方法唤醒等待池中的所有线程,才会在进入就绪序列等待os分配系统资源,
sleep是静态方法,是谁掉的谁去睡觉,就算是在main线程里调用了线程b的sleep方法,实际上还是main去睡觉,想让线程b去睡觉要在b的代码中掉sleep
相关推荐
总之,掌握Java多线程的生命周期、创建、启动、同步以及线程池的使用是编写高效、稳定并发程序的基础。理解这些知识点对于解决并发编程中的问题,比如资源竞争、死锁、线程安全性等问题,至关重要。在实际开发中,...
操作系统,网络协议,Java基础,Java线程,java并发,mysql,redis,分布式讲解,基础算法
`Thread`类是Java提供的一个基础类,用于封装线程的基本功能。当一个线程被创建后,调用其`start()`方法将启动该线程,线程的执行体即`run()`方法内的代码将在新的线程中运行。 #### 示例代码解析 ```java class ...
### Java线程基础 在Java语言中,线程是程序执行流的基本单元。一个标准的Java应用程序至少会有一个线程,即主线程,用于执行程序的主要逻辑。通过创建多个线程,可以实现并发执行任务,提高程序的运行效率和响应...
本篇总结涵盖了Java多线程的基础概念、创建与启动、线程调度、同步与协作以及新特性。 **一、Java线程:概念与原理** 1. **线程与进程**: - **进程**:是操作系统资源分配的基本单位,每个进程都有独立的内存...
总结来说,Java线程基础知识涵盖的内容丰富,从线程的基本概念,到线程在程序中的应用,再到线程间的通信和同步机制,都是多线程编程中不可或缺的一部分。掌握这些知识对于开发高性能、多任务并行处理的应用程序具有...
1.线程安全 1.1什么是线程安全? 就是当多个线程访问某一个类(对象或方法)时,这个类(对象或方法)始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。 1.2 synchronized关键字 可以在任意对象及...
Java线程安全是多线程编程的基础,它涉及到内存模型、同步机制等多个方面。正确理解和运用这些机制是编写高效、稳定多线程程序的关键。随着Java技术的发展,越来越多的工具和框架被引入到并发编程中,这使得开发者...
#### 一、Java线程基础知识概述 **1.1 什么是线程?** 线程是程序执行流的最小单元,是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。在Java中,线程是一种轻量级的进程,...
创建Java线程主要有两种方式: 1. 扩展`java.lang.Thread`类。通过重写`run()`方法来定义线程的行为。当调用`start()`方法时,会启动一个新的线程并执行`run()`方法。 2. 实现`java.lang.Runnable`接口。在实现类的`...
这篇“Java基础知识总结(经典)”涵盖了Java开发中的核心概念和重要知识点,旨在为初学者和有经验的开发者提供一个全面的回顾。以下是主要的学习点: 1. **Java环境配置**:在开始编程之前,必须安装Java ...
### Java线程基础详解 #### 一、线程概述 **1.1 什么是线程?** 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。在Java中,线程是程序执行流的最小单元,一个标准的...
Java是一种广泛使用的面向对象的编程语言,其基础知识涵盖了多个方面,包括语法、面向对象特性、异常处理、多线程、I/O流、网络编程、反射技术、设计模式以及JVM等核心概念。以下是对这些知识点的详细说明: 一、...
#### 一、线程基础概述 - **定义与特点**:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。Java是首个在语言级别明确支持线程特性的主流编程语言之一。在Java中,线程...
### Java线程总结教程知识点详解 #### 一、操作系统与多线程概念 - **多任务与分时操作系统**:现代操作系统(如Windows、Linux)能够实现多任务处理,即在用户看来似乎多个应用程序在“同时”运行。实际上,这是...
Java线程是并发编程的核心部分,它允许程序在同一时间执行多...总的来说,Java线程提供了强大的并发能力,是构建高并发应用的基础。通过理解和熟练运用这些概念和机制,开发者可以编写出更加高效、响应更快的应用程序。
Java 基础知识总结 Java 是一种广泛使用的编程语言,由 Sun 公司的 James Gosling 等人于 1991 年开始开发。Java 有三种技术架构:JavaEE、JavaSE 和 JavaME。JavaSE 是桌面应用程序的开发基础,JavaEE 是企业环境...
15. **多线程**:Java内置了对多线程的支持,允许开发者创建并管理多个执行线程,提高程序效率。 以上只是Java基础知识的冰山一角,深入学习Java还包括设计模式、网络编程、数据库连接、反射、注解、NIO等高级主题...
Java线程是并发编程的核心部分,它允许程序在同一时间执行多个任务,从而提高了系统的效率和响应性。在Java中,线程的管理、同步和通信是实现多线程编程的关键。 1. **线程的基本术语和概念** - **线程状态**:...