目录
2.1 synchronized ( Lock ) { code... }
进程与线程
- 进程:应用程序在内存中分配的空间。(正在运行中的程序)
- 线程:负责程序执行的单元,也称为执行路径。(需要线程来执行代码)。一个进程至少包含一条线程,如果一个进程中出现了条线程,此程序就为多线程程序。
多线程技术原理
多线程解决多部分代码同时执行的需求。
1. CPU切片
1、CPU在某一时间段只能执行一条线程,我们看到的程序以为都是在同时执行的(其实并不是同时执行),2、只不过是CPU在高速的切换执行的线程(切换的时间是仅仅是几~ms),3、CPU切换的线程并不是有序的(根据某种规则算法(如线程优先级)算出最需要执行的线程,所以CPU切换执行的线程是随机性的)4、线程的执行时间段根据CPU算法而定。
2. CPU多核处理
早期的CPU一个时间段只能执行一条线程(这是因为只有一个核心),早期的服务器装的CPU直接装的几个。后来为了解决发热问题也就使用了纳米技术缩小CPU里的晶体管,之后就集成了多核CPU,根据CPU的每个核心就能同时处理多条线程。但是一个进程中的多条线程是不会出现在不同CPU核心的,也就是一个进程中的多线程只能被一个CPU核心处理。
3. 合理利用CPU资源
线程开多了并不会真正的提高程序的效率,会降低每条线程能被执行到的时间、线程被执行的时间段、线程被执行的几率,以及可能会出现卡顿现象。所以请合理利用CPU资源开启多线程。
JVM中的多线程与垃圾回收
JVM有自己的线程,有多条以上,我们现在关注的有:一条是程序运行的Main线程,一条是垃圾回收线程(根据垃圾回收器的不同可能有多条垃圾回收线程并发执行)。
对象的回收 :
所有对象都具备被回收前的回调方法,此方法由程序员在自定义类时,垃圾回收前检查此类的相关资源是否关闭等清除操作...。
Object类的finalize()方法
测试:
package com.bin.demo;
import java.util.ArrayList;
class Demo {
@Override
protected void finalize() throws Throwable {
System.out.println("已回收");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ArrayList<Demo> list = new ArrayList<Demo>();
for (int i = 0; i < 5; i++) {
list.add(new Demo());
}
// list = null;
list.clear(); //释放强引用
System.gc(); //手动启动垃圾回收器
System.out.println("Main线程执行完毕");
}
}
输出:
已回收
Main线程执行完毕
已回收
已回收
已回收
已回收
System.gc();启动垃圾回收器线程,执不执行CPU说了算。
可见,垃圾回收器线程回收了一个对象后,CPU立马切换到了Main线程,再切换到垃圾回收器线程回收对象。
由此可见,CPU切换执行的线程是随机性的。
创建线程的二中方式
1. 继承Thread类创建线程
继承Thread类,并重写run()方法,创建Thread对象,调用start()方法开启线程。
package com.bin.demo;
class Demo extends Thread {
private String name;
Demo(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) { //获取线程名
System.out.println(getName() + " ————> " + name);
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
//创建线程对象
Thread t0 = new Demo("啊斌");
Thread t1 = new Demo("雨烟");
//开启线程
t0.start();
t1.start();
//main线程
for (int i = 0; i < 5; i++) { //获得当前线程对象,并获取线程名
System.out.println(Thread.currentThread().getName());
}
}
}
Thread.currentThread():获得当前线程对象。
输出:(每次运行的结果都会不一样)
main
Thread-0 ————> 啊斌
Thread-1 ————> 雨烟
Thread-0 ————> 啊斌
main
main
main
main
Thread-0 ————> 啊斌
Thread-0 ————> 啊斌
Thread-0 ————> 啊斌
Thread-1 ————> 雨烟
Thread-1 ————> 雨烟
Thread-1 ————> 雨烟
Thread-1 ————> 雨烟
创建线程时的默认命名,以数字递增。实现方式是Thread类内部维护了一个 private static int XXX; 私有静态的int,每构造一个Thread对象后递增。
2. 实现Runnable接口创建线程
实现Runnable接口,重写run()方法,创建Runnable对象,构造Thread( Runnable对象 )对象,调用start()开启线程。
package com.bin.demo;
class Demo implements Runnable {
private String name;
Demo(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) { //获得当前线程对象,并获取线程名
System.out.println(Thread.currentThread().getName() + " ————> " + name);
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
//创建Runnable对象
Runnable run0 = new Demo("啊斌");
Runnable run1 = new Demo("雨烟");
//创建线程对象
Thread t0 = new Thread(run0);
Thread t1 = new Thread(run1);
//开启线程
t0.start();
t1.start();
//main线程
for (int i = 0; i < 5; i++) { //获得当前线程对象,并获取线程名
System.out.println(Thread.currentThread().getName());
}
}
}
输出:
main
Thread-0 ————> 啊斌
Thread-0 ————> 啊斌
Thread-0 ————> 啊斌
Thread-1 ————> 雨烟
Thread-1 ————> 雨烟
main
main
main
main
Thread-0 ————> 啊斌
Thread-0 ————> 啊斌
Thread-1 ————> 雨烟
Thread-1 ————> 雨烟
Thread-1 ————> 雨烟
3. 实现Runnable接口的好处
- 避免了继承Thread单继承的局限性(这样就能继承其它类)。
- Runnable接口的出现更符合面向对象,将线程任务进行单独的封装。
- 降低了Thread线程对象和Runnable线程任务的耦合性。
调用start()和run()的区别
- 为什么不直接调用Thread的run()方法,而是调用start()方法呢?
- start()方法是调用底层函数,此函数会请求设备创建一条新线程,并加载run()方法进入新线程的栈内存。
- 如果直接调用run()方法,不会请求设备创建一条新线程,只会被当前线程(调用者)加载进当前线程的栈内存。而run()方法仅仅是提供给程序员重写的方法。
多线程内存图(简)
为什么多线程的方法能一同执行?这不就违背了Java栈先进后出的规则了吗?接下来用一张图来解析:
看图可见,在创建新线程时,JVM的栈内存中分配了对应线程的栈内存。这就能说明多线程中的方法执行顺序是互不干扰的。
多线程的运行状态
了解多线程的状态,掌握线程的生命周期。
所有线程都结束时,进程才结束。
在JDK1.5后,Java线程状态单独用一个类进行了描述:
多线程同步
多线程同步产生的问题:
- 多线程任务处理到共享的数据。
- 多线程任务中有多条代码对共享数据的操作。
一个线程在操作共享数据的过程中,其它线程参与了运算,造成了数据的错误。
解决思想:
只要保证操作共享数据的多条代码在一个时间段,被一条线程所执行,执行期间不允许其它线程参与操作。
1. 多线程卖票产生的问题
package com.bin.demo;
class Ticket implements Runnable {
private int data = 20; //多线程操作任务的共享数据
@Override
public void run() {
while (data > 0) {
System.out.println(Thread.currentThread().getName() + " ————> " + data--); //卖出一张票,并递减
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
//创建Runnable对象
Runnable run = new Ticket();
//创建线程对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//开启线程
t0.start();
t1.start();
t2.start();
}
}
输出:
Thread-1 ————> 20
Thread-0 ————> 20
Thread-2 ————> 19
Thread-0 ————> 17
Thread-0 ————> 15
Thread-0 ————> 14
Thread-0 ————> 13
Thread-0 ————> 12
Thread-0 ————> 11
Thread-0 ————> 10
Thread-0 ————> 9
Thread-0 ————> 8
Thread-0 ————> 7
Thread-0 ————> 6
Thread-0 ————> 5
Thread-1 ————> 18
Thread-1 ————> 3
Thread-1 ————> 2
Thread-1 ————> 1
Thread-0 ————> 4
Thread-2 ————> 16
图解分析问题:
2. 同步代码块
解决卖票问题:把需要同步的多条代码使用synchronized同步关键字进行同步。
2.1 synchronized ( Lock ) { code... }
- 同步的前提:Lock参数,可以是任意类型的对象,每条线程必须使用同一个锁对象。
@Override
public void run() {
while (data > 0) {
synchronized (this) { //this:调用者对象Ticket
System.out.println(Thread.currentThread().getName() + " ————> " + data--); //卖出一张票,并递减
}
}
}
输出:
Thread-0 ————> 20
Thread-1 ————> 19
Thread-1 ————> 18
Thread-1 ————> 17
Thread-1 ————> 16
Thread-1 ————> 15
Thread-1 ————> 14
Thread-0 ————> 13
Thread-0 ————> 12
Thread-0 ————> 11
Thread-0 ————> 10
Thread-0 ————> 9
Thread-0 ————> 8
Thread-0 ————> 7
Thread-0 ————> 6
Thread-0 ————> 5
Thread-0 ————> 4
Thread-0 ————> 3
Thread-0 ————> 2
Thread-0 ————> 1
Thread-2 ————> 0
Thread-1 ————> -1
新问题图解:
2.2 双重判断-解决
观察代码,只要线程得到锁进同步代码块内,再次判断data数据是否大于0。
@Override
public void run() {
while (data > 0) {
synchronized (this) { //this:调用者对象Ticket
if (data > 0) { //解决
System.out.println(Thread.currentThread().getName() + " ————> " + data--); //卖出一张票,并递减
}
}
}
}
输出:
Thread-0 ————> 20
Thread-0 ————> 19
Thread-0 ————> 18
Thread-0 ————> 17
Thread-0 ————> 16
Thread-0 ————> 15
Thread-0 ————> 14
Thread-0 ————> 13
Thread-0 ————> 12
Thread-0 ————> 11
Thread-0 ————> 10
Thread-1 ————> 9
Thread-2 ————> 8
Thread-2 ————> 7
Thread-2 ————> 6
Thread-2 ————> 5
Thread-2 ————> 4
Thread-2 ————> 3
Thread-2 ————> 2
Thread-2 ————> 1
3. 同步函数
- synchronized关键字修饰函数,就是同步函数。
- 不需要程序员传锁。
- 非静态同步函数使用this锁,静态同步函数使用 (字节码对象)类.class锁,类型为Class。(重点注意)
class Ticket implements Runnable {
private int data = 20; //多线程操作任务的共享数据
@Override
public void run() {
while (data > 0) {
ticket();
}
}
private synchronized void ticket() { //同步函数
if (data > 0) { //再次判断,防止数据出错
System.out.println(Thread.currentThread().getName() + " ————> " + data--); //卖出一张票,并递减
}
}
}
输出:
Thread-1 ————> 20
Thread-1 ————> 19
Thread-1 ————> 18
Thread-1 ————> 17
Thread-1 ————> 16
Thread-1 ————> 15
Thread-1 ————> 14
Thread-1 ————> 13
Thread-1 ————> 12
Thread-1 ————> 11
Thread-1 ————> 10
Thread-1 ————> 9
Thread-1 ————> 8
Thread-2 ————> 7
Thread-2 ————> 6
Thread-2 ————> 5
Thread-2 ————> 4
Thread-2 ————> 3
Thread-2 ————> 2
Thread-2 ————> 1
3.1 锁的作用域
线程在拿到一个锁后,在此同步关键字的作用域下都能拿到此锁。
3.2 验证非静态同步函数使用 this 锁
package com.bin.demo;
class Task implements Runnable {
@Override
public void run() {
test();
}
private synchronized void test() {
synchronized (this) {
System.out.println("我是非静态同步函数");
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
new Thread(new Task()).start();
}
}
输出:
我是非静态同步函数
3.3 验证静态同步函数使用 类.class 锁
package com.bin.demo;
class Task implements Runnable {
@Override
public void run() {
test();
}
private static synchronized void test() {
synchronized (Task.class) {
System.out.println("我是静态同步函数");
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
new Thread(new Task()).start();
}
}
输出:
我是静态同步函数
4. 同步代码块与同步函数的区别
同步代码块较为优势,能自定义锁,能锁住需要同步的代码片段,而不是全部代码。
@Override
public void run() {
code...
synchronized (lock) {
code...
}
code...
}
同步函数写法简单,不能自定义锁,直接锁主一个方法,较为局限。而且在某些情况下还会降低同步性能。
5. 同步好处与弊端
好处:解决多线程操作共性数据的安全问题。
弊端:消耗CPU资源(锁的判断:在某一条线程拿到锁后,其它线程是拿不到锁的(这里就是一系列的锁判断),直到持有锁的线程释放锁后,其它线程才有机会拿到锁。),效率降低。(用性能换取安全性是可接受的)
图解弊端:
6. 练习:单列模式并发访问(性能对比)
针对懒汉式单列模式
饿汉式在没有执行静态方法时,类加载就创建对象了,饿汉式是线程安全的
package com.bin.demo;
class SingleA { //同步函数
private static SingleA instance;
private SingleA() {
super();
}
/*
* 每条线程执行时都要判断锁才能进入方法
* 如果第一条线程拿到锁进入并已创建对象,但锁未释放,切换到其它线程时都不能进入方法内,
* 只有等到再次切换回持有锁的线程释放锁后,其它线程才有机会拿到锁。
* 也就是每一条线程要进入同步方法都必须判断锁。
*/
public static synchronized SingleA getInstance() {
if (null == instance) {
instance = new SingleA();
}
return instance;
}
}
class SingleB { //同步代码块
private static SingleB instance;
private SingleB() {
super();
}
/*
* 每条线程直接进方法内
* 如果第一条线程进入方法拿到锁并创建对象,但锁未释放,切换到其它线程进入方法就直接返回对象了。
* 此后后面每一条线程不用判断锁直接返回对象。
*/
public static SingleB getInstance() {
if (null == instance) { //双重判断
synchronized (SingleB.class) {
if (null == instance) { //双重判断
instance = new SingleB(); //需要同步的代码
}
}
}
return instance;
}
}
class Run implements Runnable {
private boolean isFun;
Run (boolean isFun) {
this.isFun = isFun;
}
@Override
public void run() {
long time = System.currentTimeMillis(); //获得开始执行时间
int runCount = 10000000; //每条线程执行次数
if (isFun) {
for (int i = 0; i < runCount; i++) {
SingleA.getInstance(); //同步函数
}
} else {
for (int i = 0; i < runCount; i++) {
SingleB.getInstance(); //同步代码块
}
}
long endTime = System.currentTimeMillis() - time; //计算出运行结束时间
String name = isFun ? "函数 :" : "代码块:";
System.out.println(name + Thread.currentThread().getName() + " ————> " + endTime + "ms 执行完毕");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
//创建Runnable对象
Run runA = new Run(true); //同步函数
Run runB = new Run(false); //同步代码块
//创建线程对象
Thread t0 = new Thread(runA);
Thread t1 = new Thread(runA);
Thread t2 = new Thread(runA);
Thread t3 = new Thread(runB);
Thread t4 = new Thread(runB);
Thread t5 = new Thread(runB);
//开启线程
t0.start();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
输出:
函数 :Thread-2 ————> 442ms 执行完毕
函数 :Thread-1 ————> 632ms 执行完毕
函数 :Thread-0 ————> 638ms 执行完毕
代码块:Thread-3 ————> 41ms 执行完毕
代码块:Thread-4 ————> 58ms 执行完毕
代码块:Thread-5 ————> 59ms 执行完毕
可见同步代码块的速度,因为同步代码块用了双重判断。
死锁
概念:所有的线程都处于冻结状态——>假死状态。(程序员无法得知问题出现,也不会发生程序的异常)
场景一:同步嵌套。
场景二:手动暂停挂起线程。
场景一:同步嵌套
package com.bin.demo;
class MyLock { //定义两个锁
public static final Object LOCK_A = new Object();
public static final Object LOCK_B = new Object();
}
class Task implements Runnable {
private boolean flag;
Task(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
while (true) {
synchronized (MyLock.LOCK_A) {
System.out.println(Thread.currentThread().getName() + "_if ————> LockA");
synchronized (MyLock.LOCK_B) {
System.out.println(Thread.currentThread().getName() + "_if ————> LockB");
}
}
}
} else {
while (true) {
synchronized (MyLock.LOCK_B) {
System.out.println(Thread.currentThread().getName() + "_else ————> LockB");
synchronized (MyLock.LOCK_A) {
System.out.println(Thread.currentThread().getName() + "_else ————> LockA");
}
}
}
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
//创建Runnable对象
Task runA = new Task(true);
Task runB = new Task(false);
//创建线程对象
Thread t0 = new Thread(runA);
Thread t1 = new Thread(runB);
//开启线程
t0.start();
t1.start();
}
}
输出:
Thread-0_if ————> LockA
Thread-1_else ————> LockB
这里直接加无限循环。可见输出只有两行。
死锁过程:
- 假如Thread-0得到CPU资源拿到锁A,这时候CPU马上切换到了Thread-1。
- Thread-1得到CPU资源拿到锁B,继续向下执行判断同步锁A已被Thread-0拿走,则在此等待。
- CPU切回Thread-0向下执行代码,判断同步锁B已被Thread-1拿走,则在此等待。
- 这时候Thread-0和Thread-1都已经处于挂起状态,谁都拿着对方需要的锁不放,导致线程无法继续向下运行。
当然也有和谐的情况,一条线程执行代码直接拿走两个锁后就退出了,所以加无限循环直接看效果。
多线程间的通信
此章节内容较多,主要是代码较多,主要是个人练习,看不过去的可以直接看到Lock接口
首先得了解需要讲解的方法:
1. 线程等待唤醒机制
在使用同步语句synchronized时,锁是任意的,任意对象都有的方法【Object】,与线程相关的方法:
Object
- wait():冻结此监视器(锁)上的线程。该方法可以让线程冻结,并将线程存储到线程池中。
- notify():唤醒此监视器(锁)任意一条线程。取出并唤醒指定线程池中任意一条线程。
- notifyAll():唤醒此监视器(锁)上全部线程。取出并唤醒指定线程池中所有的线程。
非常注意:
- 这些方法必须在同步上使用,因为它们用来操作同步锁上线程的状态。
- 使用这些方法时,必须明确所属的锁,标示方式:锁对象.wait()、锁对象.notify()、锁对象.notifyAll()。
- wait()方法,会让当前监视器(锁)下的线程仅冻结于当前行。
- notify()和notifyAll()方法允许null唤醒,也就是线程池无线程时什么都不做。
线程池
2. 多生产多消费例子
package com.bin.demo;
class Res { //资源
private String name; //商品名
private String[] ress; //存储商品容器
private int count; //商品数量
private int code; //商品编号
private static final int MAX_COUNT = 20; //可存储的最大容量
Res(String name) {
ress = new String[MAX_COUNT]; //资源最大存储容量
this.name = name;
}
public synchronized void put() {
while (!(count < MAX_COUNT)) { //检查商品未满
try {
System.out.println("生产线程:" + Thread.currentThread().getName() + "......已暂停");
this.wait(); //商品满了就暂停生产
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ress[count++] = name + " [" + ++code + "]"; //添加商品,count++更新商品数量,++code对商品进行顺序编号
System.out.println(Thread.currentThread().getName() + " 生产 ++++>" + ress[count - 1]);
this.notifyAll(); //唤醒全部线程
}
public synchronized void get() {
while (!(count > 0)) { //检查是否有商品
try {
System.out.println("消费线程:" + Thread.currentThread().getName() + "......已暂停");
this.wait(); //没有商品则在这里等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
--count;
String obj = ress[count];
ress[count] = null;
System.out.println(Thread.currentThread().getName() + " 消费 ------->" + obj);
this.notifyAll(); //唤醒全部线程
}
}
class Producer implements Runnable {
private Res r;
Producer(Res r) {
this.r = r;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
r.put();
}
}
}
class Consumer implements Runnable {
private Res r;
Consumer(Res r) {
this.r = r;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
r.get();
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
long time = System.currentTimeMillis();
//创建资源
Res r = new Res("雷姆");
//创建任务
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//创建线程
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
Thread t5 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
//main线程等待子线程结束
t0.join();
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
System.out.println("用时 = " + (System.currentTimeMillis() - time) + "ms");
}
}
输出:
Thread-0 生产 ++++>雷姆 [1]
Thread-0 生产 ++++>雷姆 [2]
Thread-0 生产 ++++>雷姆 [3]
Thread-0 生产 ++++>雷姆 [4]
Thread-0 生产 ++++>雷姆 [5]
Thread-0 生产 ++++>雷姆 [6]
Thread-0 生产 ++++>雷姆 [7]
Thread-0 生产 ++++>雷姆 [8]
Thread-0 生产 ++++>雷姆 [9]
Thread-0 生产 ++++>雷姆 [10]
Thread-0 生产 ++++>雷姆 [11]
Thread-0 生产 ++++>雷姆 [12]
Thread-5 消费 ------->雷姆 [12]
Thread-5 消费 ------->雷姆 [11]
Thread-5 消费 ------->雷姆 [10]
Thread-5 消费 ------->雷姆 [9]
Thread-5 消费 ------->雷姆 [8]
Thread-5 消费 ------->雷姆 [7]
Thread-5 消费 ------->雷姆 [6]
Thread-3 消费 ------->雷姆 [5]
Thread-3 消费 ------->雷姆 [4]
Thread-3 消费 ------->雷姆 [3]
Thread-3 消费 ------->雷姆 [2]
Thread-3 消费 ------->雷姆 [1]
消费线程:Thread-3......已暂停
消费线程:Thread-4......已暂停
Thread-1 生产 ++++>雷姆 [13]
Thread-1 生产 ++++>雷姆 [14]
Thread-1 生产 ++++>雷姆 [15]
Thread-1 生产 ++++>雷姆 [16]
Thread-1 生产 ++++>雷姆 [17]
Thread-1 生产 ++++>雷姆 [18]
Thread-1 生产 ++++>雷姆 [19]
Thread-1 生产 ++++>雷姆 [20]
Thread-1 生产 ++++>雷姆 [21]
Thread-1 生产 ++++>雷姆 [22]
Thread-1 生产 ++++>雷姆 [23]
Thread-1 生产 ++++>雷姆 [24]
Thread-1 生产 ++++>雷姆 [25]
Thread-1 生产 ++++>雷姆 [26]
Thread-1 生产 ++++>雷姆 [27]
Thread-1 生产 ++++>雷姆 [28]
Thread-1 生产 ++++>雷姆 [29]
Thread-1 生产 ++++>雷姆 [30]
Thread-1 生产 ++++>雷姆 [31]
Thread-1 生产 ++++>雷姆 [32]
生产线程:Thread-1......已暂停
生产线程:Thread-2......已暂停
Thread-4 消费 ------->雷姆 [32]
Thread-4 消费 ------->雷姆 [31]
Thread-4 消费 ------->雷姆 [30]
Thread-4 消费 ------->雷姆 [29]
Thread-4 消费 ------->雷姆 [28]
Thread-4 消费 ------->雷姆 [27]
Thread-4 消费 ------->雷姆 [26]
Thread-4 消费 ------->雷姆 [25]
Thread-4 消费 ------->雷姆 [24]
Thread-4 消费 ------->雷姆 [23]
Thread-4 消费 ------->雷姆 [22]
Thread-4 消费 ------->雷姆 [21]
Thread-4 消费 ------->雷姆 [20]
Thread-4 消费 ------->雷姆 [19]
Thread-4 消费 ------->雷姆 [18]
Thread-4 消费 ------->雷姆 [17]
Thread-4 消费 ------->雷姆 [16]
Thread-4 消费 ------->雷姆 [15]
Thread-4 消费 ------->雷姆 [14]
Thread-4 消费 ------->雷姆 [13]
消费线程:Thread-4......已暂停
消费线程:Thread-3......已暂停
消费线程:Thread-5......已暂停
Thread-0 生产 ++++>雷姆 [33]
Thread-0 生产 ++++>雷姆 [34]
Thread-0 生产 ++++>雷姆 [35]
Thread-0 生产 ++++>雷姆 [36]
Thread-0 生产 ++++>雷姆 [37]
Thread-0 生产 ++++>雷姆 [38]
Thread-0 生产 ++++>雷姆 [39]
Thread-0 生产 ++++>雷姆 [40]
Thread-0 生产 ++++>雷姆 [41]
Thread-0 生产 ++++>雷姆 [42]
Thread-0 生产 ++++>雷姆 [43]
Thread-0 生产 ++++>雷姆 [44]
Thread-0 生产 ++++>雷姆 [45]
Thread-0 生产 ++++>雷姆 [46]
Thread-0 生产 ++++>雷姆 [47]
Thread-0 生产 ++++>雷姆 [48]
Thread-0 生产 ++++>雷姆 [49]
Thread-0 生产 ++++>雷姆 [50]
Thread-5 消费 ------->雷姆 [50]
Thread-5 消费 ------->雷姆 [49]
Thread-5 消费 ------->雷姆 [48]
Thread-5 消费 ------->雷姆 [47]
Thread-5 消费 ------->雷姆 [46]
Thread-3 消费 ------->雷姆 [45]
Thread-3 消费 ------->雷姆 [44]
Thread-3 消费 ------->雷姆 [43]
Thread-3 消费 ------->雷姆 [42]
Thread-3 消费 ------->雷姆 [41]
Thread-3 消费 ------->雷姆 [40]
Thread-3 消费 ------->雷姆 [39]
Thread-3 消费 ------->雷姆 [38]
Thread-3 消费 ------->雷姆 [37]
Thread-3 消费 ------->雷姆 [36]
Thread-3 消费 ------->雷姆 [35]
Thread-3 消费 ------->雷姆 [34]
Thread-3 消费 ------->雷姆 [33]
消费线程:Thread-3......已暂停
消费线程:Thread-4......已暂停
Thread-2 生产 ++++>雷姆 [51]
Thread-2 生产 ++++>雷姆 [52]
Thread-2 生产 ++++>雷姆 [53]
Thread-2 生产 ++++>雷姆 [54]
Thread-2 生产 ++++>雷姆 [55]
Thread-2 生产 ++++>雷姆 [56]
Thread-2 生产 ++++>雷姆 [57]
Thread-2 生产 ++++>雷姆 [58]
Thread-2 生产 ++++>雷姆 [59]
Thread-2 生产 ++++>雷姆 [60]
Thread-2 生产 ++++>雷姆 [61]
Thread-2 生产 ++++>雷姆 [62]
Thread-2 生产 ++++>雷姆 [63]
Thread-2 生产 ++++>雷姆 [64]
Thread-2 生产 ++++>雷姆 [65]
Thread-2 生产 ++++>雷姆 [66]
Thread-2 生产 ++++>雷姆 [67]
Thread-2 生产 ++++>雷姆 [68]
Thread-2 生产 ++++>雷姆 [69]
Thread-1 生产 ++++>雷姆 [70]
生产线程:Thread-1......已暂停
生产线程:Thread-2......已暂停
Thread-4 消费 ------->雷姆 [70]
Thread-4 消费 ------->雷姆 [69]
Thread-4 消费 ------->雷姆 [68]
Thread-4 消费 ------->雷姆 [67]
Thread-4 消费 ------->雷姆 [66]
Thread-4 消费 ------->雷姆 [65]
Thread-4 消费 ------->雷姆 [64]
Thread-4 消费 ------->雷姆 [63]
Thread-4 消费 ------->雷姆 [62]
Thread-4 消费 ------->雷姆 [61]
Thread-3 消费 ------->雷姆 [60]
Thread-3 消费 ------->雷姆 [59]
Thread-3 消费 ------->雷姆 [58]
Thread-3 消费 ------->雷姆 [57]
Thread-3 消费 ------->雷姆 [56]
Thread-3 消费 ------->雷姆 [55]
Thread-3 消费 ------->雷姆 [54]
Thread-3 消费 ------->雷姆 [53]
Thread-3 消费 ------->雷姆 [52]
Thread-3 消费 ------->雷姆 [51]
消费线程:Thread-3......已暂停
消费线程:Thread-5......已暂停
Thread-2 生产 ++++>雷姆 [71]
Thread-2 生产 ++++>雷姆 [72]
Thread-2 生产 ++++>雷姆 [73]
Thread-2 生产 ++++>雷姆 [74]
Thread-2 生产 ++++>雷姆 [75]
Thread-2 生产 ++++>雷姆 [76]
Thread-2 生产 ++++>雷姆 [77]
Thread-2 生产 ++++>雷姆 [78]
Thread-2 生产 ++++>雷姆 [79]
Thread-2 生产 ++++>雷姆 [80]
Thread-2 生产 ++++>雷姆 [81]
Thread-1 生产 ++++>雷姆 [82]
Thread-1 生产 ++++>雷姆 [83]
Thread-1 生产 ++++>雷姆 [84]
Thread-1 生产 ++++>雷姆 [85]
Thread-1 生产 ++++>雷姆 [86]
Thread-1 生产 ++++>雷姆 [87]
Thread-1 生产 ++++>雷姆 [88]
Thread-1 生产 ++++>雷姆 [89]
Thread-1 生产 ++++>雷姆 [90]
Thread-5 消费 ------->雷姆 [90]
Thread-5 消费 ------->雷姆 [89]
Thread-5 消费 ------->雷姆 [88]
Thread-3 消费 ------->雷姆 [87]
Thread-3 消费 ------->雷姆 [86]
Thread-5 消费 ------->雷姆 [85]
Thread-5 消费 ------->雷姆 [84]
Thread-5 消费 ------->雷姆 [83]
Thread-5 消费 ------->雷姆 [82]
Thread-5 消费 ------->雷姆 [81]
Thread-5 消费 ------->雷姆 [80]
Thread-5 消费 ------->雷姆 [79]
Thread-5 消费 ------->雷姆 [78]
Thread-5 消费 ------->雷姆 [77]
Thread-5 消费 ------->雷姆 [76]
Thread-5 消费 ------->雷姆 [75]
Thread-5 消费 ------->雷姆 [74]
Thread-5 消费 ------->雷姆 [73]
Thread-5 消费 ------->雷姆 [72]
Thread-5 消费 ------->雷姆 [71]
用时 = 27ms
JDK1.4版本前多生产与消费唤醒机制效率问题
每次唤醒都要唤醒全部生产线程与消费线程,也就是在生产唤醒消费线程的同时唤醒了本方生产线程,生产线程也一样。
这是不必要的性能损耗,所以从JDK1.5后出现了新的方案。
3. JDK1.5版本Lock接口
常用方法:
-
lock():获取锁。
-
unlock():释放锁。
-
newCondition():每次调用都会创建新的Condition实例,此对象封装了绑定Lock的监视器(锁)方法。如wait()、notify()、notifyAll()方法。每一个Condition实例都对应着不同的线程池。
Lock接口对象替代了synchronized语句,但是失去了隐式的自动获取锁与自动释放锁的机制,所以要显示获取与释放锁,
Lock使用:
l.lock();
try {
// code...
} finally {
l.unlock();
}
3.1 JDK1.5版本Condition接口
Condition接口替代了Object的监视器方法。并起了新名字:
- await():同wait()。
- signal():同notify()。
- signalAll():同natifyAll()。
Condition的使用:
final Lock lock = new ReentrantLock(); //创建Lock接口实现类
final Condition con_A = lock.newCondition(); //获得与lock绑定的监视器对象
final Condition con_B = lock.newCondition(); //获得与lock绑定的监视器对象
public void add() {
lock.lock();
try {
con_A.await(); //存入con_A线程池
//code...
//code...
//code...
con_B.signalAll(); //通知con_B线程池
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void remove() {
lock.lock();
try {
con_B.await(); //存入con_B线程池
//code...
//code...
//code...
con_A.signalAll(); //通知con_A线程池
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
3.2 使用Lock接口的好处
- 用一个lock锁同步需要的代码(废话)
- 利用绑定此lock锁的Condition接口监视器,存入指定线程池或唤醒指定线程池中的线程。
- 解决1.4版本前同步锁不必要的唤醒开销。
对比1.4前同步锁,与1.5同步锁:
4. 多生产多消费效率解决
在了解Lock接口的锁后,我们就可以解决多生产、消费问题。就能指定存入与唤醒线程池中的线程。解决不必要的性能开销。
更改Res类
class Res { //资源
private String name; //商品名
private String[] ress; //存储商品容器
private int count; //商品数量
private int code; //商品编号
private static final int MAX_COUNT = 20; //可存储的最大容量
//JDK1.5新唤醒机制
final Lock lock = new ReentrantLock();
final Condition producer_con = lock.newCondition();
final Condition consumer_con = lock.newCondition();
Res(String name) {
ress = new String[MAX_COUNT]; //资源最大存储容量
this.name = name;
}
public void put() {
lock.lock();
try {
while (!(count < MAX_COUNT)) { //检查商品未满
System.out.println("生产线程:" + Thread.currentThread().getName() + "......已暂停");
producer_con.await(); //商品满了就暂停生产
}
ress[count++] = name + " [" + ++code + "]"; //添加商品,count++更新商品数量,++code对商品进行顺序编号
System.out.println(Thread.currentThread().getName() + " 生产 ++++>" + ress[count - 1]);
consumer_con.signalAll(); //唤醒消费者
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void get() {
lock.lock();
try {
while (!(count > 0)) { //检查是否有商品
System.out.println("消费线程:" + Thread.currentThread().getName() + "......已暂停");
consumer_con.await(); //没有商品则在这里等待
}
--count;
String obj = ress[count];
ress[count] = null;
System.out.println(Thread.currentThread().getName() + " 消费 ------->" + obj);
producer_con.signalAll(); //唤醒生产者
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
输出:
Thread-1 生产 ++++>雷姆 [1]
Thread-3 消费 ------->雷姆 [1]
消费线程:Thread-3......已暂停
Thread-0 生产 ++++>雷姆 [2]
Thread-0 生产 ++++>雷姆 [3]
Thread-0 生产 ++++>雷姆 [4]
Thread-0 生产 ++++>雷姆 [5]
Thread-0 生产 ++++>雷姆 [6]
Thread-0 生产 ++++>雷姆 [7]
Thread-0 生产 ++++>雷姆 [8]
Thread-0 生产 ++++>雷姆 [9]
Thread-0 生产 ++++>雷姆 [10]
Thread-0 生产 ++++>雷姆 [11]
Thread-0 生产 ++++>雷姆 [12]
Thread-0 生产 ++++>雷姆 [13]
Thread-0 生产 ++++>雷姆 [14]
Thread-0 生产 ++++>雷姆 [15]
Thread-0 生产 ++++>雷姆 [16]
Thread-0 生产 ++++>雷姆 [17]
Thread-0 生产 ++++>雷姆 [18]
Thread-0 生产 ++++>雷姆 [19]
Thread-0 生产 ++++>雷姆 [20]
Thread-0 生产 ++++>雷姆 [21]
生产线程:Thread-0......已暂停
生产线程:Thread-2......已暂停
生产线程:Thread-1......已暂停
Thread-4 消费 ------->雷姆 [21]
Thread-4 消费 ------->雷姆 [20]
Thread-4 消费 ------->雷姆 [19]
Thread-4 消费 ------->雷姆 [18]
Thread-4 消费 ------->雷姆 [17]
Thread-4 消费 ------->雷姆 [16]
Thread-4 消费 ------->雷姆 [15]
Thread-4 消费 ------->雷姆 [14]
Thread-4 消费 ------->雷姆 [13]
Thread-4 消费 ------->雷姆 [12]
Thread-4 消费 ------->雷姆 [11]
Thread-4 消费 ------->雷姆 [10]
Thread-4 消费 ------->雷姆 [9]
Thread-4 消费 ------->雷姆 [8]
Thread-4 消费 ------->雷姆 [7]
Thread-4 消费 ------->雷姆 [6]
Thread-4 消费 ------->雷姆 [5]
Thread-4 消费 ------->雷姆 [4]
Thread-4 消费 ------->雷姆 [3]
Thread-4 消费 ------->雷姆 [2]
消费线程:Thread-4......已暂停
消费线程:Thread-5......已暂停
消费线程:Thread-3......已暂停
Thread-0 生产 ++++>雷姆 [22]
Thread-0 生产 ++++>雷姆 [23]
Thread-0 生产 ++++>雷姆 [24]
Thread-0 生产 ++++>雷姆 [25]
Thread-0 生产 ++++>雷姆 [26]
Thread-0 生产 ++++>雷姆 [27]
Thread-0 生产 ++++>雷姆 [28]
Thread-0 生产 ++++>雷姆 [29]
Thread-0 生产 ++++>雷姆 [30]
Thread-0 生产 ++++>雷姆 [31]
Thread-2 生产 ++++>雷姆 [32]
Thread-2 生产 ++++>雷姆 [33]
Thread-2 生产 ++++>雷姆 [34]
Thread-2 生产 ++++>雷姆 [35]
Thread-2 生产 ++++>雷姆 [36]
Thread-2 生产 ++++>雷姆 [37]
Thread-2 生产 ++++>雷姆 [38]
Thread-2 生产 ++++>雷姆 [39]
Thread-2 生产 ++++>雷姆 [40]
Thread-2 生产 ++++>雷姆 [41]
生产线程:Thread-2......已暂停
生产线程:Thread-1......已暂停
Thread-4 消费 ------->雷姆 [41]
Thread-4 消费 ------->雷姆 [40]
Thread-4 消费 ------->雷姆 [39]
Thread-4 消费 ------->雷姆 [38]
Thread-4 消费 ------->雷姆 [37]
Thread-4 消费 ------->雷姆 [36]
Thread-4 消费 ------->雷姆 [35]
Thread-4 消费 ------->雷姆 [34]
Thread-4 消费 ------->雷姆 [33]
Thread-4 消费 ------->雷姆 [32]
Thread-5 消费 ------->雷姆 [31]
Thread-5 消费 ------->雷姆 [30]
Thread-5 消费 ------->雷姆 [29]
Thread-5 消费 ------->雷姆 [28]
Thread-5 消费 ------->雷姆 [27]
Thread-5 消费 ------->雷姆 [26]
Thread-5 消费 ------->雷姆 [25]
Thread-5 消费 ------->雷姆 [24]
Thread-5 消费 ------->雷姆 [23]
Thread-5 消费 ------->雷姆 [22]
消费线程:Thread-5......已暂停
消费线程:Thread-3......已暂停
Thread-2 生产 ++++>雷姆 [42]
Thread-2 生产 ++++>雷姆 [43]
Thread-2 生产 ++++>雷姆 [44]
Thread-2 生产 ++++>雷姆 [45]
Thread-2 生产 ++++>雷姆 [46]
Thread-2 生产 ++++>雷姆 [47]
Thread-2 生产 ++++>雷姆 [48]
Thread-2 生产 ++++>雷姆 [49]
Thread-2 生产 ++++>雷姆 [50]
Thread-2 生产 ++++>雷姆 [51]
Thread-2 生产 ++++>雷姆 [52]
Thread-2 生产 ++++>雷姆 [53]
Thread-2 生产 ++++>雷姆 [54]
Thread-2 生产 ++++>雷姆 [55]
Thread-2 生产 ++++>雷姆 [56]
Thread-2 生产 ++++>雷姆 [57]
Thread-2 生产 ++++>雷姆 [58]
Thread-2 生产 ++++>雷姆 [59]
Thread-2 生产 ++++>雷姆 [60]
Thread-2 生产 ++++>雷姆 [61]
生产线程:Thread-1......已暂停
Thread-5 消费 ------->雷姆 [61]
Thread-5 消费 ------->雷姆 [60]
Thread-5 消费 ------->雷姆 [59]
Thread-5 消费 ------->雷姆 [58]
Thread-5 消费 ------->雷姆 [57]
Thread-5 消费 ------->雷姆 [56]
Thread-5 消费 ------->雷姆 [55]
Thread-5 消费 ------->雷姆 [54]
Thread-5 消费 ------->雷姆 [53]
Thread-5 消费 ------->雷姆 [52]
Thread-5 消费 ------->雷姆 [51]
Thread-5 消费 ------->雷姆 [50]
Thread-5 消费 ------->雷姆 [49]
Thread-5 消费 ------->雷姆 [48]
Thread-5 消费 ------->雷姆 [47]
Thread-5 消费 ------->雷姆 [46]
Thread-5 消费 ------->雷姆 [45]
Thread-5 消费 ------->雷姆 [44]
Thread-5 消费 ------->雷姆 [43]
Thread-5 消费 ------->雷姆 [42]
消费线程:Thread-3......已暂停
Thread-1 生产 ++++>雷姆 [62]
Thread-1 生产 ++++>雷姆 [63]
Thread-1 生产 ++++>雷姆 [64]
Thread-1 生产 ++++>雷姆 [65]
Thread-1 生产 ++++>雷姆 [66]
Thread-1 生产 ++++>雷姆 [67]
Thread-1 生产 ++++>雷姆 [68]
Thread-1 生产 ++++>雷姆 [69]
Thread-1 生产 ++++>雷姆 [70]
Thread-1 生产 ++++>雷姆 [71]
Thread-1 生产 ++++>雷姆 [72]
Thread-3 消费 ------->雷姆 [72]
Thread-3 消费 ------->雷姆 [71]
Thread-3 消费 ------->雷姆 [70]
Thread-3 消费 ------->雷姆 [69]
Thread-3 消费 ------->雷姆 [68]
Thread-3 消费 ------->雷姆 [67]
Thread-3 消费 ------->雷姆 [66]
Thread-3 消费 ------->雷姆 [65]
Thread-3 消费 ------->雷姆 [64]
Thread-3 消费 ------->雷姆 [63]
Thread-3 消费 ------->雷姆 [62]
消费线程:Thread-3......已暂停
Thread-1 生产 ++++>雷姆 [73]
Thread-1 生产 ++++>雷姆 [74]
Thread-1 生产 ++++>雷姆 [75]
Thread-1 生产 ++++>雷姆 [76]
Thread-1 生产 ++++>雷姆 [77]
Thread-1 生产 ++++>雷姆 [78]
Thread-1 生产 ++++>雷姆 [79]
Thread-1 生产 ++++>雷姆 [80]
Thread-1 生产 ++++>雷姆 [81]
Thread-1 生产 ++++>雷姆 [82]
Thread-1 生产 ++++>雷姆 [83]
Thread-1 生产 ++++>雷姆 [84]
Thread-1 生产 ++++>雷姆 [85]
Thread-1 生产 ++++>雷姆 [86]
Thread-1 生产 ++++>雷姆 [87]
Thread-1 生产 ++++>雷姆 [88]
Thread-1 生产 ++++>雷姆 [89]
Thread-1 生产 ++++>雷姆 [90]
Thread-3 消费 ------->雷姆 [90]
Thread-3 消费 ------->雷姆 [89]
Thread-3 消费 ------->雷姆 [88]
Thread-3 消费 ------->雷姆 [87]
Thread-3 消费 ------->雷姆 [86]
Thread-3 消费 ------->雷姆 [85]
Thread-3 消费 ------->雷姆 [84]
Thread-3 消费 ------->雷姆 [83]
Thread-3 消费 ------->雷姆 [82]
Thread-3 消费 ------->雷姆 [81]
Thread-3 消费 ------->雷姆 [80]
Thread-3 消费 ------->雷姆 [79]
Thread-3 消费 ------->雷姆 [78]
Thread-3 消费 ------->雷姆 [77]
Thread-3 消费 ------->雷姆 [76]
Thread-3 消费 ------->雷姆 [75]
Thread-3 消费 ------->雷姆 [74]
Thread-3 消费 ------->雷姆 [73]
用时 = 22ms
当然这里线程比较少,而且直接同步一个方法,看不出什么性能上的变化。
记住Lock接口的Condition监视器能指定线程存入的线程池和唤醒指定线程池中的线程就OK了。
wait()和sleep()的区别
- wait()释放执行权(锁)并释放执行资格,并且wait()方法只能在监视器(锁)上(synchronized)中使用。
- sleep()释放执行权,不释放执行资格。
- 共同点:wait()和sleep()都能指定传入超时时间。
异常信息在多线程中的阅读
Thread[Thread-0,5,main] //直接输出线程的信息
- Thread-0:线程名。
- 5:线程优先级,这个为默认优先级。
- main:Thread-0所在的线程组,也就是main线程启动的Thread-0线程。
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at com.bin.demo.Main$1.run(Main.java:11)
- Exception in thread "Thread-0":异常在名叫 Thread-0的线程发生。
- java.lang.ArithmeticException:这个位置是异常所在的包以及类型。
- : / by zero:这个位置是异常详细信息。
- at com.bin.demo.Main$1.run(Main.java:11):异常所在的代码行数,包含了所在包及类,$符号1.run:是因为我这里使用了匿名内部类的方式创建对象,自动取名叫1,然后是1类里的方法名run(),所在位置11行。
线程优先级
Thread中的优先级静态字段:
优先级值范围 1 ~ 10
- MAX_PRIORITY = 10:最高优先级
- MIN_PRIORITY = 1:最低优先级
- NORM_PRIORITY = 5:默认优先级
通过setPriority(int newPriority)方法设置优先级。
小列子理解线程优先级:
package com.bin.demo;
class Task implements Runnable {
private int count = 60;
@Override
public void run() {
sop();
}
private void sop() {
for (int i = 0; i < 11; i++) {
synchronized (this) {
if (!(count > 0)) {
return;
}
System.out.println(Thread.currentThread() + "————> " + count--);
}
}
}
}
public class Main {
public static void main(String[] args) {
Runnable task = new Task();
for (int i = 0; i < 6; i++) {
if (i < 3) {
new Thread(task).start();
} else {
Thread t = new Thread(task);
t.setPriority(Thread.MAX_PRIORITY);
t.start();
}
}
}
}
输出:
Thread[Thread-0,5,main]————> 60
Thread[Thread-0,5,main]————> 59
Thread[Thread-0,5,main]————> 58
Thread[Thread-0,5,main]————> 57
Thread[Thread-0,5,main]————> 56
Thread[Thread-0,5,main]————> 55
Thread[Thread-0,5,main]————> 54
Thread[Thread-0,5,main]————> 53
Thread[Thread-5,10,main]————> 52
Thread[Thread-5,10,main]————> 51
Thread[Thread-5,10,main]————> 50
Thread[Thread-5,10,main]————> 49
Thread[Thread-5,10,main]————> 48
Thread[Thread-5,10,main]————> 47
Thread[Thread-5,10,main]————> 46
Thread[Thread-5,10,main]————> 45
Thread[Thread-5,10,main]————> 44
Thread[Thread-5,10,main]————> 43
Thread[Thread-5,10,main]————> 42
Thread[Thread-1,5,main]————> 41
Thread[Thread-1,5,main]————> 40
Thread[Thread-1,5,main]————> 39
Thread[Thread-1,5,main]————> 38
Thread[Thread-1,5,main]————> 37
Thread[Thread-1,5,main]————> 36
Thread[Thread-1,5,main]————> 35
Thread[Thread-1,5,main]————> 34
Thread[Thread-1,5,main]————> 33
Thread[Thread-1,5,main]————> 32
Thread[Thread-1,5,main]————> 31
Thread[Thread-2,5,main]————> 30
Thread[Thread-2,5,main]————> 29
Thread[Thread-2,5,main]————> 28
Thread[Thread-2,5,main]————> 27
Thread[Thread-2,5,main]————> 26
Thread[Thread-2,5,main]————> 25
Thread[Thread-2,5,main]————> 24
Thread[Thread-2,5,main]————> 23
Thread[Thread-2,5,main]————> 22
Thread[Thread-4,10,main]————> 21
Thread[Thread-4,10,main]————> 20
Thread[Thread-4,10,main]————> 19
Thread[Thread-4,10,main]————> 18
Thread[Thread-4,10,main]————> 17
Thread[Thread-4,10,main]————> 16
Thread[Thread-4,10,main]————> 15
Thread[Thread-4,10,main]————> 14
Thread[Thread-4,10,main]————> 13
Thread[Thread-4,10,main]————> 12
Thread[Thread-4,10,main]————> 11
Thread[Thread-3,10,main]————> 10
Thread[Thread-3,10,main]————> 9
Thread[Thread-3,10,main]————> 8
Thread[Thread-3,10,main]————> 7
Thread[Thread-3,10,main]————> 6
Thread[Thread-3,10,main]————> 5
Thread[Thread-3,10,main]————> 4
Thread[Thread-3,10,main]————> 3
Thread[Thread-3,10,main]————> 2
Thread[Thread-3,10,main]————> 1
设置线程优先级的意义并不是很大,还是取决于CPU的切换算法
join()方法
作用:某一线程A调用了某一线程B对象的join()方法后,线程A将等待线程B执行结束后,线程A才会继续向下执行。
package com.bin.demo;
class Task implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "___" + i);
}
System.out.println(Thread.currentThread().getName() + "线程结束");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Runnable task = new Task();
Thread t0 = new Thread(task);
t0.start();
t0.join(); //当前线程等待此线程结束再执行(main线程执行了其它线程的join()方法)
System.out.println(Thread.currentThread().getName() + " 线程结束");
}
}
输出:
Thread-0___1
Thread-0___2
Thread-0___3
Thread-0___4
Thread-0___5
Thread-0线程结束
main 线程结束
注意:join()方法要放到start()方法之后,因为如果线程没启动根本就不用等待。
守护线程
Java中前台线程与后台线程的概念:只要前台线程都结束,不管有多少个后台(守护)线程,这些后台线程都将被强制结束。守护线程即为后台线程。
setDaemon(boolean on) :使用此方法进行标记线程是否为守护(后台)线程,true守护,默认false前台线程。
package com.bin.demo;
class Task implements Runnable {
@Override
public void run() {
boolean flag = true;
while (flag) { //无限循环
// not code...
}
System.out.println(Thread.currentThread().getName() + "线程结束");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Runnable task = new Task();
Thread t0 = new Thread(task);
t0.setDaemon(true); //标记为守护线程
t0.start();
Thread t1 = new Thread(task);
t1.setDaemon(true); //标记为守护线程
t1.start();
System.out.println(Thread.currentThread().getName() + " 线程结束");
}
}
输出:
main 线程结束
可见守护线程是无限循环的,main线程结束后,守护线程直接强制结束了。
线程停止
很遗憾的是stop()方法有缺陷已过时不允许使用,已用interrupt()方法替代。
在API文档中的中断一词谁听谁蒙逼。
interrupt()方法:
interrupt()方法的主要功能就是将阻塞状态清除。
下面将用API文档提示的stop()信息描述写一个示列:基于一个变量进行终止运行的代码,如果线程长时间阻塞等待,则使用interrupt()方法中断阻塞等待:
package com.bin.demo;
class Task implements Runnable {
private boolean flag = true;
@Override
public void run() {
task();
System.out.println(Thread.currentThread().getName() + "线程结束");
}
private synchronized void task() {
while (flag) { //标记变量
try {
this.wait(); //等待
} catch (InterruptedException e) {
System.out.println("接收到中断消息 = " + e);
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
Thread t0 = new Thread(task);
t0.start();
long millis = 0L;
while (true) {
Thread.sleep(millis += 1000);
if (millis > 3000) { //3秒后检查
if (t0.isAlive()) { //检查是否处于活动状态
task.setFlag(false); //清除标记,退出任务
t0.interrupt(); //中断等待
break; //退出while
}
}
}
}
}
输出:
接收到中断消息 = java.lang.InterruptedException
Thread-0线程结束
调用interrupt()中断线程等待,线程将接收到一个java.lang.InterruptedException异常消息。