Java并发:线程基础

什么是线程

操作系统通过进程来抽象程序的运行。运行一个Java程序,实际上就是让操作系统创建并开始一个JVM进程。

运行一个程序需要做很多工作,例如加载程序代码、初始化静态数据、动态内存管理、函数调用与执行等等。

抽象来说,这些工作可分为两类:资源分配(内存管理)和指令执行(CPU调度)。操作系统把两者分离开来,程序的代码执行实际是用线程来表示的,所以线程是操作系统CPU调度的基本单元。

线程依赖于进程,是CPU调度的基本单元。一个进程至少有一个线程(主线程),它们共享进程的资源,但拥有独立的栈。栈用于存储该线程私有的局部变量,还用来存放线程的函数调用栈帧。

在这里插入图片描述

Thread和Runnable

Java中使用Thread类表示线程。要创建一个线程,可以直接创建该对象:

Thread t=new Thread();

线程是用来执行任务的,那么,该如何指派任务给线程呢?Java把任务抽象出来,用Runnable接口表示。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

我们可以定义自己的类实现Runnable接口:

package demo.threading;

public class Task implements Runnable {
	@Override
	public void run() {
		System.out.println("任务开始执行");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("任务执行完毕");

	}
}

由@FunctionalInterface注解可知,Runnble接口还是函数式接口,如果没有自定义的数据域,可以直接用Lambda表达式:

Runnable task = () -> {
	System.out.println("任务开始执行");
	try {
		Thread.sleep(5000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	System.out.println("任务执行完毕");
};

为了方便为线程指派任务,Thread类实现了Runnable接口:

public class Thread implements Runnable {
	private Runnable target;//要执行的任务
	
	public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
	@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

如上源码所示,我们可以使用构造函数Thread(Runnable target),在创建线程的时候为它指派任务:

Thread t=new Thread(new Task());

或者:

Thread t = new Thread(() -> {
	System.out.println("任务开始执行");
	try {
		Thread.sleep(5000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	System.out.println("任务执行完毕");
});

当然,我们也可以继承Thread类,重写run()方法:

public class MyThread extends Thread {

	@Override
	public void run() {
		System.out.println(Thread.currentThread() + ": 我是一个线程");
	}

}

线程调度与生命周期

线程创建之后,何时开始执行任务?Thread类提供了start()方法,调用该方法表示开始该线程。

public synchronized void start() {
	//非NEW状态,表示已经执行过了
	if (threadStatus != 0)
        throw new IllegalThreadStateException();

    group.add(this);
    
    boolean started = false;
    try {
        start0();//native方法,调用target任务的run()方法
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */
        }
    }
}

start()方法只能调用一次,所以加了synchronized,防止多个线程同时调用Thread对象的start()方法。

从上面代码中看到,start()方法中尝试调用native方法start0(),即它希望线程start后立即执行任务。但该方法由JVM调用,由于JVM线程与操作系统线程是一一对应的,所以真正进行线程调度的是操作系统

Thread类使用threadStatus变量来表示线程当前的状态,可以调用getState()方法查看。它的可能值由枚举类State表示:

public enum State {
    //新建状态。start方法还没调用,还没在JVM中运行
    NEW,
    
    //运行状态。正在JVM中运行,但它有可能在等待操作系统调度
    RUNNABLE,
    
	//阻塞状态,正在等待监视器锁。线程在同步块/方法中调用了共享变量的wait方法,
    BLOCKED,

    /**
    * 等待状态.一直等待另一个线程执行特定操作。
    * 可能由以下原因造成:
    *  1.调用共享变量的不带超时参数的Object.wait方法。等待另一个线程调用Object.notify方法或Object.notifyAll。 
    *  2.调用不带超时参数的Thread.join方法。等待它加入的那个线程终止。
    *  3.调用LockSupport.park
    */
    WAITING,

    /**
     * 超时等待状态,与WAITING类似,只不过在指定时间内等不到目标线程指定动作发生后会停止等待。
     * 可能由以下原因造成:
     *  1.调用Thread.sleep方法睡眠一段时间。等待自己的睡眠时间结束。
     *  2.调用共享变量的带超时参数的Object.wait方法。等待另一个线程调用Object.notify方法或Object.notifyAll。 
     *  3.调用带超时参数的Thread.join方法。等待它加入的那个线程终止。
     *  4.调用LockSupport.parkNanos
     *  5.调用LockSupport.parkUntil
     */
    TIMED_WAITING,

    //终止状态,线程已经执行完了。注意,线程是不能restart的。
    TERMINATED;
}

线程中断

一个线程可以尝试中断另一个线程。Java中线程中断有一定的规则:

  1. 线程不能中断自己,废话。
  2. 当前线程如果想要中断另一个线程。可以调用那个线程的interrupt()方法。
  3. 收到中断请求后,具体如何响应是线程自己的事,它可以忽略这个请求,也可以立即退出执行。

Thread类中三个关于中断的方法:

  • void interrupt()方法:调用目标线程实例的interrupt()方法中断目标线程。
  • boolean isinterrupted()方法:调用目标线程实例的isinterrupted()返回目标线程的中断标记。
  • static boolean interrupted()静态方法:Thread.interrupted()方法返回当前线程的中断标记,同时还会清除该中断标记

示例:RUNNABLE状态下响应中断。

public class Test {

	public static void main(String[] args) {

		Thread t = new Thread(() -> {
			// 测试一个长任务
			while (true) {
				System.out.println("线程t的状态:" + Thread.currentThread().getState());
				if (Thread.currentThread().isInterrupted()) {
					System.out.println("线程t:听说有人想干我?我怂了,我退出");
					return;
				}
			}
		});

		t.start();// 启动

		t.interrupt();// 中断线程t

		// 等待线程t执行完
		try {
			t.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("线程t的状态:" + t.getState());
		System.out.println("主线程退出");
	}
}

但是,如果目标线程处于等待状态(WAITING或TIMED_WAITING),能否响应中断呢?

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

这就是为什么调用上述方法时,IDE会提示我们捕获InterruptedException异常。线程因为调用这些方法而阻塞时,这时如果中断它,它会清除中断标记,并且抛出InterruptedException。

public class Test {

	public static void main(String[] args) {

		Thread t1 = new Thread(() -> {
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				System.out.println("线程t1:我被中断了,我立即退出");
				e.printStackTrace();
			}
		});

		System.out.println(LocalDateTime.now());
		t1.start();// 启动
		t1.interrupt();// 中断线程t1

		// 等待线程t1执行完
		try {
			t1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("线程t1的状态:" + t1.getState());
		System.out.println(LocalDateTime.now());
		System.out.println("主线程退出");
	}

}

从结果可以看到,默认情况是线程终止,并抛出异常:

2019-06-30T12:53:15.878
线程t1:我被中断了,我立即退出
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at demo.threading.Test.lambda$0(Test.java:11)
	at java.lang.Thread.run(Thread.java:748)
线程t1的状态:TERMINATED
2019-06-30T12:53:15.880
主线程退出

wait()和notify()

当线程调用共享变量上的wait()方法时,它会处于WAITING状态,等待另一个线程调用该共享变量上的notify()或notifAll()方法。

显然,等待和通知用于线程间的通信。当多个线程使用共享变量时,可能需要等待这个共享变量满足某个条件,这时就可以使用wait和notify机制。

最典型的例子就是生产者和消费者,它们并发运行,一个向共享变量生产数据,一个向共享变量消费数据。当共享变量没有数据时,消费者需要等待某个生产者生产一些数据;当共享变量的数据满了时,生产者需要等待某个消费者消费一些数据。

生产者示例:

package demo.threading;

import java.util.Queue;

public class Producer implements Runnable {

	private Queue<Integer> queue;

	public Producer(Queue<Integer> queue) {
		super();
		this.queue = queue;
	}

	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			produce();
		}
	}

	private void produce() {
		synchronized (queue) {
			// 队列满了,等待
			while (queue.size() == ProducerAndConsumerDemo.ELEMENTS_MAX_SIZE) {
				try {
					System.out.println("Producer: queue is full,waiting for some consumers ...");
					queue.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			// 队列未满,生产一个值,并通知消费者
			int ele = (int) (1 + Math.random() * 10);
			queue.add(ele);
			System.out.println("Producer: produce one element:" + ele);

			queue.notifyAll();
		}
	}

}

消费者示例:

package demo.threading;

import java.util.Queue;

public class Consumer implements Runnable {

	private Queue<Integer> queue;

	public Consumer(Queue<Integer> queue) {
		super();
		this.queue = queue;
	}

	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			consume();
		}
	}

	private void consume() {
		synchronized (queue) {
			// 队列空,等待
			while (queue.size() == 0) {
				try {
					System.out.println("Consumer: queue is empty,waiting for some producers ...");
					queue.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			// 队列不空,消费一个值,并通知生产者
			int ele = queue.remove();
			System.out.println("Consumer: consume one element:" + ele);
			queue.notifyAll();
		}
	}

}

运行示例代码:

package demo.threading;

import java.util.LinkedList;
import java.util.Queue;

public class ProducerAndConsumerDemo {
	// 为了测试结果出现队列满的情况,把size设置得小一点
	public static final int ELEMENTS_MAX_SIZE = 5;

	public static void main(String[] args) {
		Queue<Integer> elements = new LinkedList<>();
		Thread consumer = new Thread(new Consumer(elements));
		Thread producer = new Thread(new Producer(elements));
		consumer.start();
		producer.start();

	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值