java线程 订阅
《Java线程》是由Scott Oaks 编写, 中国电力出版社2003-5 出版的一本书籍。 展开全文
《Java线程》是由Scott Oaks 编写, 中国电力出版社2003-5 出版的一本书籍。
信息
页    数
337
作    者
Scott Oaks
定    价
39.00元
出版时间
2003-5
出版社
中国电力出版社
ISBN
9787508313184
Java线程内容介绍
线程并不是新的概念:许多操作系统
收起全文
精华内容
下载资源
问答
  • java线程
    万次阅读
    2022-06-13 20:14:53

    在Linux系统下,启动一个新的进程必须要给它分配独立的地址空间,建立众多的数据表来维护它的代码段,堆栈段和数据段,这是一种昂贵的多任务工作方式。而在进程中同时运行多个线程,多个线程彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于一个进程所花费的空间,而且线程之间彼此切换所需要的时间远远小于进程之间切换所需要的时间

    多线程并发面临的挑战:

    1. 多线程资源共享
    2. 线程死锁
    3. 锁的选择

    由于多个线程是共同占用所属进程的资源和地址空间的,如果多个线程要同时访问某个资源怎么办?

    其实在Java并发编程中,经常遇到多个线程访问同一个 共享资源,这时候作为开发者必须考虑如何维护数据一致性,这就是Java锁机制(同步问题)的来源

    Java提供了多种多线程锁机制的实现方式,常见的有:

    • Synchronized
    • ReentrantLock
    • Semaphore
    • Atomiclnteger等

    每种机制都有优缺点与各自的适用场景

    Synchronized(依赖于JVM,可靠性高,性能随Java版本升级而提高)

    在Java中Synchronized关键字被常用于维护数据一致性

    Synchronized机制是给共享资源上锁,只有拿到锁的线程才可以访问共享资源,这样就可以强制使得对共享资源的访问都是按顺序的

    Synchronized来实现多线程的同步操作是很简单的,在需要同步的对方的代码块,类,方法中加入此关键字,它能保证在同一个时刻最多只有一个线程执行同一个对象的同步代码,可保证修饰的代码在执行过程中不会被其他线程干扰。

    当线程使用Synchronized等待锁时 是不能被Thread.interrupt () 中断的,否则会造成线程死锁

    Synchronized修饰的代码具有原子性和可见性,在需要进程同步的程序中使用的频率非常高,可满足一般的进程同步要求

    ReentrantLock(可重入锁)

    ReentrantLock(可重入锁)可以被线程多次重复进行获取操作

    ReentrantLock继承接口Lock并实现了接口中定义的方法,除了能完成Synchronized所能完成的所有工作,还提供了可响应中断锁,可轮询锁请求,定时锁等 避免多线程死锁的方法

    Lock依赖于特殊的CPU,可以认为不受JVM的约束,可以通过其他语言平台来完成底层的实现

    Synchronized与ReentrantLock的性能比

    在并发量较小的多线程应用程序中,ReentrantLock与Synchronized性能相差无几

    在高并发量的情况下,Synchronized性能迅速下降几十倍,而ReetrantLock的性能仍然维持一个水准

    建议:在高并发量的情况下使用ReentrantLock

    ReentrantLock引入了两个概念:   公平锁与非公平锁

    公平锁指的是锁的分配机制是公平的,通常 先对锁提出获取请求的线程 会先被分配到锁。

    非公平锁指的是JVM按随机,就近原则分配锁的机制被称为非公平锁

    ReentrantLock在构造函数中提供了是否公平锁的初始化,默认是非公平锁

    非公平锁实际执行的效率远超公平锁,除非程序有特殊要求,否则常用非公平锁的分配机制

    ReentrantLock通过 lock ()  与 unlock () 来进行加锁与解锁操作,与Synchronized会被JVM自动解锁机制不同,ReentrantLock加锁后需要手动进行解锁

    为了避免程序出现异常而无法正常解锁的情况,使用ReentrantLock必须在finally控制块中进行解锁操作 

    使用方式的示例代码如下:

    Lock lock=new ReentrantLock();
    
        try{
            lock.lock();
        }catch(){
    
        }finally{
            lock.unlock();
        }

    Semaphore

    由于Synchronized和ReentrantLock是互斥锁,互斥是进程同步关系的一种特殊情况,相当于只存在一个临界资源,因此最多只能给一个线程提供服务。在实际复杂的多线程应用程序中可能存在多个临界资源,这时可以借助Semaphore信号量来完成多个临界资源的访问

    Semaphore基本能完成ReentrantLock的所有工作,使用方法类似,通过acquire () 与 release () 方法来获取和释放临界资源

    Semaphore.accquire () 方法默认为可响应中断锁,与ReentrantLock.lockInterruptibly () 作用效果一致,也就是说在等待临界资源的过程中可以被Thread.interrupt () 方法中断

    Semaphore也实现了 可轮询的锁请求 与 定时锁 的功能,除了方法名accquire与lock不同,使用方法与ReentrantLock几乎一致。Semaphore也提供了公平锁与非公平锁机制,也可以在构造函数中进行初始化设定

    Semaphore的锁释放操作也由手动进行,与ReentrantLock一样,为避免线程因抛出异常而无法正常释放锁的情况发生,释放锁的操作也必须在finally代码块中完成

    AtomicInteger

    AtomicInterger是一系列相同类的代表之一,常见的还有AtomicLong等,它们的实现原理都相同,区别就在于运算对象类型的不同

    在多线程应用程序中,如++i  ,i++等运算不具有原子性,是不安全的线程操作之一

    通常使用Synchronized将该操作变成一个原子操作,但JVM为此类操作特意提供了一些同步类,从而使得更加方便,并且程序运行效率变得更高,

    AtomicInteger的性能是ReentrantLock的好几倍

    Java线程锁总结:

    Synchronized

    在资源竞争不是很激烈的情况下,偶尔有同步的情况下,Synchronized是很合适的,因为编译程序通常会尽可能的优化Synchronized,可读性也很好

    ReentrantLock

    在资源竞争不激烈的情况下,性能稍微比Synchronized差一点,但是当同步很激烈情况下,Synchronized的性能一下子能下降好几十倍,而ReentrantLock依然能维持常态

    在高并发量的情况下使用ReentrantLock

    Atomic:

    在资源竞争不激烈的情况下,性能比Synchronized差一点,而资源竞争激烈的情况下,也能维持常态

    在资源竞争激烈的情况下,Atomic的性能会优于ReentrantLock一倍左右

    缺点在于,只能同步一个值,一段代码中只能出现一个Atomic的变量,多余一个同步无效,它不能在多个Atomic之间同步

    个人建议:

    写同步的时候,优先考虑Synchronized,如果有特殊的情况,再进一步优化

    ReentrantLock和Atomic如果用的不好,不仅不能提高性能,还可能带来灾难

    更多相关内容
  • Java线程(第三版).pdf[带书签]

    千次下载 热门讨论 2015-02-12 17:32:23
    Java线程(第三版)》,完整版本。作者:Scott Oaks、Henry Wong,翻译:O’Reilly Taiwan 公司,出版社:东南大学出版社,ISBN:756410239X,PDF 格式,扫描版,大小 29MB。本资源带有PDF书签,方便读者朋友阅读。 ...
  • java 线程 (一) 线程的简单使用

    万次阅读 多人点赞 2020-12-05 11:02:48
    大家好,从今天开始,我和大家一起来探讨 java线程的使用。线程java 知识体系中非常重要的一部分,我将写一系列的文章来详细的介绍 java 线程中需要掌握的知识。如果你是 java 线程的初学者,本系列文章你一定...

    大家好,从今天开始,我和大家一起来探讨 java 中线程的使用。线程是 java 知识体系中非常重要的一部分,我将写一系列的文章来详细的介绍 java 线程中需要掌握的知识。如果你是 java 线程的初学者,本系列文章你一定不要错过哦。

    本篇文章是 java 线程系列文章的第一篇文章,主要介绍进程与线程的概念和 java 中如何使用线程。

    1 进程与线程

    1.1 进程的概念

    首先我们先来介绍一下什么是进程

    进程可以理解为一个个正在执行的应用程序比如我们使用网易云音乐软件播放音乐,同时我们在使用 WPS 编辑我们的文档,并且还打开了 Chrome 浏览器查询资料等等。类似于下图:
    在这里插入图片描述
    这三个正在执行的应用程序就是我们的进程。这些进程之间是互不干扰的,即播放音乐的网易云音乐不会干扰到我们正在编辑文档的 WPS。

    进程的定义:进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。简言之,就是一些处于运行状态的程序

    1.1 线程的概念

    首先还是先来体验一下什么是线程,比如我们在使用网易云播放音乐的时候,在听歌曲的同时我们会去浏览该歌曲相关的评论或者是自己发表一个评论等。
    一般情况下,这时就有两个线程:一个是播放音乐的线程另一个是我们正在输入评论信息。这两个线程是互不干扰的,我们在发表评论时,音乐照样可以播放,并且这两个线程都是属于网易云音乐这个进程的。

    线程的定义是操作系统能够进行运算调度的最小单位它被包含在进程之中是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,多条线程并行执行不同的任务

    进程与线程的区别:进程只是一个动态的概念,代表的是程序代码在一个数据集上的一次运行过程。进程的主要作用就是获取到操作系统给它分配的硬件资源:包括用于存放程序正文、数据的磁盘和内存地址空间,以及在运行时所需要的I/O设备,已打开的文件,信号量等具体完成任务的是进程管理的线程

    一个进程至少包含一个线程。

    类似下图:
    在这里插入图片描述

    2 java 中如何创建线程

    在了解了线程和进程的基本概念后,我们来学习一下 java 中的线程如何使用。

    我们只需要关注如何创建和启动一个线程,关于进程的创建等是不需要我们考虑的,当我们启动一个 java 程序时,操作系统会创建进程。比如我们执行 main() 方法时,会启动 jvm 虚拟机,然后在虚拟机中执行我们的 main() 方法,虚拟机会分配一个线程来执行我们的 main() 方法,这个线程一般被称作主线程。

    在 java 中有两种方式创建一个线程,我们下面分别介绍。

    2.1 继承 Thread 类的方式

    定义一个线程类,该线程的任务是每隔半秒输出一个数字。

    PrintNumberThread.java:

    public class PrintNumberThread extends Thread {
        private int number;
        //线程要执行的任务写在 run() 方法中
        @Override
        public void run(){
            while(number<100){
                System.out.println(Thread.currentThread().getName()+":输出数字 "+number++);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    ThreadDemo.java:

    public class ThreadDemo {
    	//创建一个线程实例
        PrintNumberThread printNumberThread = new PrintNumberThread();
        // 给线程设置名字
        printNumberThread.setName("printNumberThread");
        //启动线程
        printNumberThread.start();
    }
    

    在该类中创建一个 PrintNumberThread 类的实例,然后调用 start() 方法启动该线程实例,如果我们不调用 printNumberThread 对象的 start() 方法,该线程是不会执行的,它就和我们的普通的 java 对象一样

    运行结果:
    在这里插入图片描述
    控制台会一直打印,直到 number 加到 100 才停止。
    此段程序运行时至少启动了两个线程,一个是主线程,一个是我们自定义的 PrintNumberThread 线程。

    2.2 实现 Runnable 接口

    由于 java 中是单继承,通过继承 Thread 类的方式有时候就不难么方便呢,于是 java 还为我们提供了另外一种方式:实现 Runnable 接口

    PrintNumberRunnable.java:

    public class PrintNumberRunnable implements Runnable {
        private int number;
        public void run() {
            while(number<100){
                System.out.println(Thread.currentThread().getName()+":输出数字 "+number++);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    Runnable 接口只有一个 run() 方法需要我们实现,与 Thread 类的 run() 方法一样,我们只需要将想要完成的任务写在 run() 方法中即可。

    ThreadDemo.java:测试类

    public class ThreadDemo {
        public static void main(String[] args) {
            //创建一个线程实例
            PrintNumberRunnable printNumberRunnable = new PrintNumberRunnable();
            Thread thread = new Thread(printNumberRunnable);
            thread.setName("printNumberRunnable");
            thread.start();
        }
    }
    

    在创建线程实例时与前面不一样,需要先创建一个 PrintNumberRunnable 的实例 printNumberRunnable,再创建一个 Thread 类的实例 thread,并需要 PrintNumberRunnable 实例作为构造函数的参数,然后再调用 thread 的 start() 方法启动线程。

    运行结果与前面的一样。

    3 运行多个线程

    经过前面的例子,大家可能没有看出使用线程和不使用线程的差别,下面我们再举一个例子来体验一下。

    编写两个线程,一个线程每隔 1 秒输出一个数字,一个线程每隔 0.5 秒输出一个字母。

    我们的目的是要感受到多个任务是可以同时执行的。比如我们用网易云听音乐时,播放音乐的同时,我们还可以写评论。

    LetterThread.java: 每隔一秒输出一个字母

    public class LetterThread extends Thread {
    
        private char[] letters = {'A','B','C','D','E'};
        //每隔 1 秒输出一个字母
        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            Long start = System.currentTimeMillis();
            System.out.println(name + " 输出字母开始时间:"+start);
            for (int i = 0;i<5;i++){
    
                System.out.println(name+" 输出字母:"+letters[i]);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Long end = System.currentTimeMillis();
            System.out.println(name + " 输出字母结束时间:"+end);
        }
    }
    

    NumberThread.java: 每隔半秒输出一个数字

    public class NumberThread extends Thread {
    
        //每隔 0.5 秒输出一个数字
        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            Long start = System.currentTimeMillis();
            System.out.println(name + " 输出数字开始时间:"+start);
            for (int i = 0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+" 输出数字:"+i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Long end = System.currentTimeMillis();
            System.out.println(name + " 输出数字结束时间:"+end);
        }
    }
    

    Test.java: 测试类

    public class Test {
        public static void main(String[] args) {
            NumberThread numberThread = new NumberThread();
            numberThread.setName("numberThread");
            //启动打印数字的线程
            numberThread.start();
    
            //启动打印字母的线程
            LetterThread letterThread = new LetterThread();
            letterThread.setName("letterThread");
            letterThread.start();
        }
    }
    

    运行结果:
    在这里插入图片描述

    从结果我们可以看出,两个线程几乎是在同一时刻启动,在同一时刻停止,差不多都运行了 5 秒。所以我们可以说他们是同时在执行的,即都在这 5 秒里完成了自己的任务。

    大家还需要注意一点:虽然在代码中 numberThread.start(); 写在了 letterThread.start(); 的前面,但是不代表 numberThread 就一定先于 letterThread 启动;start() 方法只是告诉 CPU 当前线程需要启动,但是什么时候启动,就由 CPU 来决定了,我们就不能再控制它了。我们唯一能做的就是重写 run() 方法来定义一个线程需要完成什么任务和调用 start() 方法来启动这个线程。

    所以前面的代码运行的时候也可能会出现 letterThread 比 numberThread 先启动的情况:
    在这里插入图片描述

    4 start() 方法 和 run() 方法

    在前面的所有示例代码中,我们都重写了 Thread 类或者 Runabble 接口的 run() 方法。并在 run() 方法中定义这个线程需要完成的任务。但是启动线程时不是直接去调用线程实例的 run() 方法,而是通过调用 start() 方法来启动线程。需要注意的是,调用了 start() 方法后,线程可能不会立即执行,它需要等待 cpu 来调度。cpu 在处理该线程的任务时,其实就是执行我们定义的 run() 方法。

    如果我们直接在代码中调用线程实例的 run() 方法,是没有多线程的效果的,run() 方法直接就在 main 线程中执行了,与执行一个普通方法没有什么区别。

    所以我们应该调用线程实例的 start() 方法来启动一个线程。而不是直接调用 run() 方法。

    下面我们还是拿前面的交替打印字母和数字的例子做一个举例:
    使用 start() 方法启动两个线程的代码与前面一致,就不在写了。我们将直接调用 run() 方法的代码做一个简单例子:

    Test01.java:

    public class Test01 {
        public static void main(String[] args) {
            NumberThread numberThread = new NumberThread();
            numberThread.setName("numberThread");
            //调用 run() 方法
            numberThread.run();
    
            LetterThread letterThread = new LetterThread();
            letterThread.setName("letterThread");
            //调用 run() 方法
            letterThread.run();
        }
    }
    

    在这里插入图片描述
    从上图中我们可以看出以下两点:
    (1)输出数字的线程的名字不是我们设置的 numberThread ,而是 main 线程;输出字母的线程的名字也不是我们设置的 letterThread ,而是 main 线程。所以印证了我们前面的说法:如果直接调用线程实例对象的 run() 方法不会启动一个新的线程,而是直接在 main(主)线程中执行 run() 方法。
    (2)从上图中我们可以看出,这段代码大约执行了 10 秒,

    5 总结

    本文就到此结束了,主要介绍了如下几个知识点:

    • 线程与进程
    • java 中如何创建线程、启动一个线程
    • start() 方法和 run() 方法的区别

    示例代码地址:https://github.com/coderllk/threadDemo/tree/main/threadDemoOne

    展开全文
  • java 线程知识点总结

    千次阅读 2022-04-22 12:00:09
    程序、进程、线程: 程序:是指含有指令和数据的文件,被存储在磁盘或其他的数据设备中,也就是说程序是静态的代码; 进程:是程序的一次执行过程,是代码在数据集合上的一次运行活动,是系统资源分配和调度的...

    程序、进程、线程:

    程序:是指含有指令和数据的文件,被存储在磁盘或其他的数据设备中,也就是说程序是静态的代码;

    进程:是程序的一次执行过程,是代码在数据集合上的一次运行活动,是系统资源分配和调度的基本单位;

    线程:是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源;

    线程的创建:

    1. 继承Thread创建线程:
      1. 扩展 Thread类,

        重写父类的run()方法,规定线程的具体操作

        创建扩展类的对象,调用start()启动线程

    2.      实现Runnable接口创建线程:
      1.   创建实现Runnable接口类,实现run()方法

        使用实现类的对象作为目标对象,创建Thread对象

        调用start()启动线程       

    3.  使用Callable和Future创建线程(了解)
      1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建Callable实现类的对象

        使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值

        使用FutureTask对象作为Thread对象的目标对象创建并启动新线程

        调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

    4. 通过实现接口和继承类两种方式创建对象的比较:

      采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。还便于目标对象的共享

      使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。不能再继承其他类

    线程的生命周期:

     这个图讲的很详细:

    • yield()让出一次执行机会,使线程从运行状态转到可运行状态
    • join()等待这个线程结束;也就是,t.join()方法阻塞调用此方法的线程,直到线程t完成,此线程再继续;通常用于在main()主线程内,等待其它线程完成再结束main()主线程
    •  
    • sleep方法:
    •             

     

    线程的同步:

    1. volatile关键字:

      简单,保证数据的可见性。就是一个线程在对主内存的某一份数据进行更改时,改完之后会立刻刷新到主内存。并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。

      1. ​​​​​​​

         
    2. synchronized关键字:对对象自动上锁

      同步普通方法(实例方法)上锁,锁是当前实例对象 ,进入方法前要获得当前实例的锁,方法执行完释放。

      同步静态方法,锁是当前类的class对象 ,进入方法前前要获得当前类对象的锁,方法执行完释放。

      同步代码块,要指定锁的对象,是可以是实例对象,也可以是类对象,进入同步代码块前要获得给定对象的锁,代码块执行完释放
      1. ​​​​​​​
    3. Lock接口和 ReentrantLock类:需要 显示的获得、释放锁。实现同步更灵活

         常用方法:lock()获取锁;及unLock()释放锁

      1. ​​​​​​​​​​​​​​

         

    4. wait():的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。直到其他线程调用此对象的 notify() 方法或  notifyAll() 方法,当前线程被唤醒(进入“就绪状态”)

      notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程

     

    展开全文
  • Java线程的状态及主要转化方法

    千次阅读 2021-09-13 10:28:14
    1 操作系统中的线程状态转换 首先我们来看看操作系统中的线程状态转换。 在现在的操作系统中,线程是被视为轻量级进程的,所以操作系统线程的状态其实和操作系统进程的状态是一致...4.2 Java线程的6个状态 // Thre

    1 操作系统中的线程状态转换

    首先我们来看看操作系统中的线程状态转换。

    在现在的操作系统中,线程是被视为轻量级进程的,所以操作系统线程的状态其实和操作系统进程的状态是一致的

    系统进程/线程转换图

    操作系统线程主要有以下三个状态:

    • 就绪状态(ready):线程正在等待使用CPU,经调度程序调用之后可进入running状态。
    • 执行状态(running):线程正在使用CPU。
    • 等待状态(waiting): 线程经过等待事件的调用或者正在等待其他资源(如I/O)。

    2 Java线程的6个状态

    // Thread.State 源码
    public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
    

    2.1 NEW

    处于NEW状态的线程此时尚未启动。这里的尚未启动指的是还没调用Thread实例的start()方法。

    private void testStateNew() {
        Thread thread = new Thread(() -> {});
        System.out.println(thread.getState()); // 输出 NEW 
    }
    

    从上面可以看出,只是创建了线程而并没有调用start()方法,此时线程处于NEW状态。

    关于start()的两个引申问题

    1. 反复调用同一个线程的start()方法是否可行?
    2. 假如一个线程执行完毕(此时处于TERMINATED状态),再次调用这个线程的start()方法是否可行?

    要分析这两个问题,我们先来看看start()的源码:

    public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
    
        group.add(this);
    
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
    
            }
        }
    }
    

    我们可以看到,在start()内部,这里有一个threadStatus的变量。如果它不等于0,调用start()是会直接抛出异常的。

    我们接着往下看,有一个native的start0()方法。这个方法里并没有对threadStatus的处理。到了这里我们仿佛就拿这个threadStatus没辙了,我们通过debug的方式再看一下:

    @Test
    public void testStartMethod() {
        Thread thread = new Thread(() -> {});
        thread.start(); // 第一次调用
        thread.start(); // 第二次调用
    }
    

    我是在start()方法内部的最开始打的断点,叙述下在我这里打断点看到的结果:

    • 第一次调用时threadStatus的值是0。
    • 第二次调用时threadStatus的值不为0。

    查看当前线程状态的源码:

    // Thread.getState方法源码:
    public State getState() {
        // get current thread state
        return sun.misc.VM.toThreadState(threadStatus);
    }
    
    // sun.misc.VM 源码:
    public static State toThreadState(int var0) {
        if ((var0 & 4) != 0) {
            return State.RUNNABLE;
        } else if ((var0 & 1024) != 0) {
            return State.BLOCKED;
        } else if ((var0 & 16) != 0) {
            return State.WAITING;
        } else if ((var0 & 32) != 0) {
            return State.TIMED_WAITING;
        } else if ((var0 & 2) != 0) {
            return State.TERMINATED;
        } else {
            return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
        }
    }
    

    所以,我们结合上面的源码可以得到引申的两个问题的结果:

    两个问题的答案都是不可行,在调用一次start()之后,threadStatus的值会改变(threadStatus !=0),此时再次调用start()方法会抛出IllegalThreadStateException异常。

    比如,threadStatus为2代表当前线程状态为TERMINATED。

    2.2 RUNNABLE

    表示当前线程正在运行中。处于RUNNABLE状态的线程在Java虚拟机中运行,也有可能在等待CPU分配资源。

    Java中线程的RUNNABLE状态

    看了操作系统线程的几个状态之后我们来看看Thread源码里对RUNNABLE状态的定义:

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    

    Java线程的RUNNABLE状态其实是包括了传统操作系统线程的readyrunning两个状态的。

    2.3 BLOCKED

    阻塞状态。处于BLOCKED状态的线程正等待锁的释放以进入同步区。

    我们用BLOCKED状态举个生活中的例子:

    假如今天你下班后准备去食堂吃饭。你来到食堂仅有的一个窗口,发现前面已经有个人在窗口前了,此时你必须得等前面的人从窗口离开才行。
    假设你是线程t2,你前面的那个人是线程t1。此时t1占有了锁(食堂唯一的窗口),t2正在等待锁的释放,所以此时t2就处于BLOCKED状态。

    2.4 WAITING

    等待状态。处于等待状态的线程变成RUNNABLE状态需要其他线程唤醒。

    调用如下3个方法会使线程进入等待状态:

    • Object.wait():使当前线程处于等待状态直到另一个线程唤醒它;
    • Thread.join():等待线程执行完毕,底层调用的是Object实例的wait方法;
    • LockSupport.park():除非获得调用许可,否则禁用当前线程进行线程调度。

    我们延续上面的例子继续解释一下WAITING状态:

    你等了好几分钟现在终于轮到你了,突然你们有一个“不懂事”的经理突然来了。你看到他你就有一种不祥的预感,果然,他是来找你的。

    他把你拉到一旁叫你待会儿再吃饭,说他下午要去作报告,赶紧来找你了解一下项目的情况。你心里虽然有一万个不愿意但是你还是从食堂窗口走开了。

    此时,假设你还是线程t2,你的经理是线程t1。虽然你此时都占有锁(窗口)了,“不速之客”来了你还是得释放掉锁。此时你t2的状态就是WAITING。然后经理t1获得锁,进入RUNNABLE状态。

    要是经理t1不主动唤醒你t2(notify、notifyAll..),可以说你t2只能一直等待了。

    2.5 TIMED_WAITING

    超时等待状态。线程等待一个具体的时间,时间到后会被自动唤醒。

    调用如下方法会使线程进入超时等待状态:

    • Thread.sleep(long millis):使当前线程睡眠指定时间;
    • Object.wait(long timeout):线程休眠指定时间,等待期间可以通过notify()/notifyAll()唤醒;
    • Thread.join(long millis):等待当前线程最多执行millis毫秒,如果millis为0,则会一直执行;
    • LockSupport.parkNanos(long nanos): 除非获得调用许可,否则禁用当前线程进行线程调度指定时间;
    • LockSupport.parkUntil(long deadline):同上,也是禁止线程进行调度指定时间;

    我们继续延续上面的例子来解释一下TIMED_WAITING状态:

    到了第二天中午,又到了饭点,你还是到了窗口前。

    突然间想起你的同事叫你等他一起,他说让你等他十分钟他改个bug。

    好吧,你说那你就等等吧,你就离开了窗口。很快十分钟过去了,你见他还没来,你想都等了这么久了还不来,那你还是先去吃饭好了。

    这时你还是线程t1,你改bug的同事是线程t2。t2让t1等待了指定时间,此时t1等待期间就属于TIMED_WATING状态。

    t1等待10分钟后,就自动唤醒,拥有了去争夺锁的资格。

    2.6 TERMINATED

    终止状态。此时线程已执行完毕。

    3 线程状态的转换

    根据上面关于线程状态的介绍我们可以得到下面的线程状态转换图

    线程状态转换图

    3.1 BLOCKED与RUNNABLE状态的转换

    我们在上面说到:处于BLOCKED状态的线程是因为在等待锁的释放。假如这里有两个线程a和b,a线程提前获得了锁并且暂未释放锁,此时b就处于BLOCKED状态。我们先来看一个例子:

    @Test
    public void blockedTest() {
    
        Thread a = new Thread(new Runnable() {
            @Override
            public void run() {
                testMethod();
            }
        }, "a");
        Thread b = new Thread(new Runnable() {
            @Override
            public void run() {
                testMethod();
            }
        }, "b");
    
        a.start();
        b.start();
        System.out.println(a.getName() + ":" + a.getState()); // 输出?
        System.out.println(b.getName() + ":" + b.getState()); // 输出?
    }
    
    // 同步方法争夺锁
    private synchronized void testMethod() {
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    

    初看之下,大家可能会觉得线程a会先调用同步方法,同步方法内又调用了Thread.sleep()方法,必然会输出TIMED_WAITING,而线程b因为等待线程a释放锁所以必然会输出BLOCKED。

    其实不然,有两点需要值得大家注意,一是在测试方法blockedTest()内还有一个main线程,二是启动线程后执行run方法还是需要消耗一定时间的

    测试方法的main线程只保证了a,b两个线程调用start()方法(转化为RUNNABLE状态),如果CPU执行效率高一点,还没等两个线程真正开始争夺锁,就已经打印此时两个线程的状态(RUNNABLE)了。

    当然,如果CPU执行效率低一点,其中某个线程也是可能打印出BLOCKED状态的(此时两个线程已经开始争夺锁了)。

    这时你可能又会问了,要是我想要打印出BLOCKED状态我该怎么处理呢?BLOCKED状态的产生需要两个线程争夺锁才行。那我们处理下测试方法里的main线程就可以了,让它“休息一会儿”,调用一下Thread.sleep()方法。

    这里需要注意的是main线程休息的时间,要保证在线程争夺锁的时间内,不要等到前一个线程锁都释放了你再去争夺锁,此时还是得不到BLOCKED状态的。

    我们把上面的测试方法blockedTest()改动一下:

    public void blockedTest() throws InterruptedException {
        ······
        a.start();
        Thread.sleep(1000L); // 需要注意这里main线程休眠了1000毫秒,而testMethod()里休眠了2000毫秒
        b.start();
        System.out.println(a.getName() + ":" + a.getState()); // 输出?
        System.out.println(b.getName() + ":" + b.getState()); // 输出?
    }
    

    在这个例子中两个线程的状态转换如下

    • a的状态转换过程:RUNNABLE(a.start()) -> TIMED_WATING(Thread.sleep())->RUNABLE(sleep()时间到)->BLOCKED(未抢到锁) -> TERMINATED
    • b的状态转换过程:RUNNABLE(b.start()) -> BLOCKED(未抢到锁) ->TERMINATED

    斜体表示可能出现的状态, 大家可以在自己的电脑上多试几次看看输出。同样,这里的输出也可能有多钟结果。

    3.2 WAITING状态与RUNNABLE状态的转换

    根据转换图我们知道有3个方法可以使线程从RUNNABLE状态转为WAITING状态。我们主要介绍下Object.wait()Thread.join()

    Object.wait()

    调用wait()方法前线程必须持有对象的锁。

    线程调用wait()方法时,会释放当前的锁,直到有其他线程调用notify()/notifyAll()方法唤醒等待锁的线程。

    需要注意的是,其他线程调用notify()方法只会唤醒单个等待锁的线程,如有有多个线程都在等待这个锁的话不一定会唤醒到之前调用wait()方法的线程。

    同样,调用notifyAll()方法唤醒所有等待锁的线程之后,也不一定会马上把时间片分给刚才放弃锁的那个线程,具体要看系统的调度。

    Thread.join()

    调用join()方法,会一直等待这个线程执行完毕(转换为TERMINATED状态)。

    我们再把上面的例子线程启动那里改变一下:

    public void blockedTest() {
        ······
        a.start();
        a.join();
        b.start();
        System.out.println(a.getName() + ":" + a.getState()); // 输出 TERMINATED
        System.out.println(b.getName() + ":" + b.getState());
    }
    

    要是没有调用join方法,main线程不管a线程是否执行完毕都会继续往下走。

    a线程启动之后马上调用了join方法,这里main线程就会等到a线程执行完毕,所以这里a线程打印的状态固定是TERMINATED

    至于b线程的状态,有可能打印RUNNABLE(尚未进入同步方法),也有可能打印TIMED_WAITING(进入了同步方法)。

    3.3 TIMED_WAITING与RUNNABLE状态转换

    TIMED_WAITING与WAITING状态类似,只是TIMED_WAITING状态等待的时间是指定的。

    Thread.sleep(long)

    使当前线程睡眠指定时间。需要注意这里的“睡眠”只是暂时使线程停止执行,并不会释放锁。时间到后,线程会重新进入RUNNABLE状态。

    Object.wait(long)

    wait(long)方法使线程进入TIMED_WAITING状态。这里的wait(long)方法与无参方法wait()相同的地方是,都可以通过其他线程调用notify()或notifyAll()方法来唤醒。

    不同的地方是,有参方法wait(long)就算其他线程不来唤醒它,经过指定时间long之后它会自动唤醒,拥有去争夺锁的资格。

    Thread.join(long)

    join(long)使当前线程执行指定时间,并且使线程进入TIMED_WAITING状态。

    我们再来改一改刚才的示例:

    public void blockedTest() {
        ······
        a.start();
        a.join(1000L);
        b.start();
        System.out.println(a.getName() + ":" + a.getState()); // 输出 TIEMD_WAITING
        System.out.println(b.getName() + ":" + b.getState());
    }
    

    这里调用a.join(1000L),因为是指定了具体a线程执行的时间的,并且执行时间是小于a线程sleep的时间,所以a线程状态输出TIMED_WAITING。

    b线程状态仍然不固定(RUNNABLE或BLOCKED)。

    3.4 线程中断

    在某些情况下,我们在线程启动后发现并不需要它继续执行下去时,需要中断线程。目前在Java里还没有安全直接的方法来停止线程,但是Java提供了线程中断机制来处理需要中断线程的情况。

    线程中断机制是一种协作机制。需要注意,通过中断操作并不能直接终止一个线程,而是通知需要被中断的线程自行处理。

    简单介绍下Thread类里提供的关于线程中断的几个方法:

    • Thread.interrupt():中断线程。这里的中断线程并不会立即停止线程,而是设置线程的中断状态为true(默认是flase);
    • Thread.interrupted():测试当前线程是否被中断。线程的中断状态受这个方法的影响,意思是调用一次使线程中断状态设置为true,连续调用两次会使得这个线程的中断状态重新转为false;
    • Thread.isInterrupted():测试当前线程是否被中断。与上面方法不同的是调用这个方法并不会影响线程的中断状态。

    在线程中断机制里,当其他线程通知需要被中断的线程后,线程中断的状态被设置为true,但是具体被要求中断的线程要怎么处理,完全由被中断线程自己而定,可以在合适的实际处理中断请求,也可以完全不处理继续执行下去。


    参考资料

    展开全文
  • Java线程Dump分析

    千次阅读 2019-10-29 08:48:43
    每一个Java虚拟机都有及时生成所有线程在某一点状态的thread-dump的能力,虽然各个 Java虚拟机打印的thread dump略有不同,但是 大多都提供了当前活动线程的快照,及JVM中所有Java线程的堆栈跟踪信息,堆栈信息一般...
  • 本文将为您提供一个教程,使您可以确定活动应用程序Java线程中保留了多少Java堆空间 。 将提供来自Oracle Weblogic 10.0生产环境的真实案例研究,以使您更好地理解分析过程。 我们还将尝试证明过多的垃圾回收或...
  • Java线程安全的集合

    千次阅读 2022-03-15 15:22:04
    Java有哪些线程安全的集合?有哪些是常用的,有哪些是不常用的?都是怎么实现线程安全的?
  • JAVA线程之间的通信

    千次阅读 2021-11-28 14:40:48
    线程通信涉及到的三个方法: (1)wait():一旦执行此方法当前线程进入阻塞状态,并释放同步监视器 (2)notify():一旦执行此方法就会唤醒被wait的另一个线程。如果存在多个被wait的线程,唤醒优先级高的线程。 (3...
  • 查看java线程

    千次阅读 2020-02-16 14:43:53
    我们知道java线程使用的是底层操作系统"轻量级线程"+"内核线程"的1:1模型,那么我们如何查看一个运行的java进程中线程数呢? 1、linux上查看进程中线程数: 主要有以下三种方法: 1)top -Hp pid $ top -Hp ...
  • Java线程状态RUNNABLE详解

    千次阅读 2020-11-03 20:53:50
    Java虚拟机层面所暴露给我们的状态,与操作系统底层的线程状态是两个不同层面的事。具体而言,这里说的 Java 线程状态均来自于 Thread 类下的 State 这一内部枚举类中所定义的状态: 什么是 RUNNABLE? 直接看它的 ...
  • JAVA线程栈用多少内存

    千次阅读 多人点赞 2022-05-15 22:59:35
    1.为什么叫私有线程栈呢 因为每个线程都有自己独享的栈空间默认大小为1m, 栈的空间大小能决定在所有方法不出栈的情况下该线程能执行多少个方法 2.为了方便测试我把栈大小设置为-Xss128k 这个大小差不多可以执行...
  • Java线程详解(深度好文)

    万次阅读 多人点赞 2018-09-16 21:03:21
    Java线程:概念与原理 一、进程与线程    进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间)。进程不依赖于线程而独立存在,一个进程中可以启动多个线程。比如...
  • java线程join方法

    千次阅读 2020-04-16 23:04:39
    java线程join方法 1.join方法 ​ join方法的作用是进行线程插队,也就是说调用了join方法的线程相对于调用它的上级线程拥有跟高的执行权。调用join方法的线程的上级线程必须等待调用join方法的线程执行完成才能继续...
  • Java线程和操作系统线程的关系

    万次阅读 多人点赞 2018-04-18 18:06:01
    1.操作系统线程模型1.1 线程实现在用户空间下 当线程在用户空间下实现时,操作系统对线程的存在一无所知,操作系统只能看到进程,而不能看到线程。所有的线程都是在用户空间实现。在操作系统看来,每一个进程只有一...
  • java线程状态(六种&五种)

    千次阅读 2021-11-19 10:36:41
    java线程的状态常见的有两种说法,一种是将其分为6种一种是分为5种 1. 六种: 划分依据:主要是从java代码的角度来进行划分。 1. 新建状态 : 使用new关键字创建一个thread对象,刚刚创建出的这个线程就处于...
  • java 线程图解[基础篇]

    千次阅读 2022-03-12 11:51:14
    java线程基础图解,帮助快速入门
  • 获取Java线程转储的常用方法

    万次阅读 2021-01-07 21:26:48
    1. 线程转储简介 线程转储(Thread Dump)就是JVM中...Java中的线程模型, 直接使用了操作系统的线程调度模型, 只进行简单的封装。 线程调用栈, 也称为方法调用栈。 比如在程序执行过程中, 有一连串的方法调用链: ......
  • 详解Java线程中的join()方法

    千次阅读 多人点赞 2021-03-09 20:02:07
    对于Java中的join()方法的描述,我们首先来看下源码当中的解释 从源码当中的描述,我们只能知道join方法的作用是让线程陷入等待。其中可以传递以毫秒为单位的等待时间参数,如果传递参数为0,则线程会一直等待。 ...
  • JAVA 线程延迟的几种操作

    千次阅读 2021-09-24 09:46:22
    我们常用的延迟操作,在JS或者python 3中有异步操作,处理这种问题是比较简单,在java中也有几种异步处理操作的方式,这里我们来看一下几种操作 第一种,主线程直接延迟 1秒 System.out.println("主线程 -> 1...
  • Java线程的实现方式详解(Java基础)

    千次阅读 多人点赞 2020-05-29 18:15:48
    你好我是辰兮,很高兴你能来阅读,本篇为Java基础之多线程的实现讲解,基础知识的讲解,相关的更多,面试知识已经提前整理好文章可以阅读学习,希望对初学者有帮助。 线程进程基础知识参考: 进程线程的面试问题小...
  • Java线程的状态

    万次阅读 2022-02-23 16:38:29
    运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程...
  • Java线程中的wait、notify和notifyAll解析

    万次阅读 2022-04-25 14:48:22
    顾客去包子铺买包子,告知老板自身需求后,进入等待(调用wait()方法)老板处理的过程,此时顾客的状态为 WAITING,老板做好包子后,告知(调用notify()方法)顾客包子...线程间的通信的主要思想是生产者消费者机制。
  • java线程同步的实现方式

    万次阅读 多人点赞 2019-03-08 01:47:21
    这里抛砖引玉,为何要使用同步?...下面总结一些java线程实现同步方式,大致有下面几种: 1.同步方法 使用 synchronized关键字,可以修饰普通方法、静态方法,以及语句块。由于java的每个对象都有一个内置锁...
  • Java线程中的sleep方法详解

    千次阅读 2021-03-14 20:19:09
    关于线程的sleep方法 static void sleep(long mills) 1、静态方法:Thread.sleep(1000); 2、参数是毫秒 3、作用:让当前线程进入休眠,进入“阻塞”状态,放弃占有CPU时间片,让给其他线程使用。 public class ...
  • Java线程面试题合集(含答案)

    万次阅读 多人点赞 2018-06-18 14:30:48
    来源:Java线程面试题下面是我自己收集整理的Java线程相关的面试题,可以用它来好好准备面试。 参考文档: 《Java核心技术 卷一》 Java线程面试题 Top 50:http://www.importnew.com/12773.html JAVA多线程和并发...
  • 一文弄懂Java线程安全队列

    千次阅读 2019-03-01 17:26:45
    java中所有队列都继承至java.util.Queue接口,该接口定义了以下三组方法: 方法名 抛出异常 返回特殊值 插入 add(e) offer(e) 移除 remove() poll() 检查 element() peek() Java提...
  • Java线程片——线程的关闭

    万次阅读 2018-10-24 11:38:46
    在我们寻求Java线程的关闭关闭方式的时候,我们也许会按住Ctrl键进入到Thread类中阅读源码,很快的我们就能找到一个stop()方法。似乎stop()方法就是结束线程的方法,但是事实恰恰与我们的猜想相反。stop()方法太过于...
  • 3.java进程的基本操作 3.1.创建进程 方法一,直接使用 Thread // 构造方法的参数是给线程指定名字,,推荐给线程起个名字(用setName()也可以) Thread t1 = new Thread("t1") { @Override // run 方法内实现了要...
  • 关于Java线程,你必须掌握的知识点

    千次阅读 多人点赞 2020-03-01 17:42:53
    Java线程 我为什么要写这篇文章?主要有一下几点: 加深自己对Java线程的理解 勘误网上有些错误的知识和认知 通过写文章,来交流思想 网上关于Java线程的文章数不胜数,实在太多了,我又怎么写一篇让其他人一看...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,935,828
精华内容 774,331
关键字:

java线程