1.认识线程
- 1.1 什么是线程
百度百科:线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程
我们可以将阿里巴巴看做一个进程,那么线程就是马云(马云干自己该干的事)
1.2 多线程的优势?
相比于单线程,多线程的效率更高(众人拾柴火焰高)
2.线程的实现
2.1 线程的实现有三种方式
- 继承Thread,重写run方法
- 实现Runnable接口,重写run方法
- 实现callable接口,
创建线程 ---------继承Thread
public class ThreadDemo extends Thread {
@Override
public void run() {
//TODO 实现具体的逻辑
}
}
创建线程--------实现Runnable 接口, 再实现run()
public class ThreadDemo implements Runnable{
public void run() {
//TODO 实现具体的逻辑
}
}
创建线程--------实现callable接口
public class ThreadDemo implements Callable {
public Object call() throws Exception {
//TODO 实现具体的逻辑
return null;
}
}
2.2 Thread 的构造方法
方法 | 说明 |
---|---|
public Thread() | 分配一个新的Thread 对象 |
public Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
public Thread(String name) | name 为新线程的名字 |
public Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象 ,并命名 |
2.3线程的常用属性
属性 | 方法 | 说明 |
---|---|---|
ID | getId() | 线程ID是唯一的,并且在其生命周期内保持不变。 当线程被终止时,该线程ID可以被重用。 |
名称 | getName() | 返回此线程的名称。 |
状态 | getState() | 返回此线程的状态。 该方法设计用于监视系统状态,不用于同步控制。 |
优先级 | getPriority() setPriority () | 获取线程的优先级和设置线程的优先级,其中MIN_PRIORITY =1 ,MAX_PRIORITY=10,NORM_PRIORITY=5,如果不设置默认为5 |
是否为后台线程 | isDaemon() | 测试这个线程是否是守护线程。 |
是否存活 | isAlive() | 测试这个线程是否活着。 如果一个线程已经启动并且尚未死亡,那么线程是活着的。 |
是否中断 | interrupted() | 该方法可以清除线程的中断状态 。 换句话说,如果这个方法被连续调用两次,那么第二个调用将返回false(除非当前线程再次中断,在第一个调用已经清除其中断状态之后,在第二个调用之前已经检查过)。 |
- 什么是守护线程呢?
1.守护线程(例如垃圾回收线程:gc线程)
2.非守护线程(用户线程:用户线程即我们手动创建的线程) - 两者的区别
1.守护线程有一个特征,例如当主线程运行的时候,垃圾回收线程一起运行。当主线程销毁,会和主线程一起销毁。
2.非守护线程
如果主线程销毁,用户线程继续运行且互不影响。
2.4 启动一个线程-----start()
创建一个线程时,我们要重写其中的run(),但线程对象被创建出来并不代表者该线程已经启动,run()中我们想让该线程做的事情,要想让该线程启动,我们还需手动的调用start()方法。
这里就有一个面试题了:
面试题:start()与run()的区别:
系统调用线程类的start()方法来启动一个线程,此时该线程处于就绪状态,而非运行状态,也就意味着这个线程可以被JVM来调度执行。在调度过程中,JVM通过调用线程类的run()方法来完成实际的操作,当run()方法结束后,此线程就会终止。
如果直接调用线程类的run()方法,这会被当做一个普通的函数调用,程序中仍然只有主线程这一个线程,也就是说,start()方法能够异步地调用run()方法,但是直接调用run()方法却是同步的,因此也就无法达到多线程的目的。
只有通过调用线程类的start()方法才能真正达到多线程的目的。
2.5 中断一个线程-----interrupt()
interrupt()方法他是一个"通信员",什么意思呢?当一个线程调用这个方法,这个线程会不会中断由线程内部的代码决定,而不是调用interrupt后这个线程就中止了,只是通知该线程停止运行。
在每一个线程内部都有一个标志位,默认为false,当调用这个方法后会将标志位改为true,可以通过Thread.isInterrupted()或Thread.currentThread().isInterrupted()
来判断标志位是否被改。
这两个方法也是有区别的,Thread.isInterrupted()
此方法调用后会清除标志位,即又将标志位变为false
Thread.currentThread().isInterrupted()
不会清除标志位
当线程调用了wait()/sleep()/join()
阻塞挂起时候,以 InterruptedException 异常的形式通知,并清除中断标志
测试代码:
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
for(int i = 0; i < 1000; i++ ){
System.out.println("正在打印....");
}
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().isInterrupted());
}
}
});
t.start();
Thread.sleep(1);
t.interrupt();
}
}
通过打印结果我们可以看到,在抛出异常前标志位变成了false;
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.interrupted()) {
for(int i = 0 ; i < 1000 && !Thread.interrupted(); i++){
System.out.println("正在打印...."+i);
}
}
}
});
t.start();
Thread.sleep(1);
t.interrupt();
}
}
死循环一直在打印,Thread.interrupt()清除了标志位。
2.6等待一个线程
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("t线程结束...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
System.out.println("主线程结束...");
}
}
当我们执行上述代码时候,我们会发现运行结果是:
有时候我们需要让主线程最后结束,这个时候我们就要用到了join()
,join方法时使当前线程放弃抢夺cpu,直到调用的线程结束,什么意思呢?我们来看接下来的代码
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("t线程结束...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
t.join();
System.out.println("主线程结束...");
在主线程内,使用了t.join()就会是主线程进入阻塞挂起状态,直到t线程结束。也就是说在A线程里调用了B线程的join方法,A线程就会阻塞挂起,直到B线程结束。
2.7休眠当前线程
方法 | 解释 |
---|---|
public static void sleep(long millis) | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) | 可以更高精度的休眠 |
sleep()方法是使当前线程睡眠,不在去抢夺cup,直到睡眠结束
3.线程的状态
上面我们就提到了阻塞挂起,这只是线程的一个状态,现在我们来看一看线程都有哪些状态
public static void main(String[] args) throws InterruptedException {
for(Thread.State state :Thread.State.values()){
System.out.println(state);
}
}
通过该代码我们可以看到,在java中线程共有 6中状态
1.NEW : 指的是线程被创建但没有启动,即没有调用strat();
2.RUNNABLE : 与操作系统中不同的是Java中的RUNNABLE状态不一定是在运行态,还可能是在等待cpu,这个状态就是指该线程已经拥有了抢夺cpu的资格,但不一定已经抢到了,他包含了(RUNNING,READY)两种状态
3.BLOCKED:等待监视锁,这个时候线程被操作系统挂起。当进入synchronized块/方法或者在调用wait()被唤醒/超时之后重新进入synchronized块/方法,锁被其它线程占有,这个时候被操作系统挂起,状态为阻塞状态
4.WAITING:(无限期的等待,不知道什么时候结束)使用 join(),wait()方法等方法后进入此状态,已经没有抢夺cup的资格。
5.TIMED_WAITING:当线程调用sleep(睡眠时间)/wait(等待时间)/join(等待时间)/ LockSupport.parkNanos(等待时间)/LockSupport.parkUntil(等待时间)方法之后所处的状态,在指定的时间没有被唤醒或者等待线程没有结束,会被系统自动唤醒,正常退出。
6.TERMINATED:线程终止。
线程终止的方式:
- 正常结束,run()方法运行完成
- stop()已启用,太过暴力,可能会丢失数据
- interrupt() ,通知线程 然后在线程内部结束线程
观察 1: WAITING 、 BLOCKED 、 TIMED_WAITING 状态的转换
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
Thread t = new Thread(() -> {
synchronized (object) {
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000_0000; i++) {}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println(t.getState());;
t.start();
System.out.println(t.getState());
Thread.sleep(10);
synchronized (object) {
for (int i = 0; i < 10; i++) {
System.out.println(t.getState());
}
object.notify();
}
while (t.isAlive()) {
System.out.println(t.getState());
}
}
线程状态的转移图