Java多线程核心技术1-多线程技能
来自阅读Java多线程编程核心技术的读书笔记,按照自己思路写了一些整理
Java多线程技能
1.1 使用多线程
继承Thread类
-
继承
public class Thread implements Runnable
可以实现线程public class MyThread extends Thread { @Override public void run() { super.run(); // ... } } // 运行方式 MyThread mt = new MyThread(); mt.start();
-
继承
Thread
类后,通过mt.start()
运行(不是run) -
当多次调用
start()
,则出现异常java.lang.IllegalThreadStateException
实现Runnable接口
-
为什么?如果线程类已经有一个父类,则不能再继承
Thread
-
实现Runnable接口后,还是要传递给
Thread
对象执行,使用构造器Thread thread = new Thread(new MyRunnable()); thread.start(); // 同样使用thread执行
-
给
Thread
传入一个Runnable
对象,让线程创建更加灵活
-
线程安全问题
-
类似
i++
之类的语句,在JVM中将分成三步:取得原有i值、计算i+1、对i赋予新值
-
通过在
run
方法中加上synchronized
关键字,将对象本身作为锁synchronized public void run() { super.run(); count++; // ... }
1.2 Thread相关方法
currentThread()
-
返回代码段正在被哪个线程调用
Thread.currentThread.getName()
-
Thread.currentThread
返回**当前运行线程的引用**,与this
不完全一致:public class currentAndThis { static class MyThread extends Thread { public MyThread() { System.out.println("---ctor start---"); System.out.println("current: " + Thread.currentThread().getName() + "; alive: " + Thread.currentThread().isAlive()); System.out.println("this : " + this.getName() + "; alive: " + this.isAlive()); System.out.println("---ctor end ---"); } @Override public void run() { super.run(); System.out.println("---run start---"); System.out.println("current: " + Thread.currentThread().getName() + "; alive: " + Thread.currentThread().isAlive()); System.out.println("this : " + this.getName() + "; alive: " + this.isAlive()); System.out.println("---run end ---"); } } public static void main(String[] args) { test1(); // test2(); } static void test1() { MyThread myThread = new MyThread(); myThread.setName("myThread"); myThread.start(); } static void test2() { MyThread myThread = new MyThread(); Thread thread = new Thread(myThread); myThread.setName("myThread"); thread.setName("thread"); thread.start(); } }
isAlive()
-
判断当前线程是否在活动状态
thread.isAlive(); Thread.currentThread.isAlive();
sleep()
-
指定毫秒数,让当前**正在执行的线程**休眠。指的是
this.currentThread()
返回的线程-
不能指定其他线程,只能是正在执行的线程休眠
Thread.sleep(2000);
-
getId()
-
获取某个线程的唯一标识
Thread.currentThread().getId();
yield()
- 主动让出CPU给其他任务占用,调用方法为:
Thread.yield()
,当前线程让出
1.3 停止线程
stop() 方法
- 停止线程不容易,意味着要放弃线程正在进行的操作。
- 使用
stop
方法强行停止线程,和suspend/resume
一样都是作废过期的方法,产生不可预料的结果 - 使用
interrupt()
方法中断线程
- 使用
- 强制使用
stop
,导致**得不到清理;且可能造成数据的不一致**
停止状态
-
Thread.java
类中提供两种方法:static boolean interrupted()
测试当前线程 是否已经 中断状态(interrupt位)boolean isInterrupted()
测试线程** 是否已经 **中断状态(interrupt位)
-
Thread.interrupted()
只能返回当前线程的状态- 调用后将清除状态标志,即置为
false
- 调用后将清除状态标志,即置为
-
td.isInterrupted()
能返回任意线程的状态- 调用后不清除状态标志
利用 interrupt 优雅停止
查缺补漏:不推荐在非静态环境下,调用静态方法
- 因为实例会被回收
- 应当直接使用类本身来访问静态成员
// static void go() Main main = new Main(); main.go(); // warning
先看一个小结,基本使用方法如下
- 非睡眠情况下
- 循环中用
Thread.interrupted()
检查是否置停止位,并抛**InterruptedException**- 外面
catch
住,检查发现是**False**(interrupted()
置位)- 睡眠情况,先睡眠再被***interrupt***
- 不需要循环中抛异常检查,被
interrupt()
自会抛出异常- 外面
catch
住,检查发现是**False**(自动置位)- 睡眠情况,先被***interrupt***再睡眠
- 睡眠前代码正常执行,睡眠后会抛出异常,外面
catch
住- 总之:置位***interrupt***(False)有下列情况
- 执行
Thread.interrupted()
检测后- 睡眠时被
interrupt
后
非睡眠状态:利用异常停止
static class InThread extends Thread {
@Override
public void run() {
super.run();
try {
for (int i = 0; i < 10000; i++) {
if (Thread.interrupted())
throw new InterruptedException();
System.out.println("Print " + i);
}
} catch (InterruptedException e) {
System.out.println("我被停止 interrupted=" + this.isInterrupted());
// false
}
}
}
睡眠状态:利用异常停止
-
先睡眠再interrupt:睡眠中停止
Thread.sleep
后直接用异常接住InterruptedException
static class InThread extends Thread { @Override public void run() { super.run(); try { Thread.sleep(20000); } catch (InterruptedException e) { System.out.println("我被停止 interrupted=" + this.isInterrupted()); // 输出: false } } }
-
先interrupt再睡眠:睡眠前代码正常执行,睡眠后感受到异常
static class InThread extends Thread { @Override public void run() { super.run(); try { for (int i = 0; i < 10000; i++) { System.out.println(i); // ... 正常执行 } Thread.sleep(20000); // 睡眠后,才异常 } catch (InterruptedException e) { System.out.println("我被停止 interrupted=" + this.isInterrupted()); // 输出: 1 2 3...10000 false } } }
非睡眠状态:利用return停止
-
基本原理与利用异常停止一致,不需要抛出异常
static class InThread extends Thread { @Override public void run() { super.run(); for (int i = 0; i < 10000; i++) { if (Thread.interrupted()) { // ... return; } System.out.println("Print " + i); } } }
-
建议使用抛异常的方式,优雅的停止线程的运行
-
因为catch块中还可以将异常上抛,以使停止线程的事件得以传播
-
1.4 暂停线程
suspend 与 resume
调用thread.suspend()
与thread.resume()
确实可以暂停与恢复线程
独占
使用suspend()/resume()
容易造成公共对象的独占,让其他线程无法访问公共同步对象
-
一旦某个持有锁的线程被
suspend
,且没有被正确的resume
,则持有的锁就不能被正确的释放,其他线程就无法进入对应的临界区 -
持有锁并非一定显式持有,例如
System.out.println()
方法即为同步方法public void println(String x) { synchronized (this) { print(x); newLine(); } }
若无法正确
resume
暂停的线程,则会导致这里的锁不能释放,其他线程难以打印!
不同步
可能导致事务被打断,例如在两个赋值语句中间加入suspend
,若resume
时机不对,则可能导致执行结果不可预测
1.5 线程优先级
-
设置优先级、查看优先级
// 设置优先级 thread.setPriority(newPriority); // 查看优先级 thread.getPriority();
-
优先级具有继承特性:A线程启动B线程,则B线程的优先级和A一致
-
优先级具有规则性:高优先级线程大部分先执行完,但是不代表高优先级线程一定先执行完
-
优先级具有随机性:优先级高的线程不一定每次都先执行完
1.6 守护线程
- Java守护线程:当进程中***不存在非守护线程时,守护现场自动销毁***
- 任何一个守护线程,都是JVM中***非守护线程***的 保姆
- GC就是一种典型的守护线程
- 设置为守护线程:
thread.setDaemon(true)