先看看这张经典的图
Java线程调度
在多线程中,Java虚拟机必须实现一个有优先权的、基于优先级的调度机制。这意味着Java程序中的每一个线程被分配到一定的优先权。优先级可以被开发者改变。优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。
线程的优先权
- 使用时没有指定优先权时,默认是普通优先级。
- 优先级高的线程将会优先被执行,但是不能保证线程在启动时就进入运行状态。
- 调度程序决定哪一个线程被执行。
- 使用setPriority() 方法来设置线程的优先级。
- 线程在被使用前需要设置优先级,否则为默认普通优先。
java虚拟机有自己的指令集以及各种运行时内存区域,Java 虚拟机是整个 Java 平台的基石,是 Java 技术用以实现硬件无关与操作系统无关的关键部分。其中线程调度与常见的linux系统类似,但并不限定线程是以时间片运行,如果感兴趣可以查阅Linux的书籍了解系统线程调度的过程。
yield()方法
yield意味着放手,放弃,投降。一个调用yield()方法的线程告诉虚拟机它乐意让其他线程占用自己的位置。
源码注释介绍:
A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint .
简单测试
未加入yieid方法
public class Example {
public static void main(String[] args) {
Thread t1 = new Example1(); // 创建线程1
Thread t2 = new Example2(); // 创建线程2
t1.setPriority(Thread.MIN_PRIORITY); // 运行前设置优先级为最低
t2.setPriority(Thread.MAX_PRIORITY); // 运行前设置优先级为最高
t1.start();
t2.start();
}
}
class Example1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++){
System.out.println("t1 ---- " + i);
}
}
}
class Example2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++){
System.out.println("t2 -- " + i);
}
}
}
结果输出
t2 -- 0
t2 -- 1
t2 -- 2
t2 -- 3
t2 -- 4
t1 ---- 0
t1 ---- 1
t1 ---- 2
t1 ---- 3
t1 ---- 4
结果也表明我们设置的线程优先级更高的t2先运行,然后再执行t1,但此结果不是唯一,与运行时线程的状态有关,并不是每次都是顺序的(与调度的方法有关,调度从多方面来决定是否运行该线程,有时调度器觉得某个线程排队等候时间过长,即使优先级较低,也可能先于高优先级的线程先执行)。
加入yield()方法
public class Example {
public static void main(String[] args) {
Thread t1 = new Example1(); // 创建线程1
Thread t2 = new Example2(); // 创建线程2
t1.setPriority(Thread.MIN_PRIORITY); // 运行前设置优先级为最低
t2.setPriority(Thread.MAX_PRIORITY); // 运行前设置优先级为最高
t1.start();
t2.start();
}
}
class Example1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++){
System.out.println("t1 ---- " + i);
Thread.yield();
}
}
}
class Example2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++){
System.out.println("t2 -- " + i);
Thread.yield();
}
}
}
结果输出:
t2 -- 0
t1 ---- 0
t2 -- 1
t1 ---- 1
t2 -- 2
t1 ---- 2
t2 -- 3
t1 ---- 3
t2 -- 4
t1 ---- 4
线程执行完任务后,会调用yield()方法,来告诉虚拟机愿意把让出cpu让其他线程先执行,所以我们会看到t1与t2交替着运行,此结果并不是唯一,与调度的方法有关。(仿佛有点鸡肋)
join()方法
源码如上图,注释也清楚的写明了等待这个线程结束,同时join还有一个带参数的重载方法,意思为等待X秒时间,如果过了X秒线程还未结束,那就不等了,接下去做别的事情。
代码示例
public class Example {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Example1();
t1.start();
// t1.join();
System.out.println(new Date());
}
}
class Example1 extends Thread{
@Override
public void run() {
try {
System.out.println(new Date());
Thread.sleep(3000);
}catch (Exception e){
}
}
}
当不加入join方法时,两个时间打印几乎在同时就打印了出来,也就是说t1的线程放在了后台运行,主线程没有等待t1的睡眠结束,再接下去执行。 如果加入了join方法,那么t1线程在打印完当前时间后,进入到睡眠状态,此时主线程会一直等待t1睡眠结束之后,然后打印第二个时间。
带超时时间的代码示例
public class Example {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Example1();
t1.start();
t1.join(2000);
System.out.println(new Date());
}
}
class Example1 extends Thread{
@Override
public void run() {
try {
System.out.println(new Date());
Thread.sleep(3000);
}catch (Exception e){
}
}
}
在这个带超时时间的join方法,我们告诉主线程,等待t1线程2秒的时间,如果2秒内线程未结束,则不继续等待,往下执行。因为我们的t1线程在打印时间后要睡眠3秒钟,2秒内不会结束,所以主线程会再2秒后去打印另一个时间打印,如下图所示
wait(),notify(),notifyAll()使用
- wait()、notify/notifyAll() 要在synchronized 代码块执行,说明当前线程一定是获取了锁的。
- 当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
- 当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized代码块的代码或是中途遇到wait() ,再次释放锁。
- notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。
- notify和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。
- notify和notifyAll()的区别只是notify只唤醒一个,选择哪个线程执行由JVM决定。notifyAll()会唤醒所有等待的线程,至于那个线程被先执行取决于JVM。
代码实例
工厂接口
public interface MachineFactory {
void production(int num);
void consume(int num);
}
工厂接口实现类
public class MachineFactoryImpl implements MachineFactory {
// 最大库存量(爆仓啦)
private final int MAX_STORAGE = 100;
// 仓库空间
private LinkedList list = new LinkedList();
@Override
public void production(int num) {
// 负责生产产品
// 生产前需要判断仓库满了没有,如果仓库放不下,则等待被消费后,满足空间条件再生产
// 生产时需要持有list的锁,避免因为同时生产和消费时,产生队列下标异常的错误。
synchronized (list){
while (list.size() + num > MAX_STORAGE){
System.out.println("【当前仓库库存量: "+list.size()+"】,【即将生产的货量: "+num+"】 等待仓库位置空间!");
try {
Thread.sleep(2000);
// 线程释放锁,进入等待状态
list.wait();
}catch (Exception e){
e.printStackTrace();
}
}
// 满足生产条件进行生产
for (int i = 0; i<num; i++){
list.add(new Object());
}
System.out.println("【生产的数量:"+num + "】"+"【当前仓库的库存:"+list.size() + "】");
// 此时可能存在消费者在等待工厂生产产品,所以要通知所有处于wait状态的消费线程
// 他们将收到状态改变的通知进行判断是否执行程序代码
list.notifyAll();
}
}
@Override
public void consume(int num) {
// 负责消费产品
// 消费的前提是拥有足够的产品,若是仓库不存在这么多的产品,则进行等待,直到有这么多的产品进行消费
synchronized (list){
while (list.size() < num){
System.out.println("【当前仓库库存量: "+list.size()+"】,【即将消费的数量: "+num+"】 等待工厂生产!");
try {
Thread.sleep(2000);
// 让出锁,进入等待状态
list.wait();
}catch (Exception e){
e.printStackTrace();
}
}
// 满足条件
for (int i = 0; i<num; i++){
list.remove();
}
System.out.println("【消耗的数量:"+num +"】,【当前仓库剩余:"+list.size() + "】");
// 此时仓库可能已经爆仓,需要通知生产线程进行过了一次消费,此时生产现场收到状态会进行重新的检查
list.notifyAll();
}
}
}
生产者
public class Producer extends Thread{
private int number;
private MachineFactory factory;
private Producer(){
// 不提供无参构造函数
}
public Producer(MachineFactory f, int n){
this.factory = f;
this.number = n;
}
@Override
public void run() {
factory.production(number);
}
public void setNumber(int number) {
this.number = number;
}
}
消费者
public class Consumer extends Thread {
private int number;
private MachineFactory factory;
private Consumer(){
}
public Consumer(MachineFactory f , int n){
this.factory = f;
this.number = n;
}
@Override
public void run() {
factory.consume(number);
}
public void setNumber(int number) {
this.number = number;
}
}
代码中调用
public static void main(String[] args) {
// 实例化一个工厂
MachineFactoryImpl machineFactory = new MachineFactoryImpl();
// 实例化几个生产者和消费者,这些生产者和消费者都在同一个工厂进行操作,并且是并行的
Producer p1 = new Producer(machineFactory,30);
Producer p2 = new Producer(machineFactory,50);
Producer p3 = new Producer(machineFactory,50);
Producer p4 = new Producer(machineFactory,100);
//
Consumer c1 = new Consumer(machineFactory,20);
Consumer c2 = new Consumer(machineFactory,70);
c1.start();
c2.start();
p1.start();
p2.start();
p3.start();
p4.start();
}
运行结果
【当前仓库库存量: 0】,【即将消费的数量: 20】 等待工厂生产!
【生产的数量:100】【当前仓库的库存:100】
【当前仓库库存量: 100】,【即将生产的货量: 50】 等待仓库位置空间!
【当前仓库库存量: 100】,【即将生产的货量: 50】 等待仓库位置空间!
【消耗的数量:70】,【当前仓库剩余:30】
【生产的数量:30】【当前仓库的库存:60】
【当前仓库库存量: 60】,【即将生产的货量: 50】 等待仓库位置空间!
【当前仓库库存量: 60】,【即将生产的货量: 50】 等待仓库位置空间!
【消耗的数量:20】,【当前仓库剩余:40】
【生产的数量:50】【当前仓库的库存:90】
【当前仓库库存量: 90】,【即将生产的货量: 50】 等待仓库位置空间!