线程详解 (java实现线程的两种方式)

线程基本图谱.png

  1. 继承Thread类
  2. 实现Runnable接口
    注意点:
1
2
3
4
5
6
继承自Thread的类 ThreadClass :
ThreadClass tc = new ThreadClass();
tc.start(); //注意此处必须调用start方法
实现Runnable接口的类 ThreadRunable :
Thread t = new Thread(new ThreadRunnable());
t.start();

线程的基本属性和方法

属性

  1. id和name
    id是自增的整数
    name是“Thread-”后跟一个整数
  2. 优先级
    默认为5 范围1-10(不同操作系统可能不一样) 优先级对于操作系统而言只是建议
  3. 状态
    Thread.State
1
2
3
4
5
6
7
8
public enum State {
NEW, //没有调用start的线程状态为new
RUNNABLE, //调用start后线程在执行run方法且没有阻塞时为Runnable 不过Runnable不一定代表CPU一定在执行该线程的代码,可能正在执行 也可能在等待操作系统分配时间片,只是它没有在等待其他条件
BLOCKED, //线程不能获得锁的时候 会加入等待队列等待 线程阻塞
WAITING, //线程中 对象显式调用wait()
TIMED_WAITING, //线程中 对象显式调用wait()
TERMINATED; //线程运行结束后状态
}
  1. 是否daemon线程 (守护进程)
    启动线程会启动一条单独的执行流,整个程序只有在所有线程都结束的时候才退出,但daemo线程是例外,当整个程序中剩下的都是daemo线程的时候,程序就会退出。
    daemo线程有什么用呢?它一般是其他线程的辅助线程,在它辅助的主线程退出的时候,它就没有存在的意义了。在我们运行一个即使最简单的”hello world”类型的程序时,实际上,Java也会创建多个线程,除了main线程外,至少还有一个负责垃圾回收的线程,这个线程就是daemo线程,在main线程结束的时候,垃圾回收线程也会退出。

方法

  1. sleep方法
1
public static native void sleep(long millis) throws InterruptedException;

睡眠期间该线程让出cpu 但是睡眠时间不一定是给定的毫秒数 偏差与系统定时器和操作系统调度器的准确度和精度有关
睡眠期间 线程可以被中断 如果被中断 sleep会抛出interruptedException

  1. yield方法
1
public static native void yield();

告诉系统调度器 线程不着急使用CPU 可以先让其他线程运行 当然这也只是建议 调度器如何处理不一定 也可能完全忽略

  1. join方法
    在前面HelloThread的例子中,HelloThread没执行完,main线程可能就执行完了,Thread有一个join方法,可以让调用join的线程等待该线程结束,join方法的声明为:
1
public final void join() throws InterruptedException

在等待线程结束的过程中,这个等待可能被中断,如果被中断,会抛出InterruptedException。

join方法还有一个变体,可以限定等待的最长时间,单位为毫秒,如果为0,表示无期限等待:

1
public final synchronized void join(long millis) throws InterruptedException

eg:

1
2
3
4
5
public static void main(String[] args) throws InterruptedException {
Thread thread = new HelloThread();
thread.start();
thread.join();
}

线程的优点及成本

优点

为什么要创建单独的执行流?或者说线程有什么优点呢?至少有以下几点:

  • 充分利用多CPU的计算能力,单线程只能利用一个CPU,使用多线程可以利用多CPU的计算能力。
  • 充分利用硬件资源,CPU和硬盘、网络是可以同时工作的,一个线程在等待网络IO的同时,另一个线程完全可以利用CPU,对于多个独立的网络请求,完全可以使用多个线程同时请求。
  • 在用户界面(GUI)应用程序中,保持程序的响应性,界面和后台任务通常是不同的线程,否则,如果所有事情都是一个线程来执行,当执行一个很慢的任务时,整个界面将停止响应,也无法取消该任务。
  • 简化建模及IO处理,比如,在服务器应用程序中,对每个用户请求使用一个单独的线程进行处理,相比使用一个线程,处理来自各种用户的各种请求,以及各种网络和文件IO事件,建模和编写程序要容易的多。

成本

关于线程,我们需要知道,它是有成本的。创建线程需要消耗操作系统的资源,操作系统会为每个线程创建必要的数据结构、栈、程序计数器等,创建也需要一定的时间。

此外,线程调度和切换也是有成本的,当有当量可运行线程的时候,操作系统会忙于调度,为一个线程分配一段时间,执行完后,再让另一个线程执行,一个线程被切换出去后,操作系统需要保存它的当前上下文状态到内存,上下文状态包括当前CPU寄存器的值、程序计数器的值等,而一个线程被切换回来后,操作系统需要恢复它原来的上下文状态,整个过程被称为上下文切换,这个切换不仅耗时,而且使CPU中的很多缓存失效,是有成本的。

当然,这些成本是相对而言的,如果线程中实际执行的事情比较多,这些成本是可以接受的,但如果只是执行本节示例中的counter++,那相对成本就太高了。

另外,如果执行的任务都是CPU密集型的,即主要消耗的都是CPU,那创建超过CPU数量的线程就是没有必要的,并不会加快程序的执行。

作者:swiftma
链接:https://juejin.im/post/58a1ba8786b599006b4877aa
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

lemon wechat
欢迎大家关注我的订阅号 SeeMoonUp
写的不错?鼓励一下?不差钱?