Java多线程--线程各状态如何进行切换

首先要说的是线程状态,了解了线程状态以及状态切换的过程基本上就了解了多线程。

线程的状态

1、新建状态(New):新创建了一个线程对象。 2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。 3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。 4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。 (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。 (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程实现的两种方法–初始状态

1.继承Thread类并重写它的run方法。之后创建这个子类的对象并调用start()方法。 2.通过定义实现Runnable接口的类进而实现run方法。这个类的对象在创建Thread的时候作为参数被传入,然后调用start()方法。 两种方法均需执行线程的start()方法为线程分配必须的系统资源、调度线程运行并执行线程的run()方法。 start()和run()的区别 start()为启动一个新的进程,也就是说这个线程为Runnable,只是说明这个线程在未来会被调度运行,但是并没有进入Running状态。 run()中放着的是线程实际要做的工作,也就是程序中的一段代码来交给这个线程来执行。 start()方法为线程分配必须的系统资源、调度线程运行并执行线程的run()方法,执行start()方法进入Runnable状态,不是Running状态。在Runnable状态到Running状态的过程中实际上是run()方法从未执行到执行的转变。

调度算法Runnable–Running

从Runnable到Running需要调度策略,这就涉及到线程的优先级。 线程的优先级及设置 线程的优先级是为了在多线程环境中便于系统对线程的调度,优先级高的线程将优先执行。 一个线程的优先级设置遵从以下原则: 线程创建时,子继承父的优先级。 线程创建后,可通过调用setPriority()方法改变优先级。 线程的优先级是1-10之间的正整数。

同步机制Running–锁池状态lock pool

也就是上边说介绍的三种阻塞中的同步阻塞。 在同步机制下,当资源被一个线程访问时,上锁,其他线程就进入了锁池,也就是进入了同步阻塞状态。 什么是锁? 在java中每个对象都有一个锁,一旦这个线程获得了这个对象的锁,这里的锁还有具体分为锁定类实例、锁定类对象两种不同的锁,针对不同的锁会限制其他线程对资源的访问,其他线程则在这个线程没有释放这个对象锁之前去访问锁定的资源了。 如何加锁: 关键字synchronized -加在方法上,同步方法。锁定类实例

    public class Demo1 {     

        public synchronized void m1(){     
            //...............     
        }     

        public void m2(){     
            //............     

            synchronized(this){     
                //.........     
            }     

            //........     
        }     
    }    

假设 Demo1 demo = new Demo1() Thread1 thread1 = new Thread1(demo) Thread2 thread2 = new Thread2(demo) Thread1和Thread2都是重写或者是实现了run()方法,而run方法中调用了demo.m1()。那么如果Thread1进入running状态,并且抢先执行了run(),那么thread1则获得了demo的对象锁,在没有释放demo的对象锁之前,t***重点内容***hread2是无法访问demo 的m1和m2方法的。 但是这是对象锁,如果是不同的对象,则对象之间的锁机制是不会互相限制的。 -加在代码块上

public class Demo2 {     
    Object a = new Object();     
    Object b = new Object();     

    public void m1(){     
        //............     

        synchronized(a){     
            //.........     
        }     

        //........     
    }     

    public void m2(){     
        //............     

        synchronized(b){     
            //.........     
        }     

        //........     
    }     
}   

码块锁定,锁定的对象是 变量 a 或 b; (注意,a 、b 都是非static 的)如果有一个 类实例对象: demo = new Demo2(),另外有两个线程: thread1,thread2,都调用了demo 对象,那么,在同一时间,如果 thread1调用了demo.m1(),则thread2在该时间内可以访问demo.m2();但不能访问 demo.m1() 的同步块, 因为a被 thread1锁定了。 -加在类变量、类方法上

    public class Demo3 {     
        static Object o = new Object();     

        public static synchronized void m1() {     
            //....     
        }     

        public static void m2() {     
            //...     
            synchronized (Demo3.class) {     
                //.....     
            }     
            //.....     
        }     

        public static void m3() {     
            //..........     
            try {     
                synchronized (Class.forName("Demo3")) {     
                  //............     
                }     
            } catch (ClassNotFoundException ex) {     
            }     
            //.............     
        }     

        public static void m4() {     
            //............     
           synchronized(o){     
             //........     
           }     
            //..........     
        }     
    }    

锁定的是类对象,因为同步的资源是属于类对象的,在这种情况下,如果thread1 访问了这4个方法中的任何一个, 在同一时间内其它的线程都不能访问 这4个方法。 但是需要注意的是: 如果在类中使用synchronized关键字来定义非静态方法,那将影响这个中的所有使用synchronized关键字定义的非静态方法。如果定义的是静态方法,那么将影响类中所有使用synchronized关键字定义的静态方法。静态方法和非静态方法的情况类似。但静态和非静态方法不会互相影响 同步机制存在的初衷 避免修改修饰、读脏数据以及保障操作的原子性。 锁释放和锁获取的内存语义。 当线程释放锁时,JVM会把该线程对应的本地内存中共享变量刷新到主内存中。 当线程获取时,JVM会把该线程对应的本地内存置为无效,从而使得被锁保护的邻界区代码必须从主内存中读取共享变量。 在线程的内存管理中,共享变量存放在堆中,但是为了保障线程之间的独立性,每个线程有一个栈内存,也就是每个线程所对应的本地内存了。 可以通过下面这段话来进一步理解线程 如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,它们对该成员变量是彼此影响的,也就是说一个线程对成员变量的改变会影响到另一个线程。   如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝(即便是同一个对象中的方法的局部变量,也会对每一个线程有一个拷贝),一个线程对该局部变量的改变不会影响到其他线程。   当一个线程进入到一个对象的synchronized方法,那么其他线程是可以进入这个对象的非synchronized方法,但是不可能进入synchronized方法。

Running–wait poll

  从Running状态到阻塞状态,主要是线程调用了wait()方法。   线程调用了wait()之后,释放掉锁,进入等待池,直到收到其他线程的通知才能从等待阻塞状态恢复到锁池状态,也就是同步阻塞状态。   或许你在想从一个阻塞状态恢复到另外一个阻塞状态有什么区别。其实如果线程在等待阻塞状态的它是没有机会恢复到Runnable的,而同步阻塞状态则可以。 wait()   wait()方法使得当前线程必须要等待,等到另外一个线程调用notify()或者notifyAll()方法。   当前的线程必须拥有当前对象的monitor,也即lock,就是锁。   线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行。   要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。 wait()和sleep()   当线程调用了wait()方法时,它会释放掉对象的锁。   另一个会导致线程暂停的方法:Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁。     notify()方法会唤醒一个等待当前对象的锁的线程。   如果多个线程在等待,它们中的一个将会选择被唤醒。这种选择是随意的,和具体实现有关。(线程等待一个对象的锁是由于调用了wait方法中的一个)。   被唤醒的线程是不能被执行的,需要等到当前线程放弃这个对象的锁。   被唤醒的线程将和其他线程以通常的方式进行竞争,来获得对象的锁。也就是说,被唤醒的线程并没有什么优先权,也没有什么劣势,对象的下一个线程还是需要通过一般性的竞争。 notify()   是被拥有对象的锁的线程所调用。   (This method should only be called by a thread that is the owner of this object’s monitor.)   换句话说,和wait()方法一样,notify方法调用必须放在synchronized方法或synchronized块中。   wait()和notify()方法要求在调用时线程已经获得了对象的锁,因此对这两个方法的调用需要放在synchronized方法或synchronized块中.

其他阻塞状态

sleep()   线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态,在规定时间内,这个线程是不会运行的 join()   join是Thread类的一个方法,启动线程后直接调用,即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(0)==join()意思为永远等待,只要当前线程的生命周期没有结束。 join()方法的必要性 在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

Running-Runnable

yield() 暂停当前正在执行的线程对象,并执行其他线程。 Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。 yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。 这样就基本把java中涉及到的多线程都归纳了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阿策小和尚

【Flutter 专题】10 页面间小跳转 (二)

和尚前两天尝试了一下 Flutter 中的页面跳转,主要时通过 Navigator 相关的 push 和 pop 方法进行页面跳转和基本传参,很方便...

9230
来自专栏架构师进阶

并发模式(一)Future模式 顶

常用的并发设计模式有Future模式、Master-Worker模式、Guarded Suspension模式、不变模式、生产者-消费者模式,在多线程环境中,合...

9410
来自专栏石头岛

spring cloud 四种服务下线方式

这里指已经在 Eureka 注册中心注册的服务,如果需要停用,有四种方式进行停用。

39730
来自专栏王大锤

前端入门系列之HTML

超文本标记语言 (英语:Hypertext Markup Language,简称:HTML ) 是一种用来结构化 Web 网页及其内容的标记语言。网页内容可以是...

10830
来自专栏企业平台构建

jsch实现与服务器完成文件相关操作

以前为了实现文件上传服务器的功能,于是在晚上搜了下,发现可以通过jsch来实现,同时发现jsch还是与服务器间通过一些命令完成其他操作,觉得不可思议,但是当时也...

15540
来自专栏landv

如何修复GitKraken Inotify Limit Error\idea erro - 升级Ubuntu / Linux inotify限制

GitKraken是一个非常优秀的Git客户端。如果您是软件开发人员,那么您绝对应该试试GitKraken。今天我去了我的一个存储库做了一些提交,但是GitKr...

10320
来自专栏Golang开发

Scala基础——高阶函数

在非函数式编程语言里,函数的定义包含了“函数类型”和“值”两种层面的内容。但是,在函数式编程中,函数是“头等公民”,可以像任何其他数据类型一样被传递和操作,也就...

6320
来自专栏LieBrother

老板叫你别阻塞了

继续咱们的 Java 多线程系列文章,今天再讲讲概念,这篇应该是最后一篇基础概念,接下来就直接进入 Java 多线程主题了,在后面的文章里如果有概念需要单独拿出...

8710
来自专栏专利

2000万中国专利PDF文档批量打包下载

B03 用液体或用风力摇床或风力跳汰机分离固体物料 从固体物料或流体中分离固体物料的磁或静电分离 高压电场分离

45790
来自专栏AustinDatabases

表设计与死锁,及为什么MYSQL 的死锁比别的数据库少

最近公司业务系统中的死锁较多,比较担心,并且最近在群里面,经常听到有一些群友,提到为什么MYSQL的死锁监控上比较LOW,但还好的是MYSQL的死锁不是太多。这...

14730

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励