与线程的第一次见面
1、线程
2、Java中线程的创建
3、线程生命周期及线程状态之间的转换
4、线程的常用方法
1、那,什么是线程呢?
闲谈:
学习Java到现在,作为一个身份是一丢丢的程序员的我来说,写过了许多叫做程序的东西。我们写好一段代码,然后运行,然后得到预期的结果。
我想,对于线程陌生,那么进程就会是一个稍微贴切的概念。
对于平时用的QQ啊、微信啊、微博啊、网易云音乐,都可以称为一个进程,对于实现它们的那些代码,则叫做程序。
进程,是系统资源分配和处理机调度的一个独立单位。
那么,线程,则可以称为轻量级的线程。
比如说,你打开了咪咕音乐,这是一个进程。对于它来说,你可以在听歌的时候去下载歌曲,还可以去读歌曲下的评论,或者去做一些别的事,对于咪咕音乐这个进程来说,你的这些操作就可以称为一个个线程。
之所以引入线程,是因为进程的创建与撤销的花销比较大,在不追求安全和稳定的情况下,那么我们可以只启动线程也能完活。
说简单点,现在电脑的性能越来越好了,为了对得起电脑的内存利用率和系统吞吐量,都在追求高并发,无疑,多线程是实现这样需求的一种很好的思路。
对于线程来说,其实它本身并不拥有系统资源,它只需要有一点必不可少的、能保证独立运行的资源,即允许多个线程共享该进程所拥有的资源。
那么对应到Java里来说,
**方法区、堆:当前所有线程之间所共享
每个线程都有自己的程序计数器、栈(Java栈和本地方法栈)
2、Java中创建一个线程的三种方式
首先,无论是什么方法,都要重写一个run()方法。
而启动线程,则是调用start()方法
1)继承Thread类,重写run()方法
class MyThread extends Thread{
@Override
public void run() {
System.out.println("hello world");
}
}
调用 new MyThread.start()方法启动一个线程
2)实现Runnable接口,重写run()方法
class MyThread1 implements Runnable{
@Override
public void run() {
System.out.println("implments Runnable to create a thread");
}
}
仍然要调用start()方法启动一个线程
3)匿名线程(通过匿名类实现)
new Thread(){
public void run(){
}
}.start();
或
Thread thread = new Thread(){
public void run(){
}
};
thread.start();
不是调用run()方法去启动线程,因为Java是这么搞的,你把你想以多个线程展现的东西写在重写的run()里面,然后调用start()方法,如果进展顺利,就会调用一个start0()方法,这是一个Java的本地方法,这个方法用来启动线程,同时会将当前的线程加入到线程组中,线程就会进入Runnable状态。
源码:
public synchronized void start() {
/*
threadStatus,默认值 0,第一次启动后,就不是0了,所以说,一个线程只能被启动一次
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);//加入到一个线程组,进入到就绪状态
boolean started = false;
try {
start0();
started = true;
} finally {
try {
//如果start0()失败,就把这个进程加入到这里面
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
3、线程的生命周期及线程状态之间的转换
又到了我灵魂画手展现威力的时刻了!!!哈哈哈!
Java中进程的生命周期与操作系统的中略有区别
Java:
1)New状态:创建一个线程对象
2)Runnabled状态:就绪状态,只要获得系统资源,就有执行的可能。
3)Running状态:执行状态
4)Blocked状态:阻塞状态,等待监视器锁/调用Object.wait方法
5)Waiting状态:同阻塞状态,Object.wait/thread.join/LockSupport.park()
6)Timed_Waiting状态:限时等待
7)Terminated状态:终止状态
相信学过操作系统的同学都有能力画出来,对于线程状态的转换也不难:⬇
完美(此处应有手势)。
操作系统:
New:新建状态
Runnabled:就绪状态
Blocked:阻塞状态
Running:执行状态
Dead:终止状态
4、线程的一些常用方法
1)不用多说,当然是启动线程,
start()方法。
2)sleep()方法,让线程睡一会儿。
sleep(long millis),单位是毫秒,想多睡一会儿的话就稍有不便。
所以这里有一个使用更方便的枚举类,TimeUnit
TimeUnit.NANOSECONDS.sleep(纳秒);
TimeUnit.MICROSECONDS.sleep(微秒);
TimeUnit.MILLISECONDS.sleep(毫秒);
TimeUnit.SECONDS.sleep(忘记了);
TimeUnit.MINUTES.sleep(不认识);
TimeUnit.HOURS.sleep(???);
TimeUnit.DAYS.sleep(-。-);
TimeUnit.MONTH.sleep(月!!!);//哦,不好意思,没有这个
另一个让线程飞一会儿的方法,yield()方法
它的使用跟sleep方法差不多,有一点点的区别如下:
①sleep只会导致当前线程暂停,不会放弃cpu时间片
②yield放弃cpu一次时间片这个过程是不一定,如果cpu没有忽略这个提示,那才会导致线程切换
③yield方法会使得线程从Running切换到Runnable状态
④sleep,使用interrupt会捕获到中断信号,yield不会
3)线程中断
interrupt - 由Thread的一个对象调用,如果线程处于这个状态,将中断状态位置为true
那么,sleep()/join/wait()则会使得当前的线程进入阻塞状态->中断状态
线程处于阻塞状态时不能被打断,如果被打断就会抛出一个InterruptedException
isInterrupted - Thread.xxx 检测中断状态位是否为true
interrupted - Thread.xxx 判断当前线程是否被中断
如果当前线程被打断,第一次调用interrupted会返回true,然后立即擦出当前inteerrupt标识,
第二次包括以后调用都返回的是false
4)获取当前线程
public static Thread currentThread() 返回当前执行线程的引用
也就相当于得到当前线程的一个对象(总觉得这句话是多余的。。。)
5)join方法
线程A调用join方法,会使得线程A调用join方法时所在的线程B进入到等待队列,
此时必须等到线程A执行完或者到达给定的时间, 线程B才可以去执行
6)守护线程
如果在主程序中存在非守护线程,会导致main线程结束,而子线程依旧在执行,JVM进程也将不会退出。
守护线程的意思就是,当主线程结束,它的守护线程也就随之而亡。
这么说也不太对,当线程B成为了A的守护线程,只要A结束,B也就跟着结束了,反之B结束,一般情况下,A的结束与否和B无关;当B不是A的守护线程,只要它们两个没得关系,who care who?