线程的常用方法

2.线程的常用方法

2.1 Thread 类中的 start() 和 run() 方法有什么区别?

public class TestStartRun {

    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("子线程....name:"+Thread.currentThread().getName());
            }
        };
        System.out.println("主线程....name:"+Thread.currentThread().getName());
        //运行发现 t.run 打印的线程都是主线程,t.start 打印的是子线程
 		// thread.run();
        thread.start();
		
    }
}
/*
执行结果:
	主线程....name:main
	子线程....name:Thread-0
*/

查看源码

请添加图片描述

在 jvm.h 头文件中回到 http://hg.openjdk.java.net/jdk8u 根下 hotspot-browse-src/share/vm/prims-jvm.cpp Java 的

Thread 线程的一下原生方法

请添加图片描述

一路跟踪源码发现 start 和 run 的执行过程

请添加图片描述

这个问题经常被问到,但还是能从此区分出面试者对 Java 线程模型的理解程度。

  1. start()方法来启动线程,真正实现了多线程运行。这时无需等待 run()方法体代码执行完毕,可以直接继续执行 t.start()下面的代码。

  2. 通过调用 Thread 类的 start()方法来启动一个线程,这时此线程是处于就绪状态,并没有运行。

  3. 方法 run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行 run ()当中的代码。 run 方法运行结束此线程终止。然后 CPU 再调度其它线程。

2.2线程的睡眠和等待

public class TestSleepWait {
    public static void main(String[] args) {
        // 共享资源,当锁用
        Object lock = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("A    等待中,获取锁");
                synchronized (lock){
                    System.out.println("A   锁获取成功");
                    try {
                        // 做业务处理
                        Thread.sleep(20);
                        System.out.println("A    开始等待");
                        lock.wait();// wait :有参:设置时间,无参:无限等待,需要NOITFY唤醒
                        System.out.println("A    执行完成!!!");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

//        try {
//            //目的是为了让线程A先启动再执行启动B
//            Thread.sleep(10);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("B   等待中,获取锁");
                synchronized (lock){
                    System.out.println("B   锁获取成功");
                    try {
                        // 做业务处理
                        System.out.println("B    睡10毫秒");
                        Thread.sleep(10);
                        System.out.println("B    执行完成!!!");
                        // 唤醒lock的wait::人为唤醒
                        lock.notify();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

    }

}
/*
执行结果:
A    等待中,获取锁
A   锁获取成功
B   等待中,获取锁
A    开始等待
B   锁获取成功
B    睡10毫秒
B    执行完成!!!
A    执行完成!!!
*/

结论:

  1. 对于 sleep()方法方法是属于 Thread 类中的。而 wait()方法则是属于 Object 类中的

  2. sleep()方法导致了程序暂停执行指定的时间,让出 cpu 给其他线程,但是他的监控状态依然保持着,当指定的时间到期了又会自动恢复运行状态。

  3. 在调用 sleep()方法的过程中,线程不会释放对象锁。而当调用 wait()方法的时候,线程会放弃对象锁进入等待状态,只有针对此对象调用 notify()方法后本线程才会重新尝试执行。

  4. sleep 方法可以在任何地方使用,而 wait 方法只能在 synchronized 方法或者 synchronized 块中使用。

Wait 让出 CPU 并且释放锁,sleep 仅让出 CPU 但是不让出锁。

2.3 线程唤醒(notify与notifyAll)

​ Object 类中的 notify()方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会

随机选择唤醒其中一个线程。notifyAll 会让处于等待池的所有线程进入锁池去竞争得到锁的机会。

2.4 线程让步(yield)

​ 当调用 Thread.yield()方法时,会给线程调度器一个当前线程愿意让出 CPU 执行时间片的暗示,但是线程调度器可能会忽略这个暗示。但是调用 yield 对锁是没有影响的,Thread.yield()会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。一般情况下, 优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对线程优先级并不敏感。

    public static void main(String[] args) {
        Runnable runnableYield = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("当前线程是:"+Thread.currentThread().getName()+"循环次数:"+i);
                    if (i==5){
                        Thread.yield();
                    }
                }
            }
        };
       Thread t1 =  new Thread(runnableYield,"t1");
        Thread t2 = new Thread(runnableYield,"t2");
        t1.start();
        t2.start();

    }

多执行几次,会发现并不是每次到了 5 的时候就会切换线程继续执行

2.5 线程中断(stop\suspend\resume\interrupt)

  • 已经被抛弃的方法(stop\suspend\resume)

直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。

程序中可以直接使用 thread.stop()来强行终止线程,但是 stop 方法是很危险的,就象突然关 闭计算机电源,

而不是按正常程序关机一样,可能会 产生不可预料的结果,不安全主要是: thread.stop()调用之后,创建子线

程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子 线程所持有的所有锁。一般任何进行加锁的代码

块,都是为了保护数据的一致性,如果在调用 thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控

制),那么被保护数据就有可能呈 现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪

的应用程序错误。因 此,并不推荐使用 stop 方法来终止线程

通过调用线程对象的 suspend()和 resume()方法停止

  • 生产环境使用的方法(interrupt)
  1. 调用 interrupt(),只是通知了线程应该中断了,其本意是给这个线程一个通知信号,会影响这个线程内

部的一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)

  1. 如果这时线程正在调用 sleep()而使线程处于 TIMED-WATING 阻塞的状态,那么调用 interrupt()线程将立即

退出被 TIMED-WATING 阻塞的状态,并抛出**InterruptedException **异常

  1. 如果这时线程处于正常活动状态,那么会将线程的中断标志设置为 true,被设置中断标志的线程将继续

正常运行,不受影响。

  1. 许多声明抛出 InterruptedException 的方法(如 Thread.sleep(long mills 方法)),抛出异常前,都会清除

中断标识位,所以抛出异常后,调用 isInterrupted()方法将会返回 false。

  1. 想要使用 interrupt()方法终止一个线程需要被调用的线程配合中断。中断状态是线程固有的一个标识位,

可以通过此标识位安全的终止线程。比如你想终止一个线程 thread 的时候,可以调用 thread.interrupt()

方法,在线程的 run 方法内部可以根据 thread.isInterrupted()的值来优雅的终止线程

public class TestInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Runnable interRunnable = new Runnable() {
            @Override
            public void run() {
                try {
                int i = 0;
              //在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程
                while (!Thread.currentThread().isInterrupted()){
                     // 同样休眠 300ms(预期循环一次)
                        Thread.sleep(300);
                        i++;
                    System.out.println(Thread.currentThread().getName()+" is runing"+i+"次,线程的状态+" + Thread.currentThread().getState());
                 	}
                }
                catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName()+": catch InterruptedException,线程的状态+" +
                            Thread.currentThread().getState()
                    );
                    }
            }
        };
        // 启动一个线程
        Thread t1 = new Thread(interRunnable, "t1");
        System.out.println(t1.getName()+"::"+t1.getState()+" is new");
        t1.start();
        System.out.println(t1.getName()+"::"+t1.getState()+" is started");
        // 主线程休眠 300ms,然后主线程给 t1 发“中断”指令。
        Thread.sleep(300);
        t1.interrupt();
        System.out.println(t1.getName()+"::"+t1.getState()+" is isInterrupted");
        // 主线程休眠 300ms,然后查看 t1 的状态。
        Thread.sleep(300);
        System.out.println(t1.getName() +"::"+t1.getState()+" is interrupted now.");
    }
}

输出结果:

请添加图片描述

2.6 等待其他线程终止(join)

join() 方法,等待其他线程终止,在当前线程中调用另外一个线程对象的 join() 方法,则当前线程转为阻塞状态,

一直到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu 的时间片。

很多情况下,主线程生成并启动了子线程,需要用到子线程返回的结果,也就是需要主线程需要 在子线程结束后

再结束,这时候就要用到 join() 方法。

思路:1、callable

​ 2、循环等待

​ 3、join

/**
 * 主线程中启动子线程
 * 要求主线程需要拿到子线程的计算节后进行逻辑操作
 *
 * 方式一:callable
 * 方式二:循环等待
 * 方式三:join
 */
public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        SumRunnable tj = new SumRunnable();
        Thread t = new Thread(tj);
        t.start();
        // 主线程中得到子线程的运算结果(很大几率拿不到)
        // System.out.println(tj.getSum());
        /*
         * 循环等待、、可能会IO阻塞(会拿到)
        while (tj.getSum()==0){
            Thread.sleep(300);
        }
        System.out.println(tj.getSum());
        */

        /*
        * join(肯定拿到)
        *
        * */
        t.join();
        System.out.println(tj.getSum());
    }
}
class SumRunnable implements  Runnable{
    private int  sum = 0;
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            sum+=i;
        }
    }

    public int getSum() {
        return sum;
    }
}

2.7 其他方法

  1. sleep():强迫一个线程睡眠N毫秒。

  2. isAlive(): 判断一个线程是否存活。

  3. join(): 等待线程终止。

  4. activeCount(): 程序中活跃的线程数。

  5. enumerate(): 枚举程序中的线程。

  6. currentThread(): 得到当前线程。

  7. isDaemon(): 一个线程是否为守护线程。

  8. setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线 程依赖于主线程

结束而结束)

  1. setName(): 为线程设置一个名称。

  2. wait(): 强迫一个线程等待。

  3. notify(): 通知一个线程继续运行。

  4. setPriority(): 设置一个线程的优先级。

  5. getPriority()::获得一个线程的优先级

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)

用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:

只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值