大厂之路一由浅入深、并行基础、源码分析一Thread中的静态方法和实例方法

Thread类中的方法调用方式:

  • 学习Thread类中的方法是学习多线程的第一步。在学习多线程之前特别提出一点,调用Thread中的方法的时候,在线程类中,有两种方式,一定要理解这两种方式的区别:
    • this.XXX()
      这种调用方式表示的线程是线程实例本身
    • Thread.currentThread.XXX()或Thread.XXX()
      上面两种写法是一样的意思。这种调用方式表示的线程是正在执行Thread.currentThread.XXX()所在代码块的线程
  • 当然,这么说,肯定有人不理解两者之间的差别。没有关系,之后会讲清楚,尤其是在讲Thread构造函数这块。讲解后,再回过头来看上面2点,会加深理解。


  • Thread类中的实例方法
    • 从Thread类中的实例方法和类方法的角度讲解Thread中的方法,这种区分的角度也有助于理解多线程中的方法。实例方法,只和实例线程(也就是new出来的线程)本身挂钩,和当前运行的是哪个线程无关
  • 看下Thread类中的实例方法

  • start()
    • start()方法的作用讲得直白点就是通知 “线程规划器”,此线程可以运行了,正在等待CPU调用线程对象得run()方法,产生一个异步执行的效果。通过start()方法产生得到结论,先看下代码:
package com.wwj.text;

public class MyThread extends Thread {

    @Override
    public void run() {
        try{
            for(int i=0 ; i<3 ;i++){
                Thread.sleep((int)Math.random()*1000);
                System.out.println("run="+Thread.currentThread().getName());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException{
        MyThread myThread = new MyThread();
        myThread.start();
        try{
            for(int i=0 ; i<3 ; i++){
                Thread.sleep((int)Math.random()*1000);
                System.out.println("run="+Thread.currentThread().getName());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
}
  • 结果:
run=main
run=Thread-0
run=main
run=main
run=Thread-0
run=Thread-0
  • 结果分析:CPU执行哪个线程的代码具有不确定性。 再看另外一个例子:
package com.wwj.text;

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
    public static void main(String[] args) throws InterruptedException{
        MyThread myThread = new MyThread();
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        myThread.start();
        myThread2.start();
        myThread1.start();

    }
}
  • 结果:
Thread-0
Thread-1
Thread-2
  • 结果分析:调用start()方法的顺序不代表线程启动的顺序,线程启动顺序具有不确定性。

  • run()
    • 线程开始执行,虚拟机调用的是线程run()方法中的内容。稍微改一下之前的例子看一下:
package com.wwj.text;

public class MyThread extends Thread {

    @Override
    public void run() {
        try {
            for (int i = 0; i < 3; i++) {
                Thread.sleep((int)(Math.random() * 1000));
                System.out.println("run = " + Thread.currentThread().getName());
            }
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.run();
        try {
            for (int i = 0; i < 3; i++) {
                Thread.sleep((int) (Math.random() * 1000));
                System.out.println("run = " + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 结果:
run = main
run = main
run = main
run = main
run = main
run = main
  • 结果分析:看到打印了6次的"run = main",说明如果只有run()没有start(),全部被main函数执行。换句话说,只有run()而不调用start()启动线程是没有任何意义的。

  • isAlive():测试线程是否处于活动状态,只要线程启动且没有终止,方法返回的就是true。看一下例子:
package com.wwj.text;

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("run=" + this.isAlive());
    }

    public static void main(String[] args) throws InterruptedException{
        MyThread myThread = new MyThread();
        System.out.println("begin==" + myThread.isAlive());
        myThread.start();
        Thread.sleep(1000);
        System.out.println("end==" + myThread.isAlive());
    }
}
  • 结果:
begin==false
run=true
end==false
  • 结果分析:看到在start()之前,线程的isAlive是false,start()之后就是true了。main函数中加上Thread.sleep(100)的原因是为了确保Thread06的run()方法中的代码执行完,否则有可能end这里打印出来的是true,有兴趣可以自己试验一下。

  • getId()
    • 这个方法比较简单,就不写例子了。在一个Java应用中,有一个long型的全局唯一的线程ID生成器threadSeqNumber,每new出来一个线程都会把这个自增一次,并赋予线程的tid属性,这个是Thread自己做的,用户无法执行一个线程的Id。

  • getName()
    • 这个方法也比较简单,也不写例子了。我们new一个线程的时候,可以指定该线程的名字,也可以不指定。如果指定,那么线程的名字就是我们自己指定的,getName()返回的也是开发者指定的线程的名字;如果不指定, 那么Thread中有一个int型全局唯一的线程初始号生成器threadInitNum,Java先把threadInitNum自增,然后以"Thread-threadInitNum"的方式来命名新生成的线程

  • getPriority()和setPriority(int newPriority)
    • 这两个方法用于获取和设置线程的优先级,优先级高的CPU得到的CPU资源比较多,设置优先级有助于帮"线程规划器"确定下一次选择哪一个线程优先执行。换句话说,两个在等待CPU的线程,优先级高的线程越容易被CU选择执行。
    • 线程默认优先级为5,如果不手动指定,那么线程优先级具有继承性,比如线程A启动线程B,那么线程B的优先级和线程A的优先级相同

  • isDaeMon、setDaemon(boolean on)
    • Java中有两种线程,一种是用户线程,一种是守护线程。守护线程是一种特殊的线程,它的作用是为其他线程的运行提供 便利的服务最典型的应用便是GC线程。如果进程中不存在非守护线程了,那么守护线程自动销毁,因为没有存在的必要,为别人服务,结果服务的对象都没了,当然就销毁了。
    • setDaemon(true) 必须在线程start()之前

  • interrupt()
    • 尽管调用了interrupt()方法,但是线程并没有停止。
    • interrupt()方法的作用实际上是:在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞状态。换句话说,没有被阻塞的线程,调用interrupt()方法是不起作用的。关于这个会在之后讲中断机制的时候,专门写一篇文章讲解。

  • isInterrupted()
    • 测试线程是否已经中断,但不清除状态标识。这个和interrupt()方法一样,在后面讲中断机制的文章中专门会讲到。 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  • join()
    • 作用等待线程销毁
    • join()方法反应的是一个很现实的问题,比如main线程的执行时间是1s,子线程的执行时间是10s,但是主线程依赖子线程执行完的结果,这时怎么办?可以像生产者/消费者模型一样,搞一个缓冲区,子线程执行完把数据放在缓冲区中,通知main线程,main线程去拿,这样就不会浪费main线程的时间了。另外一种方法,就是join()了。先看一下例子:
package com.wwj.text;

public class MyThread extends Thread {

    @Override
    public void run() {
        try{
            int secondVal = (int)Math.random()*10000;
            System.out.println(secondVal);
            Thread.sleep(secondVal);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException{
        MyThread myThread = new MyThread();
        myThread.start();
        myThread.join();
        System.out.println("我想等 myThread 对象执行完毕之后我再执行,我做到了");
    }
}
  • 结果:
0
我想等 myThread 对象执行完毕之后我再执行,我做到了
  • 结果分析:
    • join()方法会使调用join()方法的线程(也就是myThread线程)所在的线程(也就是main线程)无限阻塞,直到调用join()方法的线程销毁为止,此例中main线程就会无限期阻塞直到mt的run()方法执行完毕。
    • join()方法的一个重点是要区分出和sleep()方法的区别。join(2000)也是可以的,表示调用join()方法所在的线程最多等待2000ms,两者的区别在于:
      sleep(2000)不释放锁,join(2000)释放锁,因为join()方法内部使用的是wait(),因此会释放锁 。看一下join(2000)的源码就知道了,join()其实和join(2000)一样,无非是join(0)而已:(只有start()方式是采用新得线程调用)
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;
        }
    }
    }


  • Thread类中的静态方法:
    • Thread类中的静态方法表示操作的线程是"正在执行静态方法所在的代码块的线程"。
    • 为什么Thread类中要有静态方法?
      • 这样就能对CPU当前正在运行的线程进行操作
  • 下面来看一下Thread类中的静态方法

  • currentThread()
    • currentThread()方法返回的是对当前正在执行线程对象的引用
    • 看一个重要的例子,然后得出结论:
package com.wwj.text;

public class MyThread extends Thread {
    static {
        System.out.println("静态块的打印:"+Thread.currentThread().getName());
    }
    public MyThread(){
        System.out.println("构造方法的打印"+Thread.currentThread().getName());
    }

    @Override
    public void run() {
        System.out.println("run方法的打印:"+Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
  • 结果:
静态块的打印:main
构造方法的打印main
run方法的打印Thread-0
  • 结论: 新创建线程的静态块和构造方法都是主线程main执行的,只有run()方法才是应用线程调用的
  • 在此基础上深入:
package com.wwj.text;

public class MyThread extends Thread {

    public MyThread(){
        System.out.println("MyThread------>begin");
        System.out.println("Thread.currentThread().getName()---->"+Thread.currentThread().getName());
        System.out.println("this.getName()---->"+this.getName());
        System.out.println("MyThread------>end");
    }

    @Override
    public void run() {
        System.out.println("run------>begin");
        System.out.println("Thread.currentThread().getName()------->"+Thread.currentThread().getName());
        System.out.println("this.getName()------>"+this.getName());
        System.out.println("run------->end");
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
  • 结果:
MyThread------>begin
Thread.currentThread().getName()---->main
this.getName()---->Thread-0
MyThread------>end
run------>begin
Thread.currentThread().getName()------->Thread-0
this.getName()------>Thread-0
run------->end
  • 结果分析: 当前执行的Thread未必就是Thread本身
    • 执行MyThread 构造方法 是main(通过开始的例子也可以得知),当前(this)线程却是 Thread-0
    • 执行run()方法的Thread-0,当前线程也是Thread-0,说明run()方法就是被线程实例去执行的

  • sleep(long millis)
    • sleep(long millis)方法的作用是在指定的毫秒内让当前"正在执行的线程"休眠(暂停执行)。这个"正在执行的线程"是关键,指的是Thread.currentThread()返回的线程。
    • 根据JDK API的说法,“该线程不丢失任何监视器的所属权”,简单说就是sleep代码上下文如果被加锁了,锁依然在,但是CPU资源会让出给其他线程。看一下例子:
package com.wwj.text;

public class MyThread extends Thread {

    @Override
    public void run() {
        try{
            System.out.println("run threadName" + this.getName()+"begin");
            //Thread.sleep(2000);
            sleep(2000);
            System.out.println("run threadName" + this.getName() + "end");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
  • 结果:
run threadNameThread-0begin
run threadNameThread-0end
  • 结果分析:
    • 两秒后打印第二句
    • 并且sleep()是静态方法,可以直接用都一样,毕竟是创建的线程用run()方法
    • 静态块中不能使用非静态变量this.

  • yield()
  • 暂停当前执行的线程对象,并执行其他线程
  • 这个暂停是会放弃CPU资源的,并且放弃CPU的时间不确定,有可能刚放弃,就获得CPU资源了,也有可能放弃好一会儿,才会被CPU执行。看一下例子:
package com.wwj.text;

public class MyThread extends Thread {

    @Override
    public void run() {
        for(int j=1 ; j<5 ;j++) {

            long beginTime = System.currentTimeMillis();
            int count = 0;
            for (int i = 0; i < 500000; i++) {
                Thread.yield(); //让出CPU资源
                count++;
            }
            long endTime = System.currentTimeMillis();

            System.out.println("用时:" + (endTime - beginTime) + "毫秒");
        }
    }
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
  • 结果:
用时:223毫秒
用时:218毫秒
用时:208毫秒
用时:228毫秒
  • 结果分析:每次执行的时间都不一样,说明Thread.yield()每次让出CPU资源的时间都不一样

  • interrupted(): 中断机制的博客有讲解
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值