普通线程(extends Thread)的几个重点
- synchronized () { }
- sysynchronized
- wait()
- notify()
- notifyAll()
- sleep()
- volatile
接下来将从生产者和消费者模型中进行学习
首先,明白什么是生产者和消费者模型,简单介绍一则实例,通过实例的要求明白(请原谅我自己给自己编的题目太Low):
- 【生产者】负责生产商品,商品不断的存入仓库
- 【消费者】负责消费商品,商品不断被取出并消费
- 【仓库】仓库最大容量为5
- 【要求1】生产者和消费者是并行的
- 【要求2】只要库存不为零,消费者就可以进行消费
- 【要求3】只要库存未满,生产者就可以生产
- 【要求4】一旦消费者消费时,发现库存为0,立马进行报告生产者生产
(自己编的题目,原谅一下可能不是很好理解)
然后,分析题目进行编码:
根据题目显然,我们需要仓库类,生产者类,消费者类,生产者线程类,消费者线程类,测试类
仓库类:
根据需求显然仓库就是一个队列容器,而且是先进先出的特性,使用ArrayDeque简直好到不能再好
import java.util.ArrayDeque;
public class Repertory {
//公共商品队列容器
volatile public static ArrayDeque<String> goods = new ArrayDeque<String>();
}
生产者类:
public class Producer {
public void produce(String goodsName){
System.out.println("生产者生产了"+goodsName);
//像仓库中添加生产的商品
Repertory.goods.add(goodsName);
//打印仓库的库存信息,展示现在未被消费的商品
System.out.println("\t----"+Repertory.goods);
}
}
消费者类:
public class Consumer {
public void consume(){
//判断队列容器的首元素是否为null
if(Repertory.goods.getFirst() != null){
//poll()操作,队列首元素出队,进行消费
System.out.println("消费者消费了:"+Repertory.goods.poll());
}
}
}
生产者线程类:
public class ProducerThread extends Thread{
private Producer producer;
public ProducerThread(Producer producer) {
this.producer = producer;
}
@Override
public void run() {
try {
int i = 0;
while (true) {
synchronized("lock"){
//开始生产
this.producer.produce("商品"+i++);
//生产周期为1秒
Thread.sleep(1000);
//如果库存容量达到最大容量5,执行wait(),立马释放锁,消费者拿到锁,进行消费
if(Repertory.goods.size()==5){
"lock".wait();
}
}
}
} catch (Exception e) {
// TODO: handle exception
}
}
}
消费者线程类:
public class ConsumerThread extends Thread{
private Consumer consumer;
public ConsumerThread(Consumer consumer) {
this.consumer = consumer;
}
@Override
public void run() {
try {
while(true){
synchronized ("lock") {
//如果库存容量大于0,则立马进行消费
if(Repertory.goods.size()>0){
this.consumer.consume();
//消费周期为0.5秒
Thread.sleep(500);
}else {
//如果库存容量小于等于0,则立马进行唤醒生产者线程,释放锁,生产线程拿到锁,进行生产
System.out.println("没有商品呢,还不补货...");
"lock".notify();
Thread.sleep(1000);
}
}
}
} catch (Exception e) {
// TODO: handle exception
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
Producer producer = new Producer();
new ProducerThread(producer).start();
Consumer consumer = new Consumer();
new ConsumerThread(consumer).start();
}
}
测试结果:
生产者生产了商品0
----[商品0]
生产者生产了商品1
----[商品0, 商品1]
生产者生产了商品2
----[商品0, 商品1, 商品2]
生产者生产了商品3
----[商品0, 商品1, 商品2, 商品3]
生产者生产了商品4
----[商品0, 商品1, 商品2, 商品3, 商品4]
消费者消费了:商品0
消费者消费了:商品1
消费者消费了:商品2
消费者消费了:商品3
消费者消费了:商品4
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
生产者生产了商品5
----[商品5]
生产者生产了商品6
----[商品5, 商品6]
消费者消费了:商品5
生产者生产了商品7
----[商品6, 商品7]
生产者生产了商品8
----[商品6, 商品7, 商品8]
生产者生产了商品9
----[商品6, 商品7, 商品8, 商品9]
生产者生产了商品10
----[商品6, 商品7, 商品8, 商品9, 商品10]
消费者消费了:商品6
消费者消费了:商品7
消费者消费了:商品8
消费者消费了:商品9
消费者消费了:商品10
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
生产者生产了商品11
----[商品11]
消费者消费了:商品11
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
生产者生产了商品12
----[商品12]
消费者消费了:商品12
没有商品呢,还不补货...
没有商品呢,还不补货...
生产者生产了商品13
----[商品13]
消费者消费了:商品13
生产者生产了商品14
----[商品14]
消费者消费了:商品14
生产者生产了商品15
----[商品15]
消费者消费了:商品15
生产者生产了商品16
----[商品16]
生产者生产了商品17
----[商品16, 商品17]
消费者消费了:商品16
程序一定要仔细分析,重要和不重要的地方都写了注释,应该还是很好理解的,一定要理解完了,然后我们开启新的解释。
线程的概念
线程是一个程序内部的顺序控制流,每个进程都有独立的代码和数据空间(进程上下文),进程切换的开销大。线程:轻量的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数 器(PC),线程切换的开销小。多线程就是在同一应用程序中有多个顺序流同时执行。
线程的生命周期
线程的生命周期:一个线程从它创建到启动,然后运行,直到最后执行完的整个过程。
新建状态:即创建一个新的线程对象,注意新创建的线程对象如果没有调用start()方法将永远得不到运行。
就绪状态:当新的线程对象调用start()方法时,就进入了就绪状态,进入就绪状态的线程不一定立即就开始运行。
运行状态:进入运行状态的线程,会由CPU处理其线程体中的代码。
阻塞状态:运行状态的线程有可能出现意外情况而中断运行,比如进行IO操作,内存的读写,等待键盘输入数据(注意不是出错,出错将提前终止线程)而进入阻塞状态。当阻塞条件解除后,线程会恢复运行。但其不是立即进入运行状态,而是进入就绪状态。
终止状态:当线程中run()方法语句执行完后进入终止状态。
synchronized () { }
在Java中,引入对象互斥锁的概念,来保证共享数据操作的完整性。
每个对象都对应于一个可称为”互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
用于方法声明中,标明整个方法为同步方法这时候他锁定的对象是this。当执行该方法语句时,当前线程有可能被中断,但对象还是被当前线程锁定着,其它线程可以进入运行状态,但不能访问这个对象。如果要用到该对象,其它线程只能等待。这样的方法也叫同步方法。
sysynchronized
用于修饰语句块,标明整个语句块为同步块。锁定的对象范围小,可以提高程序的运行效率,避免死锁。它还可以锁定不同的对象,不一定只锁定当前对象this。
wait()
注:wait会释放锁
当前线程必须拥有此对象的monitor(即锁),才能调用某个对象的wait()方法能让当前线程阻塞,(这种阻塞是通过提前释放synchronized锁,重新去请求锁导致的阻塞,这种请求必须有其他线程通过notify()或者notifyAll()唤醒重新竞争获得锁)
notify()
注:notify仅仅只是通知,不释放锁
调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;
notifyAll()
notify()或者notifyAll()方法并不是真正释放锁,必须等到synchronized方法或者语法块执行完才真正释放锁
sleep()
线程休眠:暂停执行当前运行中的线程,使之进入阻塞状态,待经过指定的”延迟时间’后再醒来并转入到就绪状态。
volatile
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
volatile关键字保证了操作的可见性,但是volatile能保证对变量的操作是原子性吗?
答案是:volatile也无法保证对变量的任何操作都是原子性的。
关于volatile和sysynchronized,在后期学到并发编程中会学到,到时候必须要讲到三大要素:原子性、可见性和有序性,等学到了,再进行补上。