一、线程和进程概念
1、线程概念
线程是沉程序执行的最小单位,是进程中的一个执行路径,一个进程可以包含多个线程,它们共享进程的内存空间。操作系统在分配资源时是把资源分配给进程的, 但是CPU 资源比较特殊, 它是被分配到线程的, 因为真正要占用CPU 运行的是线程, 所以也说线程是CPU 分配的基本单位(cpu直接分配给线程,不经过进程)。
多个线程共享进程的堆、方法区、直接内存等共享资源。
方法区则用来存放JVM 加载的类、常量及静态变量等信息,也是线程共享的。
2、进程概念
进程(Process) 是操作系统中资源分配的基本单位(Cpu资源除外),是程序的一次执行实例。简单说,进程 = 程序 + 执行上下文。
3、并发和并行
- 并发(Concurrency):同一时间段内交替执行多个任务(单核 CPU 实现)
- 并行(Parallelism):多个任务同时执行(多核 CPU 实现)
二、线程创建三种形式
Java 中有三种线程创建方式,分别为实现Runnable 接口 的run 方法,继承Thread 类并重写run 的方法,使用FutureTask 方式配合Callable。
1、继承Thread类的方式创建线程(不推荐)
1、创建类继承Thread
创建形式如下:
java
package com.example.basic;
public class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
for(int i = 0; i < 1000; i++){
System.out.println(name+" "+i);
}
}
}
执行情况:
java
MyThread myThread1 = new MyThread("李四");
myThread.setDaemon(true);
myThread.start();
2、匿名内部类的形式
java
Thread thread = new Thread(()->{
System.out.println("线程开始执行");
});
thread.start();
3、缺点
继承Thread的形式具有很明显的缺点:
- 继承了Thread,占用了唯一继承的机会,影响java中单继承的形式,不够灵活,一个任务只能够一个线程执行
2、实现Runnable的形式(推荐)
1、创建类实现Runnable接口
java
package com.example.basic;
public class RunnableThread implements Runnable{
private String name;
public RunnableThread(String name){
this.name = name;
}
@Override
public void run() {
for(int i = 0; i < 1000; i++){
System.out.println(name+" "+i);
}
}
}
执行情况:
java
Thread thread = new Thread(new RunnableThread("赵六"));
thread.start();
2、匿名内部类的形式
java
Thread runableThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程开始执行");
}
});
runableThread.start();
3、实现Callable+FutureTask的形式
java
package com.example.basic;
import java.util.concurrent.Callable;
public class CallableThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 100;
}
}
实现情况
java
CallableThread callable = new CallableThread();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread callableThread = new Thread(futureTask);
callableThread.start();
三、线程等待和通知(wait()和notify())
Java中的Object类是所有类的父类,鉴于继承机制,Java把所有类都需要的方法放到了Object类里面,其中就包含本节要讲的通知与等待系列函数。
1、wait()
当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起,直到发生下面几件事情之一才返回:
- 其他线程调用了该共享对象的notify()或者notifyAll()的方法
- 其他线程调用了该线程的interrupt()方法,该线程抛出异常。另外需要注意的是,如果调用wait() 方法的线程没有事先获取该对象的监视器锁,则调用wait()方法时调用线程会抛出IllegalMonitorStateException 异常。
wait的重要特征如下:
当执行wait()之后,该线程会释放锁,并且释放cpu。
一个线程如何才能获取一个共享变量的监视器锁呢?
- 执行synchronized同步代码块时,使用该共享变量作为参数。
java
synchronized (共享变量) {
//doSomething
}
- 调用该共享变量的方法,并且该方法使用了synchronized 修饰
java
ynchronized void add(int a,int b) {
// doSomething
}
另外需要注意的是,一个线程可以从挂起状态变为可以运行状态(也就是被唤醒),即使该线程没有被其他线程调用notify()、notifyAll() 方法进行通知,或者被中断,或者等待超时,这就是所谓的虚假唤醒。
虽然虛假唤醒在应用实践中很少发生,但要防患于未然,做法就是不停地去测试该线程被唤醒的条件是否满足,不满足则继续等待,也就是说在一个循环中调用wait()方法进行防范。退出循环的条件是满足了唤醒该线程的条件。
java
synchronized (obj ){
while (条件不满足) {
obj .wait() ;
}
}
当一个线程调用共享对象的wait()方法被阻塞挂起后,如果其他线程中断了该线程,则该线程会抛出InterruptedException异常并返回。(wait执行过程:该线程被中断之后,会首先清除中断状态,然后抛出异常)
java
package cn.tx.wait_nofity;
public class WaitNotifyInterupt {
static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
//创建线程
Thread threadA = new Thread(new Runnable() {
public void run() {
try {
System.out.println("---begin---");
//阻塞当前线程
synchronized (obj) {
obj.wait();
}
System.out.println("---end---");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
threadA.start();
Thread.sleep(1000);
System.out.println("---begin interrupt threadA---");
threadA.interrupt();
System.out.println("---end interrupt threadA---");
boolean interrupted = threadA.isInterrupted();
System.out.println(interrupted);
}
}
查看结果如下:
java
---begin---
---begin interrupt threadA---
---end interrupt threadA---
false
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at cn.tx.wait_nofity.WaitNotifyInterupt$1.run(WaitNotifyInterupt.java:14)
at java.lang.Thread.run(Thread.java:748)
返回值为false,是因为中断状态被清除了
2、wait(timeOut)
该方法相比wait()方法多了一个超时参数,它的不同之处在于,如果一个线程调用共享对象的该方法挂起后,没有在指定的timeoutms时间内被其他线程调用该共享变量的notify(或者notifyAll()方法唤醒,那么该函数还是会因为超时而返回。如果将timeout设置为0则和wait方法效果一样,因为在wait方法内部就是调用了wait(0)。 需要注意的是,如果在调用该函数时,传递了一个负的timeout则会抛出IllegalArgumentException异常。
**<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">wait(timeout)</font>**超时后不会自动重复执行,而是会从 **<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">wait()</font>**方法返回,继续执行后面的代码。
3、notify()函数
一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的 。此外,被唤醒的线程不能马上从wait方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回,也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行。类似wait系列方法,只有当前线程获取到了共享变量的监视器锁后,才可以调用共享变量的notify()方法,否则会抛出llgalMonitorStateException异常。
4、notifyAll()
不同于在共享变量上调用notify()函数会唤醒被阻塞到该共享变量上的一个线程,notifyAll()方法则会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。
java
package cn.tx.wait_nofity;
public class NotifyAllTest {
//创建资源
private static volatile Object resourceA = new Object();
public static void main(String[] args) throws InterruptedException {
//创建线程
Thread threadA = new Thread(new Runnable() {
public void run() {
//获取resourceA共享资源的监视器锁
synchronized (resourceA) {
System.out.println("threadA get resourceA lock");
try {
System.out.println("threadA begin wait");
resourceA.wait();
System.out.println("threadA end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//创建线程
Thread threadB = new Thread(new Runnable() {
public void run() {
synchronized (resourceA) {
System.out.println("threadB get resourceA lock");
try {
System.out.println("threadB begin wait");
resourceA.wait();
System.out.println("threadB end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//创建线程
Thread threadC = new Thread(new Runnable() {
public void run() {
synchronized (resourceA) {
System.out.println("threadC begin notify");
resourceA.notify();
//resourceA.notifyAll();
}
}
});
//启动线程
threadA.start();
threadB.start() ;
Thread. sleep (1000) ;
threadC.start() ;
//等待线程结束
// threadA. join() ;
// threadB. join() ;
// threadC.join() ;
System.out.println ("main over") ;
}
}
如上代码开启了三个线程,其中线程A和线程B分别调用了共享资源resourceA的wait()方法,线程C则调用了nofity() 方法。这里启动线程C前首先调用sleep 方法让主线程休眠1s,这样做的目的是让线程A和线程B全部执行到调用wait方法后再调用线程C的notify方法。这个例子试图在线程A和线程B都因调用共享资源resourceA的wait()方法而被阻塞后,让线程C再调用resourceA的notify()方法,从而唤醒线程A和线程B。
但是从执行结果来看,只有一个线程A被唤醒,线程B没有被唤醒:
java
threadA get resourceA lock
threadA begin wait
threadB get resourceA lock
threadB begin wait
main over
threadC begin notify
threadA end wait
从输出结果可知线程调度器这次先调度了线程A占用CPU来运行,线程A首先获取resourceA上面的锁,然后调用resourceA的wait()方法挂起当前线程并释放获取到的锁,然后线程B获取到resourceA上的锁并调用resourceA的wait()方法,此时线程B也被阻塞挂起并释放了resourceA. 上的锁,到这里线程A和线程B都被放到了resourceA 的阻塞集合里面。线程C休眠结束后在共享资源resourceA上调用了notify() 方法,这会激活resourceA的阻塞集合里面的一个线程,这里激活了线程A,所以线程A调用的wait()方法返回了,线程A执行完毕。而线程B还处于阻塞状态。如果把线程C调用的notify()方法改为调用notifyAll()方法,则执行结果如下。
java
threadA get resourceA lock
threadA begin wait
threadB get resourceA lock
threadB begin wait
main over
threadC begin notify
threadB end wait
threadA end wait
总结:
.wait和notify必须要在synchronized代码块之内
四、LockSupport
在java并发包下各种同步组件的底层实现中,LockSupport的身影处处可见。JDK中的定义为用来创建锁和其他同步类的线程阻塞。
我们可以使用它来阻塞和唤醒线程,功能和wait,notify有些相似,但是LockSupport比起wait,notify功能更强大,也好用的多。
wait、notify的缺点
- · wait和notify都是Object中的方法,在调用这两个方法前必须先获得锁对象,这限制了其使用场合:只能在同步代码块中。
- · 当对象的等待队列中有多个线程时,notify只能随机选择一个线程唤醒,无法唤醒指定的线程。
使用LockSupport的话,我们可以在任何场合使线程阻塞,同时也可以指定要唤醒的线程,相当的方便。
java
package cn.tx.wait_nofity;
import java.util.concurrent.locks.LockSupport;
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
Thread parkThread = new Thread(new ParkThread());
parkThread.start();
Thread.sleep(3000);
LockSupport.unpark(parkThread);
}
static class ParkThread implements Runnable{
@Override
public void run() {
System.out.println("开始线程阻塞");
//挂起当前的线程
LockSupport.park();
System.out.println("结束线程阻塞");
}
}
}
park()相当于wait(),unpark相当于notify,能指定线程唤起。
LockSupport.park();可以用来阻塞当前线程,park是停车的意思,把运行的线程比作行驶的车辆,线程阻塞则相当于汽车停车,相当直观。该方法还有个变体LockSupport.park(Object blocker),指定线程阻塞的对象blocker,该对象主要用来排查问题。方法LockSupport.unpark(Thread thread)用来唤醒线程,因为需要线程作参数,所以可以指定线程进行唤醒。
五、Join方法
java
package cn.tx.join;
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("child threadOne over!");
}
});
Thread threadTwo = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("child threadTwo over!");
}
});
//启动子线程
threadOne. start() ;
threadTwo.start() ;
System.out.println("wait all child thread over!") ;
//等待子线程执行完毕,返回
threadOne. join() ;
threadTwo. join() ;
System.out.println ("all child thread over") ;
}
}
执行情况如下:
java
wait all child thread over!
child threadOne over!
child threadTwo over!
all child thread over
如上代码在主线程里面启动了两个子线程,然后分别调用了它们的join()方法,那么主线程首先会在调用threadOne.join()方法后被阻塞,等待threadOne执行完毕后返回。threadOne执行完毕后threadOne.join() 就会返回,然后主线程调用threadTwo.join()方法后再次被阻塞,等待threadTwo执行完毕后返回。这里只是为了演示join方法的作用,在这种情况下使用后面会讲到的CountDownLatch是个不错的选择。
java
package cn.tx.join;
public class JoinTest1 {
public static void main(String[] args) throws InterruptedException {
//线程one
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadOne begin run!");
for (;;) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("==========");
}
}
});
//获取主线程
final Thread mainThread = Thread.currentThread();
//线程two
Thread threadTwo = new Thread(new Runnable() {
@Override
public void run() {
//休眠1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断主线程
mainThread.interrupt();
}
});
//启动子线程
threadOne.start();
//延迟1s启动线程
threadTwo.start();
try {//等待线程one执行结束
threadOne.join();
} catch (InterruptedException e) {
System.out.println("main thread:" + e);
}
System.out.println("main线程执行完毕");
}
}
如上代码在threadOne线程里面执行死循环,主线程调用threadOne的join方法阻塞自己等待线程threadOne执行完毕,待threadTwo休眠1s后会调用主线程的interrupt()方法设置主线程的中断标志,从结果看在主线程中的threadOne.join()处会抛出InterruptedException异常。这里需要注意的是,在threadTwo里面调用的是主线程的interrupt()方法,而不是线程threadOne的。
六、sleep方法
Thread类中有一个静态的sleep方法,当一个执行中的线程调用了Thread的sleep方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与CPU的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的。指定的睡眠时间到了后该函数会正常返回,线程就处于就绪状态,然后参与CPU的调度,获取到CPU资源后就可以继续运行了。如果在睡眠期间其他线程调用了该线程的interrupt()方法中断了该线程,则该线程会在调用sleep方法的地方抛出InterruptedException异常而返回
总结:sleep会释放cpu,但是不会释放锁
java
package cn.tx.sleep;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SleepTest {
// 创建一个独 占锁
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
//创建线程A
Thread threadA = new Thread(new Runnable() {
public void run() {
//获取独占锁
lock.lock();
try {
System.out.println("child threadA is in sleep");
Thread.sleep(1000);
System.out.println("child threadA is in awaked");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
});
//创建线程B
Thread threadB = new Thread(new Runnable() {
public void run() {
//获取独占锁
lock.lock();
try {
System.out.println("child threadB is in sleep");
Thread.sleep(1000);
System.out.println("child threadB is in awaked");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
});
//启动线程
threadA.start();
threadB.start();
}
}
七、中断
Java中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。
interrupt()
中断线程,例如,当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志为true并立即返回。设置标志仅仅是设置标志,线程A实际并没有被中断,它会继续往下执行。如果线程A因为调用了wait系列函数、join方法或者sleep方法而被阻塞挂起,这时候若线程B调用线程A的interrupt()方法,线程A会在调用这些方法的地方抛出InterruptedException 异常而返回
isInterrupted()
检测当前线程是否被中断,如果是返回true,否则返回false。注意:现在处于执行中,不能是结束状态,否则返回false
java
package cn.tx.interrupt;
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//如果当前线程被中断则退出循环
while (!Thread.currentThread().isInterrupted())
System.out.println(Thread.currentThread() + "hello");
}
});
//启动子线程
thread.start();
//主线程休眠1s,以便中断前让子线程输出.
Thread.sleep(50);
//中断子线程
System.out.println("main thread interrupt thread");
thread.interrupt();
//等待子线程执行完毕
thread.join();
System.out.println("main is over");
}
}

在如上代码中,子线程thread通过检查当前线程中断标志来控制是否退出循环,主线程在休眠50ms后调用thread的interrupt()方法设置了中断标志,所以线程thread退出了循环。
当线程为了等待一些特定条件的到来时,一般会调用sleep函数、wait 系列函数或者join() 函数来阻塞挂起当前线程。比如一个线程调用了Thread.sleep(3000),那么调用线程会被阻塞,直到3s后才会从阻塞状态变为激活状态。但是有可能在3s内条件已被满足,如果一直等到3s后再返回有点浪费时间,这时候可以调用该线程的interrupt()方法,强制sleep方法抛出InterruptedException 异常而返回,线程恢复到激活状态。下面看一个例子。
java
package cn.tx.interrupt;
public class InterruptTest1 {
public static void main(String[] args) throws InterruptedException {
Thread threadOne = new Thread(new Runnable() {
public void run() {
try {
System.out.println("threadOne begin sleep for 2000 seconds");
Thread.sleep(2000000);
System.out.println("threadone awaking");
} catch (InterruptedException e) {
System.out.println("thread0ne is interrupted while sleeping");
return;
}
System.out.println("threadOne- leaving normally");
}
});
//启动线程
threadOne.start();
//确保子线程进入休眠状态
Thread.sleep(1000);
//打断子线程的休眠,让子线程从sleep函数返回
threadOne.interrupt();
//等待子线程执行完毕
threadOne.join();
System.out.println("main thread is over");
}
}