java多线程编程核心技术
第一章、java多线程技能
1.1进程和多线程的概念及线程的优点
线程定义:线程可以理解成是在进程中独立运行的子任务。
1.2使用多线程
使用多线程,或者继承Thread类,或者实现Runable接口
多次调用start方法会抛异常,原因是Thread类的threadStatus属性会在线程启动后改变,每次执行start方法会判断这个属性,只要值不为0就会抛IllegalThreadStateException
thread的构造函数可以传入另一个Thread,这样可以做到将Thread的run()方法交由其他的线程进行调度
代码示例:
/**
* 如何启动一个线程
*/
public class Test01StartThread implements PrintThreadName{
public static void main(String[] args) {
// main函数所在线程
Test01StartThread test01 = new Test01StartThread();
// 继承thread
Thread t1 = new ThreadTest();
// 实现runnable
Thread t2 = new Thread(new RunnableTest());
t1.start();
t2.start();
test01.printThreadName();
/*
注意,最后一行代码是在主线程中执行的
运行结果:
Thread:Thread-0
Runnable:Thread-1
main:main
上述运行结果输出的顺序可能变化,但每行的内容不变
执行start方法的顺序不代表线程启动/执行的顺序
*/
}
}
/**
* 打印类名和线程名
*/
interface PrintThreadName {
default void printThreadName() {
System.out.println(String.format("%s:%s", this.getClass().getSimpleName(), Thread.currentThread().getName()));
}
}
class ThreadTest extends Thread implements PrintThreadName {
@Override
public void run() {
super.run();
printThreadName();
}
}
class RunnableTest implements Runnable,PrintThreadName {
@Override
public void run() {
printThreadName();
}
}
共享变量
共享数据的情况就是多个线程可以访问同一个变量,比如在实现投票功能的软件时,多个线程可以同时处理一个人的票数。
示例代码:
/**
* 线程间共享变量
*/
public class Test02SharedVariable {
public static void main(String[] args) {
Thread myThread = new MyThread();
Thread t1 = new Thread(myThread);
Thread t2 = new Thread(myThread);
Thread t3 = new Thread(myThread);
Thread t4 = new Thread(myThread);
Thread t5 = new Thread(myThread);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
/*
如果MyThread的run方法不加锁,则运行结果如下,thread-1和thread-2出现线程不安全的现象(出现两个count:3);
由Thread-1计算,count:3
由Thread-5计算,count:0
由Thread-2计算,count:3
由Thread-4计算,count:1
由Thread-3计算,count:2
加锁后,运行结果则类似下述,因为给整个方法加了锁,所以每次只会有一个线程进入run方法:
由Thread-1计算,count:4
由Thread-5计算,count:3
由Thread-4计算,count:2
由Thread-3计算,count:1
由Thread-2计算,count:0
*/
}
}
class MyThread extends Thread {
private int count = 5;
@Override
synchronized public void run() {
super.run();
count--;
System.out.println(String.format("由%s计算,count:%s", Thread.currentThread().getName(), count));
}
}
1.3 currentThread()方法
currentThread()方法可以返回代码段正在被哪个线程调用的信息。
1.4 isAlive方法
此方法可判断当前线程是否处于活动状态
1.5 sleep()方法
sleep方法作用是在指定的毫秒数内让当前“正在执行的线程”(即Thread.currentThread())休眠(暂停执行)。
1.6 getId()方法
此方法可取得线程的唯一标识。
包含currentThread()和getId()等方法的代码示例:
/**
* 线程名称相关,当前线程和this的区别
*/
public class Test03ThreadName {
public static void main(String[] args) {
TestThreadName testThreadName = new TestThreadName();
Thread t = new Thread(testThreadName);
t.setName("a");
testThreadName.setName("b");
t.start();
/*
Thread.currentThread是当前执行线程,而this指的是运行方法的对象
在构造方法中,由于是主线程调用TestThreadName的构造方法,所以Thread.currentThread()的线程名为main,线程id为1
而this代表的testThreadName对象,在创建时已经自动分配了新的name和id,分别为Thread-0和11
对象t是程序又新创建的一个对象,虽然它将testThreadName传入,但是t本身也是一个新的Thread对象,所以也重新分配了name和id,分别为Thread-1和11
而我们将t对象的name重新set成为"a",将testThreadName对象的name重新set成为"b"
在t线程启动后,实际上当前线程是t,但是运行的方法是在testThreadName对象中,所以出现Thread.currentThread的name为a(即t的name),this.getName()为b(即testThreadName的name)
运行结果:
TestThreadName--begin
Thread.currentThread().getName()=main
Thread.currentThread().getId()=1
this.getName()=Thread-0
this.getId()=11
TestThreadName--end
run--begin
Thread.currentThread().getName()=a
Thread.currentThread().getId()=12
this.getName()=b
this.getId()=11
run--end
*/
}
}
class TestThreadName extends Thread {
public TestThreadName() {
System.out.println("TestThreadName--begin");
System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName());
System.out.println("Thread.currentThread().getId()=" + Thread.currentThread().getId());
System.out.println("this.getName()=" + this.getName());
System.out.println("this.getId()=" + this.getId());
System.out.println("TestThreadName--end");
}
@Override
public void run() {
System.out.println("run--begin");
System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName());
System.out.println("Thread.currentThread().getId()=" + Thread.currentThread().getId());
System.out.println("this.getName()=" + this.getName());
System.out.println("this.getId()=" + this.getId());
System.out.println("run--end");
}
}
1.7 停止线程
线程停止的三种方法
a、当执行完run方法后正常退出
b、使用stop方法,不推荐,和suspend和resume一样,都是废弃过期的
c、使用interrupt方法
1.7.1 停止不了的线程
interrupt()方法仅仅是在当前线程中打了一个停止的标记,并不是真正停止线程。
代码示例:
class InteruptThread01 extends Thread {
private boolean print;
public InteruptThread01(boolean print) {
this.print = print;
}
public InteruptThread01() {
this.print = false;
}
public static void main(String[] args) throws InterruptedException {
Thread t = new InteruptThread01(true);
t.start();
Thread.sleep(1000);
t.interrupt();
/*
这里使用interrupt方法线程不会停止
*/
}
@Override
public void run() {
super.run();
for (int i = 0; i < 500000; i++) {
if (print) {
System.out.println("i=" + (i + 1));
}
}
}
}
1.7.2 判断线程是否是停止状态
interrupted()这个静态方法测试当前线程是否已经是中断状态,执行后具有将状态标志清除的功能。换句话说,如果连续两次调用该方法,则第二次调用将返回false。实际上内部调用了currentThread().isInterrupted(true),其中的true即清除中断状态。
isInterrupted()测试线程Thread对象是否已经是中断状态,但不清除状态标志。
代码示例:
public class Test04Interrupt {
@Test
public void test01() throws InterruptedException {
Thread t = new InteruptThread01();
t.start();
t.interrupt();
System.out.println("当前线程是否标识停止:" + t.interrupted()); // 实际应该使用Thread.interrupted()
System.out.println("当前线程是否标识停止:" + t.interrupted()); // 实际应该使用Thread.interrupted()
System.out.println("线程对象是否标识停止:" + t.isInterrupted());
System.out.println("线程对象是否标识停止:" + t.isInterrupted());
/*
t.interrupted()查看的是当前线程是否被中断,但是程序运行到这里当前线程是主线程,所以当下线程标识为false
运行结果:
当前线程是否标识停止:false
当前线程是否标识停止:false
线程对象是否标识停止:true
线程对象是否标识停止:true
*/
}
@Test
public void test02() throws InterruptedException {
Thread.currentThread().interrupt();
Thread t = new InteruptThread01();
t.start();
t.interrupt();
System.out.println("当前线程是否标识停止:" + t.interrupted()); // 实际应该使用Thread.interrupted()
System.out.println("当前线程是否标识停止:" + t.interrupted()); // 实际应该使用Thread.interrupted()
System.out.println("线程对象是否标识停止:" + t.isInterrupted());
System.out.println("线程对象是否标识停止:" + t.isInterrupted());
/*
由于第一行就对主线程就行了interrupt,所以t.interrupted()第一次返回true,而调用一次以后状态被擦除,所以第二次调用返回false
运行结果:
当前线程是否标识停止:true
当前线程是否标识停止:false
线程对象是否标识停止:true
线程对象是否标识停止:true
*/
}
}
1.7.3 能停止的线程——异常法
代码示例:
class InteruptThread02 extends Thread {
public static void main(String[] args) throws InterruptedException {
Thread t = new InteruptThread02();
t.start();
Thread.sleep(1000);
t.interrupt();
System.out.println("end!");
/*
异常退出法
运行结果:
i=304735
i=304736
i=304737
end!
需要退出!
进入catch方法,退出程序!
*/
}
@Override
public void run() {
super.run();
try {
for (int i = 0; i < 500000; i++) {
if (this.isInterrupted()) {
System.out.println("需要退出!");
throw new InterruptedException();
}
System.out.println("i=" + (i + 1));
}
} catch (InterruptedException e) {
System.out.println("进入catch方法,退出程序!");
e.printStackTrace();
}
}
}
1.7.4 在沉睡中停止
如果线程在sleep状态下调用该线程的interrupt()方法停止某一线程,会进入catch语句,并且清除停止状态值,使之变成false。
代码示例:
class InteruptThread03 extends Thread {
public static void main(String[] args) throws InterruptedException {
Thread t = new InteruptThread03();
t.start();
Thread.sleep(1000);
t.interrupt();
System.out.println("main end!");
/*
在线程sleep时,调用interrupt方法,程序退出
运行结果:
run begin
main end!
进入catch方法,退出程序!
*/
}
@Override
public void run() {
super.run();
try {
System.out.println("run begin");
Thread.sleep(200000);
System.out.println("run end");
} catch (InterruptedException e) {
System.out.println("进入catch方法,退出程序!");
e.printStackTrace();
}
}
}
class InteruptThread04 extends Thread {
public static void main(String[] args) {
Thread t = new InteruptThread04();
t.start();
t.interrupt();
System.out.println("main end!");
/*
去掉main方法中的sleep方法调用,即先调用interrupt方法,然后线程才进行睡眠,程序依然退出
运行结果:
i=9998
i=9999
run begin
先执行interrupt,然后线程才sleep,同样进入catch方法,退出程序!
java.lang.InterruptedException: sleep interrupted
*/
}
@Override
public void run() {
super.run();
try {
for (int i = 0; i < 10000; i++) {
System.out.println("i=" + i);
}
System.out.println("run begin");
Thread.sleep(200000);
System.out.println("run end");
} catch (InterruptedException e) {
System.out.println("先执行interrupt,然后线程才sleep,同样进入catch方法,退出程序!");
e.printStackTrace();
}
}
}
1.7.5 能停止的线程——暴力停止
stop()方法可以直接停止线程,线程会抛出java.lang.ThreadDeath异常,但在通常情况下,此异常不需要显示地捕捉。此方法已经作废,因为强制让线程停止可能使清理工作得不到完成,另外一个情况就是对锁定的对象进行了解锁,出现数据不一致的情况。
代码示例:
/**
* 暴力停止线程(反面示例)
*/
public class Test06Stop {
public static void main(String[] args) {
StopTest t = new StopTest();
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.stop();
t.print();
/*
stop直接停止进程(本质上是抛出了ThreadDeath异常,其实是个Error)
stop方法已经废弃,因为强制让线程停止可能使清理工作得不到完成,另外一个情况就是对锁定的对象进行了解锁,出现数据不一致的情况。
可以观察到,在catch语句中我们重新把这个ThreadDeath抛出了,但是程序不会显示地捕捉这个异常
另外,在调用stop后,线程并没有执行完print方法而直接释放了锁,这其实是因为抛出了异常后释放了锁,所以主线程的最后可以成功调用print方法
运行结果:
获取锁
i=1
stop执行后捕获ThreadDeath异常!
获取锁
i=2
释放锁
*/
}
}
class StopTest extends Thread {
private int i = 0;
@Override
public void run() {
try {
while (true) {
print();
}
} catch (ThreadDeath e) {
System.out.println("stop执行后捕获ThreadDeath异常!");
throw e;
}
}
synchronized public void print() {
System.out.println("获取锁");
i++;
System.out.println("i=" + i);
try {
Thread.sleep(1100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("释放锁");
}
}
1.7.8 使用return停止线程
可以在判断isInterrupted为true后直接return,不过还是推荐使用“抛异常”的方式,因为这种方式还可以在catch块中将异常向上抛,使线程停止的事件得以传播。
代码示例:
class InteruptThread05 extends Thread {
public static void main(String[] args) throws InterruptedException {
Thread t = new InteruptThread05();
t.start();
Thread.sleep(100);
t.interrupt();
System.out.println("main end!");
/*
使用return退出
运行结果:
time is : 1570698903497
time is : 1570698903497
main end!
停止!
*/
}
@Override
public void run() {
super.run();
while (true) {
if (this.isInterrupted()) {
System.out.println("停止!");
return;
}
System.out.println("time is : " + System.currentTimeMillis());
}
}
}
1.8 暂停线程
1.8.1 suspend与resume方法的使用
suspend与resume方法,是暂停线程和继续执行线程的命令,已经作废,原因是他们有独占锁以及不同步的缺点。
1.8.2 suspend与resume方法的缺点——独占
如果这两个方法使用不当,极容易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象。
代码示例:
/**
* suspend和resume方法说明
*/
public class Test05Suspend {
@Test
public void test01() throws InterruptedException {
SuspendThread01 thread01 = new SuspendThread01();
thread01.start();
Thread.sleep(100);
thread01.suspend();
System.out.println("main end!");
/*
suspend和resume方法缺点1:独占
由于System.out.println()方法是一个同步方法,
如果正好执行到println方法内时调用了suspend方法,则同步锁不会释放,后续对println方法的所有调用都会阻塞
所以程序会在打印了一些数字后卡住,不会打印main end!
*/
}
}
class SuspendThread01 extends Thread {
private long i = 0;
@Override
public void run() {
super.run();
while (true) {
i++;
System.out.println(i);
}
}
}
1.8.3 suspend与resume方法的缺点——不同步
如果这两个方法使用不当,也容易出现因为线程的暂停而导致数据不同步的情况。
代码示例:
/**
* suspend和resume方法说明
*/
public class Test05Suspend {
@Test
public void test02() throws InterruptedException {
MyObject01 myObject01 = new MyObject01();
Thread t1 = new Thread(() -> myObject01.setValue("a", "aa"));
t1.setName("a");
t1.start();
Thread.sleep(1000);
t1.suspend();
// myObject01.setValue("c", "cc");
Thread t2 = new Thread(() -> myObject01.pringUsernamePassword());
t2.start();
Thread.sleep(1000);
t1.resume();
Thread.sleep(1000);
myObject01.pringUsernamePassword();
/*
suspend和resume方法缺点2:不同步
线程t1在修改了一半数据后挂起,此时t2打印出的实际上是不一致的数据
而后调用t1的resume方法恢复线程后,再打印的结果是正常的结果
此外还可以注意到,虽然挂起后没有释放该方法的锁,但是t2调用的是该对象的非同步方法,不受影响。
如果将注释的那行打开,程序就会在执行到这里时卡住
运行结果:
停止a线程!
a pw
a aa
*/
}
}
class MyObject01 {
private String username = "zs";
private String password = "pw";
synchronized public void setValue(String u, String p) {
this.username = u;
if (Thread.currentThread().getName().equals("a")) {
System.out.println("停止a线程!");
Thread.currentThread().suspend();
}
this.password = p;
}
public void pringUsernamePassword() {
System.out.println(String.format("%s %s", username, password));
}
}
1.9 yield方法
yield()方法的作用是放弃当前的cpu资源,将它让给其他任务(放弃的时间并不能确定)。
代码示例:
/**
* yield方法说明
*/
public class Test07Yield {
public static void main(String[] args) {
Thread t = new YieldTest();
t.start();
/*
yield()方法的作用是放弃当前的cpu资源,将它让给其他任务(放弃的时间并不能确定)。
注释掉yield方法,运行结果:
time : 20
打开yield方法的注释,运行结果:
time : 17518
*/
}
}
class YieldTest extends Thread {
@Override
public void run() {
super.run();
long beginTime = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 50000000; i++) {
// Thread.yield();
count = count + (i + 1);
}
long endTime = System.currentTimeMillis();
System.out.println("time : " + (endTime - beginTime));
}
}
1.10 线程的优先级
线程的优先级分为1~10
MIN_PRIORITY = 1;
NORM_PRIORITY = 5;
MAX_PRIORITY = 10;
1.10.1 优先级具有继承性:
继承性,如A线程中启动了B线程,则B线程的优先级与A是一样的
1.10.2 优先级具有规则性
高优先级的线程总是大部分先执行完,但不代表高优先级的线程全部先执行完。
代码示例:
import java.util.Random;
/**
* 线程优先级说明
*/
public class Test08Priority {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
PriorityTest01 pt01 = new PriorityTest01();
pt01.setPriority(java.lang.Thread.NORM_PRIORITY);
PriorityTest02 pt02 = new PriorityTest02();
pt02.setPriority(java.lang.Thread.MAX_PRIORITY);
pt01.start();
pt02.start();
/*
可以看到,从整体上看,大部分到pt02线程是先于pt01线程执行完的
这就是线程优先级的规则性
运行结果:
---------- thread 1 use time=97
========== thread 2 use time=97
---------- thread 1 use time=98
========== thread 2 use time=98
========== thread 2 use time=104
========== thread 2 use time=106
---------- thread 1 use time=105
---------- thread 1 use time=107
---------- thread 1 use time=107
========== thread 2 use time=107
*/
}
}
}
class PriorityTest01 extends Thread {
@Override
public void run() {
super.run();
long beginTime = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
Random random = new Random();
random.nextInt();
}
long endTime = System.currentTimeMillis();
System.out.println("---------- thread 1 use time=" + (endTime - beginTime));
}
}
class PriorityTest02 extends Thread {
@Override
public void run() {
super.run();
long beginTime = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
Random random = new Random();
random.nextInt();
}
long endTime = System.currentTimeMillis();
System.out.println("========== thread 2 use time=" + (endTime - beginTime));
}
}
1.10.3 优先级具有随机性
随机性,也就是优先级较高的线程不一定每一次都先执行完
线程的优先级,具有继承特性、规则性、随机性。后两个的意思是说优先级高的线程大部分情况是先执行完,但也有可能后执行。
守护线程:如果进程中不存在非守护线程了,则守护线程自动销毁(如何销毁?)
1.11 守护线程
java线程中有两种线程,一种是用户线程,一种是守护线程。
当进程中不存在非守护线程时,则守护线程自动销毁。典型的守护线程就是垃圾回收线程,当程序中没有非守护线程时,垃圾回收线程也就没有存在的必要了,自动销毁。
最后
1、所有代码示例都在github中
https://github.com/llbqhh/LlbStudy/tree/master/StudyJava/src/main/java/org/llbqhh/study/java/book/java_multi_thread_programming