一、线程之间的状态转换图
我们主要来看一下关于阻塞的几个状态:
阻塞 | 状态描述 |
---|---|
等待阻塞 | 当运行的线程调用wait()方法时,线程会释放对象锁被放入到等待队列池中,如果其他线程执行了notify或者是notifyAll()方法,那么线程就会放到锁定池中。 |
同步阻塞 | 运行的线程如果想要获取对象锁,一旦有一个线程获取了对象锁,那么其他线程就会进入锁定池中等待,知道当前线程执行完同步方法或代码释放锁。 |
其他阻塞 | 如果运行的线程执行sleep()方法或join()方法,或者发出了I/O请求,sleep()状态超时或者join等待的线程终止,那么线程就可以重新进入就绪状态。 |
二、 java.lang.Object的Runnable接口和Thread类
如果我们向创建一个线程类,那么我们可以使用实现Runnable和继承Thread类两种方式实现
1、Runnable
如果我们向创建一个线程类,那么我们可以使用implements Runnable接口的方式实现;然后重写Runnable接口中的run方法就可以
public class Mythread implements Runnable{
@Override
public void run(){
//编写具体的代码
}
}
如果我们想要执行当前线程类,那么我们只要采用下面的步骤就可以了
MyThread myThread = new Thread();
Thread thread = new Thread(myThread);
thread.start();
使用implements Runnable接口的方式去实现一个类的好处主要在于一个类只能继承一个超类,但是可以实现多个接口;同时我们可以将这个线程类传递给多个Thread对象,然后访问线程类中的公共资源
2、Thread
如果我们向创建一个线程类,那么我们可以使用另一种方式,extends Thread类的方式实现;然后重写Thread类中的run方法就可以。
2.1、Thread.join
- join():如果在一个线程t1中调用另一个线程t2的join()方法,那么t1将会等待t2执行完以后,继续执行线程t1
- join(long mills):如果在一个线程t1.调用t2.join(t_num),那么此时t1只会等待t2线程运行完t_num时长以后,- 不管t2有没有运行完,此时都会开始运行线程t1
- join(long mills,int nanos):相比较上一个方法只是在时间精度上提升了
实例1:当我们不使用join方法时,可以看看执行结果为main线程会先执行,然后执行Mythread线程的方法
public class TestJoin { public static void main(String[] args){ MyThread thread = new MyThread(); thread.start(); System.out.println(Thread.currentThread().getName()+"is Running"); } } class MyThread extends Thread{ @Override public void run(){ System.out.println(Thread.currentThread().getName()+"is Running"); } }
实例二:当我们使用join方法,同时我们在Mythread设置sleep(),查看结果;
public class TestJoin { public static void main(String[] args){ MyThread thread = new MyThread(); thread.start(); //System.out.println(Thread.currentThread().getName()+" is Running"); try { thread.join(1000); System.out.println(Thread.currentThread().getName()+"Join Finish"); } catch (InterruptedException e) { e.printStackTrace(); } } } class MyThread extends Thread{ @Override public void run(){ System.out.println(Thread.currentThread().getName()+" Begin Running"); try { Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"End sleep"); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出结果
Thread-0 Begin Running mainJoin Finish Thread-0End sleep
我们可从程序中看到,当我们在主线程中调用了Mythread线程的join(1000),表明此时主线程会等待MyThread线程执行1s的时间,此时main线程进入阻塞状态,我们的Mythread线程开始执行sleep(2000),也会进入到阻塞状态;当main线程等待的时间用完,此时main线程就会开始执行,执行完以后。系统并没有马上结束,然后Mythread的sleep()执行完就会由阻塞状态;
到从执行结果看到,当Mythread执行sleep()时,并不会马上执行main线程,而是等待1s时间用完以后,就会马上执行main线程,然后又继续执行Mythread()方法。(这里涉及对象的wait方法和线程的转态转换过程)实例三:同样是在main线程中调用Mythread类的join,但是这个时候我们在Mythread睡眠的时间是小于main线程等待的时间的
package java_lang_Object; /** * Created by luckyboy on 2018/7/5. */ public class TestJoin { public static void main(String[] args){ MyThread thread = new MyThread(); thread.start(); //System.out.println(Thread.currentThread().getName()+" is Running"); try { thread.join(1000); System.out.println(Thread.currentThread().getName()+" Join Finish"); } catch (InterruptedException e) { e.printStackTrace(); } } } class MyThread extends Thread{ @Override public void run(){ System.out.println(Thread.currentThread().getName()+" Begin Running"); try { Thread.sleep(200); System.out.println(Thread.currentThread().getName()+" End sleep"); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出结果
Thread-0 Begin Running Thread-0 End sleep main Join Finish
从结果我们可以看到我们在main线程中调用了MyThread的join方法,设定等待的时长是1s,此时main线程处于阻塞状态;我们执行MyThread线程之后就会进入睡眠0.2s,也会进入阻塞状态,睡眠之后此时线程就会进入到可运行的状态,有JVM进行调用,然后开始执行Mythread线程,此时Mythread线程在main线程唤醒之前会被运行完。
- join()方法的源码
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//isAlive()是一个native方法,判断当前线程是否是活着的
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
我们从join()方法中可以看到,join()采用一个同步synchronized,此时main线程会进入lock bolcked pool中等待回去Mythread的对象锁,我们在使用synchronized的时候,join方法有一个隐形的this参数,synchronized 就是获得了Mythread的对象锁。一旦join方法传递进来的时间到wait(delay)方法执行完毕,此时main线程就会获得Mythread 的对象锁,这个时候main线程从lock blocked pool中的状态变成了。Runnable转态
2.2、Thread.sleep
Thread中有以下几个sleep的方法
- sleep(long millis) :如果线程t调用了该方法,那么线程的状态就会由运行态转换成阻塞状态,直到sleep中设置的时间用完以后,线程再一次回到Runnable状态,这个时候线程等待JVM的调用。
sleep(long mills,int nanos):该方法就是在一个时间上更加精准的sleep(long mills)方法
需要注意的是,当一个线程调用sleep方法时,这个线程并不会释放所占用的资源。我有一点疑问就是,如果一个线程在睡眠时,那么重新回到Runnable状态等待JVM调度的时候,是一直拥有着该线程之前占用的资源,还是在JVM调度的时候会重新竞争资源
三、Object的wait()、notify()、notifyAll()与synchronized关键字
3.1 synchronized 关键字
- synchronized 是Java语言的关键字,当它用来修饰一个方法或者一个代码块时保证只能有一个线程来访问该方法或者代码块;synchronized针对内存区块申请内存锁
- Java的超类Object有一个自己的内存锁,如果一个线程获取了该内存以后,其他线程就无法获取该对象的内存锁。这个时候其他线程就无法获得该对象锁
- this关键字代表类的一个对象,所以其内存锁是针对相同对象的互斥操作,而static成员属于类专有,其内存空间为该类所有成员共享,这就导致synchronized()对static成员或成员方法加锁,相当于对类加锁,也就是在该类的所有成员间实现互斥,在同一时间只有一个线程可访问该类的实例
3.2 Object的wait()、notify()、notifyAll()方法
- object.wait()、object.notify()、object.notifyAll()方法必须和synchronized(object){}一起使用
- wait()方法和notify()方法一定要在synchronized(object){}代码块或者synchronized修饰的方法中
- 当我们的线程使用synchronized获取了object对象的锁以后,我们可以使用object.wait()释放object的内存锁,这个时候当前线程会进入等待队列,等待别的线程执行完以后调用object.notify()或者object.notifyAll()唤醒等待队列中的线程
- 某一个线程的notify调用以后,该线程会在synchronized(object){}代码块中执行完毕以后,JVM会从等待队列中随机选择一个线程,将其唤醒,然后进入lock block pool,然后赋予其对象锁,然后进入Runnable状态;等待JVM调用
- notify()和notifyAll()之间的区别时,notify会让JVM随机选取一个等待的线程获取对象锁,然后notifyAll()会唤醒所有的线程,然后去竞争对象锁。竞争失败的线程重新回到等待队列。
3.3 消费者生产者模式
生产者消费者模式是指在有限容量(有限容量是其中的一种),生产者生产物品,如果仓库没有剩余的容量来存储Producer生产的Object,那么这个时候就需要进行等待,如果消费者消费了一定的物品,那么再唤醒Producer生产Object。消费者消费物品,如果仓库的物品不足以消费,那么此时Consumer就需要等待,直到producer生产了物品,然后执行唤醒消费者消费物品。
Producer和Consumer之间是互斥的关系。那么使用synchronized、notifyAll、wait实现消费者生产者模式;
首先实现一个仓库,用于Producer生产存储物品、Consumer 消费物品,用一个链表保存生产的Object对象数量
class Storage{ private final int MAX_SIZE = 100;// private LinkedList<Object> list = new LinkedList<Object>();// public void produce(int num){ synchronized (list){ //当仓库的剩余容量不足时 while(list.size() + num >MAX_SIZE){ System.out.println("要生产的数量 == "+ num +" 仓库的数量 == "+list.size()+" 不能生产,等待Consumer消费 "); try{ list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for(int i = 1;i <= num;i++){ list.add(new Object()); } System.out.println("已经生产产品数 == "+ num+" 当前的仓储量为 == "+list.size()); list.notifyAll(); } } public void consume(int num){ synchronized (list){ //当仓库的物品剩余容量不足时 while(list.size() - num <0){ System.out.println("要消费的数量 == "+ num +" 仓库的数量 == "+list.size()+" 不能消费,等待生产者生产"); try{ list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for(int i = 1;i <= num;i++){ list.remove(); } System.out.println("已经消费的产品数 == "+ num+" 当前的仓储量为 == "+list.size()); list.notifyAll(); } } public LinkedList<Object> getList() { return list; } public void setList(LinkedList<Object> list) { this.list = list; } }
实现Producer调用Storage的produce方法生产物品
class Producer extends Thread { private int num; private Storage storage; public Producer(int num,Storage storage){ this.storage = storage; this.num = num; } public void setNum(int num){ this.num = num; } @Override public void run(){ this.storage.produce(num); } }
实现Consumer调用Storage的consumer方法消费物品
class Consumer extends Thread{ private int num; private Storage storage; public Consumer(int num,Storage storage){ this.num = num; this.storage = storage; } @Override public void run(){ this.storage.consume(this.num); } }
四、参考文章
https://www.jb51.net/article/105607.htm
https://blog.youkuaiyun.com/chy555chy/article/details/52279544
https://blog.youkuaiyun.com/sinat_36042530/article/details/52565296#commentBox
https://www.cnblogs.com/wxd0108/p/5479442.html