1. 概念
- 程序(program) :是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
- 进程(process) :是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程,有它自身的产生、存在和消亡的过程,即生命周期。
如:运行中的微信、QQ等
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域 - 线程(thread) :进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
2. 线程的创建
Java语言的JVM允许程序运行多个线程,它通过 java.lang.Thread 类来体现。
创建线程的两种方式:
- 方式一:继承Thread类
1) 定义子类继承Thread类。
2) 子类中重写Thread类中的run方法。
3) 创建Thread子类对象,即创建了线程对象。
4) 调用线程对象start方法:启动线程,调用run方法 - 方式二:实现Runnable接口
1) 定义子类,实现Runnable接口。
2) 子类中重写Runnable接口中的run方法。
3) 通过Thread类含参构造器创建线程对象。
4) 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
5) 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
3. 生命周期
生命周期的五种状态:
- 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建 状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已 具备了运行的条件,只是没分配到CPU资源
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线 程的操作和功能
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中 止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
线程状态转换图
4. 线程同步
实现线程同步的两种方法:
- 同步代码块
synchronized (对象){
// 需要被同步的代码;
}
例1:
设计一个多线程的程序如下:设计一个火车售票模拟程序。假如火车站要有100张火车票要卖出,现在有5个售票点同时售票,用5个线程模拟这5个售票点的售票情况。
public class TickSale implements Runnable{
private static int num = 100; //表示车票只有100张
@Override
public void run() {
while (true){
synchronized (this){
if (num > 0){
System.out.println(Thread.currentThread().getName() +"销售的座位号为:"+ num--);
}else {
break;
}
}
}
}
public static void main(String[] args) {
TickSale target = new TickSale();
for (int i = 1;i <= 5;i++){
new Thread(target,"窗口"+i).start();
}
}
}
- 同步方法
public synchronized void show (String name){
…
}
public class TickSale implements Runnable{
private static int num = 100; //表示车票只有100张
@Override
public void run() {
while (true){
print();
}
}
public synchronized void print(){
if (num > 0){
System.out.println(Thread.currentThread().getName() +"销售的座位号为:"+ num--);
}
}
public static void main(String[] args) {
TickSale target = new TickSale();
for (int i = 1;i <= 5;i++){
new Thread(target,"窗口"+i).start();
}
}
}
例2:
编写多线程程序,模拟多个人通过一个山洞的模拟。这个山洞每次只能通过一个人,每个人通过山洞的时间为5秒,有10个人同时准备过此山洞,显示每次通过山洞人的姓名和顺序。
public class Test2 implements Runnable{
@Override
public void run() {
synchronized (this){
System.out.println(Thread.currentThread().getName() +" 穿过山洞");
try {
Thread.sleep(5000); //每个人通过山洞的时间为5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Test2 test2 = new Test2();
List<Person> list = new ArrayList<Person>();
list.add(new Person("A"));
list.add(new Person("B"));
list.add(new Person("C"));
list.add(new Person("D"));
list.add(new Person("E"));
list.add(new Person("F"));
list.add(new Person("G"));
list.add(new Person("H"));
list.add(new Person("I"));
list.add(new Person("J"));
for (int i = 0;i < 10;i++){
Thread t = new Thread(test2,list.get(i).getName());
t.start();
}
}
}
class Person{
private String name; //姓名
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
结果如下:
5. 线程通信
wait() 与 notify() 和 notifyAll()
- wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用 notify() 或 notifyAll() 方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行。
- notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
- notifyAll():唤醒正在排队等待资源的所有线程结束等待.
注意:这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报 java.lang.IllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声明。
经典例题:生产者/消费者问题
- 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20)。
- 如果生产者试图生产更多的产品,店员会叫生产者停一下;
- 如果店中有空位放产品了再通 知生产者继续生产;
- 如果店中没有产品了,店员会告诉消费者等一下;
- 如果店中有产品了再通知消费者来取走产品。
public class Clerk { //店员
private int productNum = 0;
public synchronized void addProduct(){ //进货
if (productNum >= 20){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
productNum++;
System.out.println("生产者生产了第 "+ productNum +" 个产品");
notify();
}
}
public synchronized void getProduct(){ //卖货
if (productNum <= 0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println("消费者消费了第 "+ productNum +" 个产品");
productNum--;
notify();
}
}
}
public class Productor implements Runnable { //生产者
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("生产者开始生产商品......");
while (true){
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}
public class Customer implements Runnable { //消费者
private Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("消费者可以开始消费了......");
while (true){
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.getProduct();
}
}
}
public class ProCusTest { //测试类
public static void main(String[] args) {
Clerk clerk = new Clerk();
Thread proThread = new Thread(new Productor(clerk));
Thread cusThread = new Thread(new Customer(clerk));
proThread.start();
cusThread.start();
}
}