并发编程线程基础
什么是线程
线程是CPU调度的最小单位。是进程的组成部分,一个进程至少包含一个线程
线程的创建与运行
java创建线程的方式jdk提供了3种方式,这里不包括线程池创建
- 继承
Thread
类,并且重写Thread
的run
方法,实例化Thread
对象 并且运行线程的start
方法
class ThreadOne extends Thread{
@Override
public void run(){
for(;;){
System.out.println("running");
}
}
}
public static void main(String[] args) {
Thread one = new ThreadOne();
one.start();
}
- 实现
Runnable
接口,重写run
方法,创建Thread
对象并且将实现Runnable
接口的实例传入Thread
的构造函数
class ThreadTwo implements Runnable{
@Override
public void run() {
for (;;){
System.out.println("running");
}
}
}
public static void main(String[] args) {
Thread two = new Thread(new ThreadTwo());
two.start();
}
3.实现Callable
接口,并且从写call
方法,创建FutureTask
对象 ,将实现Callable
接口的线程传入FutureTask
的构造函数,再创建Thread
对象,将FutureTask
对象传入Thread
的构造函数,运行Thread
的start
方法
class ThreadThree implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("running");
return "running";
}
}
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<>(new ThreadThree());
Thread three = new Thread(futureTask);
three.start();
}
实现Callable
接口与另外2种不同的是,该线程可以拥有返回值 ,在实现接口的时候可以传入泛型指定改线程的返回值。然后再调用futureTask.get()
获取返回值,如果没有线程没有执行完毕 那么获取改线程接口的线程会阻塞等待
线程通知与运行
线程的通知notify
与notifyAll
,wait
方法,
- 线程的运行(run方法)
当线程被创建,调用线程的start()方法及运行,注意这里的运行并不是线程执行,而是处于就绪状态,必须等到它拿到CPU的执行权才能真正运行。 - 线程的等待(wait方法)
wait
与notify
和notifyAll
方法是Object
的方法, 而Object
是所有类的父类。并不是线程的方法,所以再使用wait以及notity等方法的时候,必须获取到这个对象的监视器锁,而这个监视器锁的获取方法就是使用synchronized关键字获取这个Object的锁
当调用object的wait方法而没有获取锁的时候,就会抛出IllegalMonitorStateException
异常
public static void main(String[] args) {
Object object = new Object();
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Exception in thread "main" java.lang.IllegalMonitorStateException
//正确方式
Object object = new Object();
try {
synchronized (object){
object.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
调用wait方法会让当前线程处于WAIT状态,当其他线程调用对象锁对象的notify或者notifyAll方法。notify方法与notifyAll方法的不同在于notify只会唤醒一个等待种的线程 而具体唤醒哪一个线程取决于哪个线程抢占到了CPU的执行权 所以说synchronized是一个非公平锁,它不能保证先进入等待状态的线程先被唤醒。
public class TestWait {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
new Thread(()->{
synchronized (object){
try {
System.out.println("线程一进入等待");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程一执行完毕");
}
}).start();
new Thread(()->{
synchronized (object){
try {
System.out.println("线程二进入等待");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程二执行完毕");
}
}).start();
new Thread(()->{
synchronized (object){
try {
System.out.println("线程三进入等待");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程三执行完毕");
}
}).start();
Thread.sleep(2000);
synchronized (object){
object.notify();
}
}
}
启动了3个线程。3个线程获取到监视器锁之后 都进入了等待 同时说明了 wait方法会释放它得到的锁对象 从而别的而线程会获取到锁,进入等待状态,主线程睡眠2秒后,获取到锁对象,调用notify方法,唤醒等待3个线程中的其中一个。
线程一进入等待
线程二进入等待
线程三进入等待
线程一执行完毕
然而线程并未结束,因为还有线程二和线程三处于等待状态。
synchronized (object){
object.notifyAll();
}
线程一进入等待
线程二进入等待
线程三进入等待
线程三执行完毕
线程二执行完毕
线程一执行完毕
notifyAll方法会拥有改锁对象的所有等待线程。
wait方法还拥有一个参数,
public final native void wait(long timeout) throws InterruptedException;
该方法传入一个超时时间 单位为毫秒。表示该线程最多等待timeout的时间,如果没有被其他线程唤醒,那么就再timeout后重新进入cpu执行权的抢占。
当然再wait的时候,是可以被其他线程打断的,
Thread thread = new Thread(() -> {
synchronized (object) {
try {
System.out.println("线程三进入等待");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程三执行完毕");
}
});
thread.start();
Thread.sleep(2000);
thread.interrupt();
线程thread运行后获取监视器锁后,进入等待。主线程睡眠2秒后 执行线程的thread的 interrupr方法 ,那么该线程也是会被终止的。
线程三进入等待
java.lang.InterruptedException
线程三执行完毕
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.iron.man.TestWait.lambda$main$2(TestWait.java:44)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
等待线程执行终止的join方法
再实际运用中往往会出现一个需求,方法一和方法二再运行,必须等待他们运行结束后 再运行方法三,这时候就需要使用join方法。join方法是线程的方法,表示调用join方法的线程执行完毕之后 才能继续执行其他的线程。