• 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不能保证其执行次序,持续时间也不能保证。

      线程状态及转换:

      5、关于线程的阻塞状态

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

      阻塞情况分为3种:

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

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

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

  • 相关阅读:
    顺序栈的实现
    创建HttpRequest传输数据
    .net Json对象序列化和反序列化
    SqlBulkCopy类数据导入
    两表更新:根据条件从一个表里面查询出符合条件的结果更新另一个表
    javascript实现绚丽效果
    SQL SERVER 表结构修改
    SqlCacheDependency Application
    运用泛型实现增删改
    实现数组的全排列(百度笔试题)
  • 原文地址:https://www.cnblogs.com/bigbigbigo/p/8424909.html
Copyright © 2020-2023  润新知