【Java】多线程并发基础——细节决定成败

目录

进程与线程

多线程技术原理

1. CPU切片

2. CPU多核处理

3. 合理利用CPU资源

JVM中的多线程与垃圾回收

创建线程的二中方式

1. 继承Thread类创建线程

2. 实现Runnable接口创建线程

3. 实现Runnable接口的好处

调用start()和run()的区别

多线程内存图(简)

多线程的运行状态

多线程同步

1. 多线程卖票产生的问题

2. 同步代码块

2.1 synchronized ( Lock ) { code... }

2.2 双重判断-解决

3. 同步函数

3.1 锁的作用域

3.2 验证非静态同步函数使用 this 锁

3.3 验证静态同步函数使用 类.class 锁

4. 同步代码块与同步函数的区别

5. 同步好处与弊端

6. 练习:单列模式并发访问(性能对比)

死锁

场景一:同步嵌套

多线程间的通信

1. 线程等待唤醒机制

线程池

2. 多生产多消费例子

JDK1.4版本前多生产与消费唤醒机制效率问题

3. JDK1.5版本Lock接口

3.1 JDK1.5版本Condition接口

3.2 使用Lock接口的好处

4. 多生产多消费效率解决

wait()和sleep()的区别

异常信息在多线程中的阅读

线程优先级

join()方法

守护线程

线程停止


进程与线程

  • 进程:应用程序在内存中分配的空间。(正在运行中的程序)
  • 线程:负责程序执行的单元,也称为执行路径。(需要线程来执行代码)。一个进程至少包含一条线程,如果一个进程中出现了条线程,此程序就为多线程程序。

多线程技术原理

多线程解决多部分代码同时执行的需求。

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接口的好处

  1. 避免了继承Thread单继承的局限性(这样就能继承其它类)。
  2. Runnable接口的出现更符合面向对象,将线程任务进行单独的封装。
  3. 降低了Thread线程对象和Runnable线程任务的耦合性。

调用start()和run()的区别

  • 为什么不直接调用Thread的run()方法,而是调用start()方法呢?
  • start()方法是调用底层函数,此函数会请求设备创建一条新线程,并加载run()方法进入新线程的栈内存
  • 如果直接调用run()方法,不会请求设备创建一条新线程,只会被当前线程(调用者)加载进当前线程的栈内存。而run()方法仅仅是提供给程序员重写的方法。

多线程内存图(简)

为什么多线程的方法能一同执行?这不就违背了Java栈先进后出的规则了吗?接下来用一张图来解析:

看图可见,在创建新线程时,JVM的栈内存中分配了对应线程的栈内存。这就能说明多线程中的方法执行顺序是互不干扰的。

多线程的运行状态

了解多线程的状态,掌握线程的生命周期。

所有线程都结束时,进程才结束。

在JDK1.5后,Java线程状态单独用一个类进行了描述:

多线程同步

多线程同步产生的问题:

  1. 多线程任务处理到共享的数据。
  2. 多线程任务中有多条代码对共享数据的操作。

一个线程在操作共享数据的过程中,其它线程参与了运算,造成了数据的错误。

解决思想:

        只要保证操作共享数据的多条代码在一个时间段,被一条线程所执行,执行期间不允许其它线程参与操作。

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

这里直接加无限循环。可见输出只有两行。

死锁过程:

  1. 假如Thread-0得到CPU资源拿到锁A,这时候CPU马上切换到了Thread-1。
  2. Thread-1得到CPU资源拿到锁B,继续向下执行判断同步锁A已被Thread-0拿走,则在此等待。
  3. CPU切回Thread-0向下执行代码,判断同步锁B已被Thread-1拿走,则在此等待。
  4. 这时候Thread-0和Thread-1都已经处于挂起状态,谁都拿着对方需要的锁不放,导致线程无法继续向下运行。

当然也有和谐的情况,一条线程执行代码直接拿走两个锁后就退出了,所以加无限循环直接看效果。

多线程间的通信

此章节内容较多,主要是代码较多,主要是个人练习,看不过去的可以直接看到Lock接口

首先得了解需要讲解的方法:

1. 线程等待唤醒机制

在使用同步语句synchronized时,锁是任意的,任意对象都有的方法【Object】,与线程相关的方法:

Object

  • wait():冻结此监视器(锁)上的线程。该方法可以让线程冻结,并将线程存储到线程池中
  • notify():唤醒此监视器(锁)任意一条线程。取出并唤醒指定线程池中任意一条线程
  • notifyAll():唤醒此监视器(锁)上全部线程。取出并唤醒指定线程池中所有的线程

非常注意:

  1. 这些方法必须在同步上使用,因为它们用来操作同步锁上线程的状态
  2. 使用这些方法时,必须明确所属的锁,标示方式:锁对象.wait()锁对象.notify()锁对象.notifyAll()
  3. wait()方法,会让当前监视器(锁)下的线程仅冻结于当前行。
  4. 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()的区别

  1. wait()释放执行权(锁)并释放执行资格,并且wait()方法只能在监视器(锁)上(synchronized)中使用。
  2. sleep()释放执行权,不释放执行资格。
  3. 共同点: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异常消息。

  • 26
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虚妄狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值