Java 多线程创建和线程状态

时间:2023-03-10 05:19:52
Java 多线程创建和线程状态

  一、进程和线程

  多任务操作系统中,每个运行的任务是操作系统运行的独立程序。

  为什么引进进程的概念?

  为了使得程序能并发执行,并对并发执行的程序加以描述和控制。

  因为通常的程序不能并发执行,为使程序(含数据)能独立运行,为它配置PCB——描述和和控制进程的的运行。

  PCB记录了了操作系统所需的、用于描述进程的当前情况以及控制进程运行的全部信息。

  PCB是使一个在多道程序环境下不能独立运行的程序,成为一个能独立运行的基本单位、一个能与其它进程并发执行的基本单位。

  OS是根据PCB来对并发执行的进程进行控制和管理。

  所谓创建进程,实际上是创建进程实体中的PCB;撤销进程,实质上是撤销进程的PCB。  

  

  程序:只是一组有序指令的集合,存放于某种介质上,本身是静态的。

  进程实体:由程序段、相关数据段和PCB组成。是动态的,有生命周期。

  进程的实质是进程实体的一次执行过程。

  进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。

  1、进程

  进程是操作系统任务调度的单位,每个任务对应一个进程。

  进程是操作系统的资源管理单位,进程包含代码和数据的地址空间以及其他的资源,比如打开的文件和信号量。不同的进程的地址空间是相互隔离的。

  应用程序在启动后会创建一个进程,系统需要为进程分配ID号和内存。

  2、线程介绍

  线程是比进程更小的调度单位,它依附于进程存在,多线程是在一个进程内执行多段代码序列。

  线程有自己的程序计数器、寄存器、栈等。

  引入线程的动机在于操作系统中阻塞式I/O的存在,当一个线程所执行的I/O被阻塞的时候,同一进程中的其他线程可以使用处理器执行计算,从而提高程序的执行效率。

  3、进程和线程比较

  为什么不使用多进程?

  进程的调度代价高,而多线程的调度代价小,更高效。

  为什么线程间通信效率高?

进程中的多个线程共享进程的内存空间,当有新的线程产生时,操作系统不分配新的内存,而是让新线程共享原有进程块的内存。因此,线程间通信效率高。

  4、JVM中的进程和线程

  Java程序都运行在JVM中,每启动一个应用程序,就会启动一个JVM进程。在JVM环境中,所有的程序代码的执行都以线程实现。

Thread类提供多线程支持,应用可以创建多个并发执行的线程。

应用总是从main()方法开始运行,main()方法运行在一个线程内,称为主线程。

  每个线程都有一个调用栈,一旦创建一个新的线程,就产生一个新的调用栈

  5、应用中可以创建两类线程:用户线程和守护线程。

  用户线程执行完毕时,JVM自动关闭当前程序。

  守护线程独立于JVM,守护线程一般是由操作系统或者用户自己创建的,

  二、创建线程

  继承Thread类,实现Runnable接口和使用Timer类。

  1、继承Thread类

  Thread类实例只是一个对象,有变量和方法,创建于堆内存上,具有生命周期,可以与其他线程对象通信并协作完成特定任务。

  线程动作放在Thread中的run()方法,它代表了线程需要完成的具体任务,因此,run()被称为线程体~

  run()不是自动执行的,为了让run()执行必须调用Thread类的start()方法,调用start()方法的目的是创建新线程并执行该线程对象的run()方法,新线程与启动它的线程将并发执行。start()方法启动后立即返回,并不等待新建线程的执行,这意味着新建线程的run()方法在调用start()方法后不一定立即执行,需要等待JVM的调度。这种多线程之间的不确定性,是并发编程的难点所在。

public class InheritThread extends Thread {
private String name;
public InheritThread(String name){
this.name=name;
}
public void run(){
int c=0;
while(c<5){
System.out.println("Greetings from thread '"+name+"'!");
c++;
}
}
} class Main {
public static void main(String args[]) {
InheritThread greetingsA = new InheritThread("Inherited");
greetingsA.start();//创建新的线程
System.out.println("Thread has been started!"); }
}

 

  2、实现Runnable接口

  定义实现java.lang.Runnable接口的类,并实现该接口的run()方法,在run()中编写线程执行代码。

public class RunnableThread implements Runnable{
private String name;
public RunnableThread(String name){
this.name=name;
}
public void run(){
System.out.println("Greetings from runnable'"+name+";!");
}
} class Main {
public static void main(String args[]) {
/*创建线程,需要利用RunnableThread对象生成一个Tread类的对象,然后调用start()*/
RunnableThread greetingsB=new RunnableThread("runnable");
Thread greetingsThread=new Thread(greetingsB);
greetingsThread.start();
}
}

  这种方式的好处是,任何对象都可以实现Runnable接口,从而不受Java单继承泛型的限制。run()方法能访问类中所有的变量和方法,包括私有变量和方法。这种方式的缺点是,违反了每个对象应该有一个单一的、明确的职责的原则

  

  3、使用Timer类和TimerTask类

  Timer是一种线程设施,用于安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行,可以看成一个定时器,可以调度TimerTask。

  TimerTask是一个抽象类,实现了Runnable接口,所以具备了多线程的能力。

  一个Timer可以调度任意多个TimerTask,它会将TimerTask存储在一个队列中,顺序调度,如果想两个TimerTask并发执行,则需要创建两个Timer。看一个简单的例子:

import java.util.Timer;
import java.util.TimerTask;
class PaintTask extends TimerTask{
public void run(){
System.out.println("update");
System.out.println("repaint");
}
} public class TimeDemo {
public static void main(String args[]){
Timer timer=new Timer();
timer.schedule(new PaintTask(),1000,1*1000);  /*1秒后执行,周期为1秒*/
}
}

  我们用其它方法实现同样的功能:

/*在线程的run()中执行循环,并休眠30ms,唤醒后调用更新和重绘*/
public class Animator extends Thread{
public void run(){
while(true){
try{
Thread.sleep(2000);
}
catch (InterruptedException e){
System.out.println("Thread is interrupted"+e.getMessage());
}
updateForNextFrame();
repaint();
}
}
private void repaint(){
System.out.println("repaint");
}
private void updateForNextFrame(){
System.out.println("update");
}
public static void main(String args[]){
Animator animator=new Animator();
animator.start();//会调用run方法
System.out.println("Thread has been started!");
}
}

  三、线程状态的转换

  1、JVM的线程调度

   线程调度是指按照一定的策略为多个线程分配CPU的使用权。

  分时调度:所有线程轮流获得CPU的使用权,并平均分配占用时间。

  抢占式调度:根据线程的优先级别来获取CPU使用权,这也是JVM采用的策略。

  2、线程的5种状态

  新建(new)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)。

  阻塞意味着等待,阻塞的线程不参与线程调度,自然不占用CPU。多线程环境下,非阻塞的线程才能被调度运行。

  新线程在start()方法被调用后就进入就绪状态,随着JVM调度的程序状态的改变在运行和就绪之间切换。遇到阻塞进入阻塞状态,当run()方法结束或发生异常线程终止执行,进入死亡状态。

  3、使线程离开当前运行状态的情况有三种:

  (1) 线程的run()方法执行完毕;

  (2) 在对象上调用wait()方法(不是在线程上调用);

  (3) 线程试图调用对象的方法时不能在对象上获得锁

  4、线程状态的转换

  线程的调度程序是JVM的一部分,JVM可以决定将当前运行状态线程切换到就绪状态,以便让另一个线程获得运行机会,而不需要任何理由。对于处于就绪状态的线程,他们被选择执行的顺序是没有保障的。对于任何一组启动的线程来说,JVM不能保证其执行次序,持续时间也不能保证。

  线程状态及转换:Java 多线程创建和线程状态

  5、关于线程的阻塞状态

  阻塞状态是线程因为某种原因放弃了CPU使用权,暂时停止运行,直到线程进入就绪状态。

  阻塞情况分为3种:

  (1)等待阻塞:运行的线程执行了wait()方法,JVM把该线程放入等待池中。

  (2)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池。

  (3)其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()方法状态超时、join()方法等待线程终止或超时、或者IO处理完毕时,线程重新转入就绪状态。