目录
3、wait(long timeout,int nanos)
1.11.4 InheritableThreadLocal类
一、并发编程线程基础
1.1 什么是线程
说线程,一定要知道进程。进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,而线程是进程的一个执行路径。线程是不会独立存在的,一个进程中至少有一个线程,多个线程共享进程的资源。
系统的资源分配给进程,而cpu资源比较特殊,是直接分配给线程的,因为真正要占用cpu的是线程,也就是说线程是cpu资源的基本分配单位。
进程的堆和方法区的资源被线程共享。程序计数器和栈是每个线程私有的。
- 程序计数器:前边提到线程是占用cpu的基本单位,而cpu是以时间片轮转的方式轮询被线程占用的,当前线程的获得时间片消耗完后要让出cpu,直到下次再轮到自己。程序计数器就是记录线程的执行地址,从而当下次再获得时间片时可以从上次的执行位置继续执行。注意:当执行的是native方法时,程序计数器记录的是undefined。只有执行java方法时,程序计数器才记录的是地址。
- 栈: 线程私有,存放该线程的局部变量、基本数据类型的变量,局部变量是线程私有的,其他线程不能访问。还存放该线程的调用栈帧(栈帧:随方法调用而创建,随方法结束而消亡。存放该方法的变量)。
- 堆:堆是进程中内存最大的一块区域,是在进程创建时系统给分配的,堆内的资源被该进程下的所有线程共享,主要存放new操作创建的实例对象。
- 方法区:这一区域被线程共享,主要存放JVM加载的类、常量和静态变量等信息。
1.2 线程的创建与运行
线程创建有3个方法,一是实现Runable接口的run()方法,二是继承Thread类重写run()方法,三是使用FutureTask()方法。
以下为3种创建方式的代码:
public class CreateThread {
//线程创建方法一:继承Thread,重写run方法
public static class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("重写run方法");
}
}
//main方法
public static void main(String[] args) {
Thread myThread1 = new MyThread1();
myThread1.start();
}
}
这是通过继承Thread类创建线程。它的好处是获取当先线程时可以直接用this获取,而不必使用Thread.currentThread()方法。缺点是由于一个类只能继承一个类,再业务中需要继承其他类时就不能使用这种方法了。
注意:创建Thread实例后表示已经获得了除cpu之外的资源,此时线程处于就绪态。在执行start方法后会分配cpu资源, 然后执行run方法,run方法执行结束后,线程进入终止状态。
//线程创建方法二:实现Runable接口
public static class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("实现run方法");
}
}
//main方法
public static void main(String[] args) {
MyThread2 task = new MyThread2();
new Thread(task).start();
new Thread(task).start();
}
这是通过实现Runnable接口创建线程。但是以上两种方法都有一个问题,他们执行的任务(即run方法)没有返回值。FutureTask可以解决这个问题。
//创建任务类,类似Runnable方式。返回值类型可自定义
public static class CallTask implements Callable<String>{
@Override
public String call() throws Exception {
return "hello";
}
}
//main方法
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<>(new CallTask());
String result = null;
new Thread(futureTask).start();
try {
result = futureTask.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
小结:继承Thread的方式使用简单,但是存在java单继承的问题。实现RUnnable接口避免了这个问题,但是这两种形式中的任务代码都没有返回值。想要任务执行结束后返回数据就要使用实现FutureTask接口的方式了。
1.3 线程通知和等待
1、wait() 函数
当一个线程调用一个共享变量的wait()时,该调用线程会被阻塞挂起并释放该共享变量上的锁。直到发生以下事情之一才返回:(1)有其他线程调用了该共享变量的notify()或notifyAll()方法 (2)其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。
注意,如果调用wait()方法时,没有实现获取该对象的监视器锁,则调用wait()方法时会抛出IllegalMonitorStateException异常。
获得共享变量的方法有两个:
(1)使用synchronized()同步代码块,并使用该共享变量作为参数:
synchronized(共享变量){
//dosomething
//此时获取了共享变量的监视器锁,可以调用共享变量的wait()方法
}
(2)一个被synchronized 修饰的方法,去调用共享变量时:
synchronized void add (int a, int b){
//dosomething
//在此方法里调用共享变量的wait()方法即可。
}
另外一个需要注意的是,线程可以从挂起状态变成运行状态(唤醒),即便没有被其他线程调用notify或notifyAll方法,或被中断、等待超时,这就是虚假唤醒。(虚假唤醒 可以理解为:线程在不符合唤醒条件的情况下被唤醒了。)
这种情况虽然很少发生,但也需要预防。可以使用循环判断是否符合唤醒条件,如果不符合则继续挂起。
synchronized(obj){
while(唤醒条件不满足){
obj.wait();
}
}
·在调用共享变量的wait()时,只会释放该共享变量上的锁,如果当前线程存在其他的共享变量的锁,则他们不会被释放。
·在线程被挂起等待期间调用该线程的interrupt()方法时,会抛出interruptedException异常,返回异常后中断线程。
2、wait(long timeout)
多了一个超时参数,当被挂起的线程一直没有被唤醒且等待时间超过指定的timeout时,该线程会因为超时而返回。如果参数设置为0,则效果与不传参一样,会一直挂起。因为wait()函数的内部就是调用的wait(0)。如果参数传负值,则会抛出IllegalArgumentException异常。
3、wait(long timeout,int nanos)
·当999999>nanos>0时,timeout会自增1 。
·当nanos传入0时,效果与wait(long timeout)一样。
其他情况抛出对应的异常信息。
4、notify()函数
一个线程调用某共享变量的notify()函数后,会唤醒一个因调用该共享变量的wait系列方法而被挂起的线程。如果有多个线程因同一个共享变量的wait系列函数而挂起,那么调用notify()后被唤醒的线程是随机的。
另外,notify()函数与wait系列函数一样,必须在已经获取到该共享变量的监视器锁才能调用,否则抛出IllegalMonitorStateException异常。
还要注意,被唤醒的线程不能马上从挂起状态进入执行状态,他必须获取到该共享变量的监视器锁才能进入执行状态。而即便他被notify()唤醒,仍然可能竞争不到该共享变量的监视器锁。
5、notifyAll()函数
不同于notify()函数只能唤醒某一个线程,当一个线程调用某共享变量的notifyAll()函数时,会唤醒所有因调用该共享变量的wait系列方法而被挂起的线程。
下边是一个代码示例:
package com.learnThread.demo.part1;
/**
* @Author: tongys
* @Date: 2019/12/23
*/
public class NotifyThread {
private static volatile Object resourceA = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread_A = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA){
System.out.println("thread_A get resource's lock .");
try {
System.out.println("thread_A begin wait...");
resourceA.wait();
System.out.println("thread_A end wait...");
Thread.sleep(2000); //证明A、B两线程在同时被唤醒时,会竞争同一个监视器锁,从而同时只会有一个线程进入执行状态。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread_B = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA){
System.out.println("thread_B g