说到多线程,我们就不得不涉及到并发
和并行
的概念。
并发: 两个或多个事件在同一时间段发生。
并行: 两个或多个事件的同一时刻发生(同时进行)。
通过上面的图面可以看出,在单核CPU的情况下,并发是多个任务轮流互相争抢CPU资源来执行任务的,任务是交替执行的。
通过上面的图面可以看出,在多个CPU的情况下,并行是每个任务都会分配CPU资源去执行任务,它们不需要争抢CPU资源,多个任务同时执行,所以效率比并发要强。
在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单个CPU系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。
注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。 对这部分内容感兴趣的同学可以去看《操作系统》。
进程: 进程是指程序执行时的一个实例,分配系统资源(CPU时间、内存等)的基本单位。
线程: 线程是程序执行时的最小单位,它是进程的一个执行流,是系统调度和分派的基本单位。
进程和线程的关系: 一个进程至少包含有一个线程,甚至多个线程。所以也称线程是轻量级进程。
注意: 本文主要讲的是Java的多线程,进程了解下概念就行,能区分进程和线程的区别。
线程调度方式: 分时调度
和抢占式调度
。
分时调度:所有线程轮流使用CPU的使用权,并且平均分配每个线程占用的CPU时间片。
抢占式调度:优先让优先级高的线程使用CPU,如果线程优先级相同,就会随机选择一个线程,Java使用的正式抢占式调度。
进程就了解下即可,这里不多做介绍,感兴趣的同学可以去阅读看下《操作系统》进程的部分。
多线程: 程序在运行时产生了至少2个线程的程序。
线成的创建方式有2种
:
(1)继承Thread类
继承Thread类的实现步骤和具体代码如下:
// 1.创建一个Thread的子类
public class MyThread extends Thread {
// 2.在子类中重写Thread父类的run方法,即设置线程的任务(干什么)
@Override
public void run(){
for (int i = 0; i <10 ; i++) {
System.out.println("run->"+i);
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
// 3.创建Thread的子类对象
MyThread mt = new MyThread();
//4.Thread子类对象调用父类的start()方法,开启一个新的线程,执行run()方法的任务
mt.start();
for (int i = 0; i <10 ; i++) {
System.out.println("main->"+i);
}
// 每次运行结果都可能不一样
}
}
(2)实现Runnable接口
实现Runnable接口的实现步骤和具体代码如下:
// 1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{
// 2.重写run方法,设置线程的任务
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("run->"+i);
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
// 3.创建一个Runnable的实现类对象
Runnable r = new RunnableImpl();
// 4.创建Thread对象,构造方法中传入Runnable的实现类对象
Thread t = new Thread(r);
// 5.Thread对象调用start()方法,开启线程执行run()方法的任务
t.start();
for (int i = 0; i <10 ; i++) {
System.out.println("main->"+i);
}
}
}
实现Runnable接口比继承Thread类所具有的优势:
方法名称 | 作用 |
---|---|
public void start() | 使该线程开始执行;Java 虚拟机调用该线程的 run 方法 |
public void run() | 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。Thread 的子类应该重写该方法。 |
public final String getName() | 返回该线程的名称 |
public final void setName(String name) | 改变线程名称,使之与参数 name 相同 |
public final void setPriority(int priority) | 更改线程的优先级 |
public final void setDaemon(boolean on) | 将该线程标记为守护线程或用户线程 |
public final void join(long millisec) | 等待该线程终止的时间最长为 millis 毫秒 |
public void interrupt() | 中断线程 |
public final boolean isAlive() | 测试线程是否处于活动状态 |
public static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
public static void sleep(long millisec) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响 |
public static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
线程安全问题: 当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。例如,银行取钱,卖票问题。
线程安全产生的原因:
代码示例
/**
* 模仿卖票操作(总共100张票)
* 从第100张票开始卖
*/
public class RunnableImpl implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
// 为了提高线程安全问题出现的概率 在这里休眠一下
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "->正在卖第" + ticket + "张票");
ticket--;
}else{
return;
}
}
}
}
public class TicketTest {
public static void main(String[] args) {
Runnable r = new RunnableImpl();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
t1.start();
t2.start();
t3.start();
}
}
输出结果
卖票窗口3->正在卖第100张票
卖票窗口1->正在卖第100张票
卖票窗口2->正在卖第100张票
卖票窗口3->正在卖第97张票
卖票窗口1->正在卖第96张票
卖票窗口2->正在卖第96张票
卖票窗口2->正在卖第94张票
卖票窗口1->正在卖第93张票
卖票窗口3->正在卖第93张票
卖票窗口2->正在卖第91张票
卖票窗口1->正在卖第90张票
卖票窗口3->正在卖第89张票
卖票窗口2->正在卖第88张票
卖票窗口3->正在卖第87张票
卖票窗口1->正在卖第87张票
卖票窗口2->正在卖第85张票
卖票窗口1->正在卖第84张票
卖票窗口3->正在卖第84张票
卖票窗口2->正在卖第82张票
卖票窗口3->正在卖第81张票
卖票窗口1->正在卖第80张票
卖票窗口2->正在卖第79张票
卖票窗口3->正在卖第78张票
卖票窗口1->正在卖第78张票
卖票窗口2->正在卖第76张票
卖票窗口3->正在卖第75张票
卖票窗口1->正在卖第74张票
卖票窗口2->正在卖第73张票
卖票窗口1->正在卖第72张票
卖票窗口3->正在卖第72张票
卖票窗口2->正在卖第70张票
卖票窗口1->正在卖第69张票
卖票窗口3->正在卖第69张票
卖票窗口2->正在卖第67张票
卖票窗口1->正在卖第66张票
卖票窗口3->正在卖第66张票
卖票窗口2->正在卖第64张票
卖票窗口3->正在卖第63张票
卖票窗口1->正在卖第63张票
卖票窗口2->正在卖第61张票
卖票窗口1->正在卖第60张票
卖票窗口3->正在卖第60张票
卖票窗口2->正在卖第58张票
卖票窗口3->正在卖第57张票
卖票窗口1->正在卖第57张票
卖票窗口2->正在卖第55张票
卖票窗口1->正在卖第54张票
卖票窗口3->正在卖第54张票
卖票窗口2->正在卖第52张票
卖票窗口3->正在卖第51张票
卖票窗口1->正在卖第51张票
卖票窗口2->正在卖第49张票
卖票窗口1->正在卖第48张票
卖票窗口3->正在卖第48张票
卖票窗口2->正在卖第46张票
卖票窗口3->正在卖第45张票
卖票窗口1->正在卖第45张票
卖票窗口2->正在卖第43张票
卖票窗口1->正在卖第42张票
卖票窗口3->正在卖第42张票
卖票窗口2->正在卖第40张票
卖票窗口1->正在卖第39张票
卖票窗口3->正在卖第38张票
卖票窗口2->正在卖第37张票
卖票窗口3->正在卖第36张票
卖票窗口1->正在卖第35张票
卖票窗口2->正在卖第34张票
卖票窗口3->正在卖第33张票
卖票窗口1->正在卖第33张票
卖票窗口2->正在卖第31张票
卖票窗口1->正在卖第30张票
卖票窗口3->正在卖第30张票
卖票窗口2->正在卖第28张票
卖票窗口3->正在卖第27张票
卖票窗口1->正在卖第26张票
卖票窗口2->正在卖第25张票
卖票窗口1->正在卖第24张票
卖票窗口3->正在卖第23张票
卖票窗口2->正在卖第22张票
卖票窗口1->正在卖第21张票
卖票窗口3->正在卖第21张票
卖票窗口2->正在卖第19张票
卖票窗口3->正在卖第18张票
卖票窗口1->正在卖第17张票
卖票窗口3->正在卖第16张票
卖票窗口2->正在卖第15张票
卖票窗口1->正在卖第14张票
卖票窗口3->正在卖第13张票
卖票窗口2->正在卖第13张票
卖票窗口1->正在卖第11张票
卖票窗口2->正在卖第10张票
卖票窗口3->正在卖第10张票
卖票窗口1->正在卖第8张票
卖票窗口2->正在卖第7张票
卖票窗口3->正在卖第7张票
卖票窗口1->正在卖第5张票
卖票窗口2->正在卖第4张票
卖票窗口1->正在卖第3张票
卖票窗口3->正在卖第4张票
卖票窗口1->正在卖第1张票
卖票窗口2->正在卖第1张票
卖票窗口3->正在卖第-1张票
总结: 上述的卖票例子就能说明线程安全问题,3个人同时卖票同一张票,甚至卖出了不存在的第-1张票。当我们多个人进行卖票,就是多个线程对共享数据进行访问修改,如果只是查票剩余多少,不会出现线程安全问题,如果对剩余票量进行修改,就会出现线程安全问题,这是不允许的情况,下面有解决方案。
解决方案思路: 当有一个线程在操作多条代码的共享数据时,只让当前线程可以操作,其他线程不允许操作多条代码的共享数据。
格式:
synchronized(锁对象){
可能出现线程安全问题的代码(访问共享数据的代码)
}
注意:
- 1.同步代码块中的锁对象,可以是任意的对象。
- 2.但是必须保证多个线程使用的锁对象是同一个。
- 3.锁对象的作用:把同步代码块锁住,只让一个线程在同步代码块执行。
代码示例
public class RunnableImpl implements Runnable {
private int ticket = 100;
// 创建一个锁对象
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (ticket > 0) {
// 为了提高线程安全问题出现的概率 在这里休眠一下
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "->正在卖第" + ticket + "张票");
ticket--;
} else {
return;
}
}
}
}
}
public class SynchronizedDemo {
public static void main(String[] args) {
Runnable r = new RunnableImpl();
Thread t1 = new Thread(r,"卖票窗口1");
Thread t2 = new Thread(r,"卖票窗口2");
Thread t3 = new Thread(r,"卖票窗口3");
t1.start();
t2.start();
t3.start();
}
}
输出结果
卖票窗口1->正在卖第100张票
卖票窗口1->正在卖第99张票
卖票窗口1->正在卖第98张票
卖票窗口1->正在卖第97张票
卖票窗口1->正在卖第96张票
卖票窗口1->正在卖第95张票
卖票窗口1->正在卖第94张票
卖票窗口1->正在卖第93张票
卖票窗口1->正在卖第92张票
卖票窗口1->正在卖第91张票
卖票窗口1->正在卖第90张票
卖票窗口1->正在卖第89张票
卖票窗口1->正在卖第88张票
卖票窗口1->正在卖第87张票
卖票窗口1->正在卖第86张票
卖票窗口1->正在卖第85张票
卖票窗口1->正在卖第84张票
卖票窗口1->正在卖第83张票
卖票窗口1->正在卖第82张票
卖票窗口1->正在卖第81张票
卖票窗口1->正在卖第80张票
卖票窗口1->正在卖第79张票
卖票窗口1->正在卖第78张票
卖票窗口1->正在卖第77张票
卖票窗口1->正在卖第76张票
卖票窗口1->正在卖第75张票
卖票窗口1->正在卖第74张票
卖票窗口1->正在卖第73张票
卖票窗口1->正在卖第72张票
卖票窗口1->正在卖第71张票
卖票窗口1->正在卖第70张票
卖票窗口3->正在卖第69张票
卖票窗口3->正在卖第68张票
卖票窗口3->正在卖第67张票
卖票窗口3->正在卖第66张票
卖票窗口3->正在卖第65张票
卖票窗口3->正在卖第64张票
卖票窗口3->正在卖第63张票
卖票窗口3->正在卖第62张票
卖票窗口3->正在卖第61张票
卖票窗口3->正在卖第60张票
卖票窗口3->正在卖第59张票
卖票窗口3->正在卖第58张票
卖票窗口3->正在卖第57张票
卖票窗口3->正在卖第56张票
卖票窗口3->正在卖第55张票
卖票窗口3->正在卖第54张票
卖票窗口3->正在卖第53张票
卖票窗口3->正在卖第52张票
卖票窗口3->正在卖第51张票
卖票窗口3->正在卖第50张票
卖票窗口3->正在卖第49张票
卖票窗口3->正在卖第48张票
卖票窗口3->正在卖第47张票
卖票窗口3->正在卖第46张票
卖票窗口3->正在卖第45张票
卖票窗口3->正在卖第44张票
卖票窗口3->正在卖第43张票
卖票窗口3->正在卖第42张票
卖票窗口3->正在卖第41张票
卖票窗口3->正在卖第40张票
卖票窗口3->正在卖第39张票
卖票窗口3->正在卖第38张票
卖票窗口3->正在卖第37张票
卖票窗口3->正在卖第36张票
卖票窗口3->正在卖第35张票
卖票窗口3->正在卖第34张票
卖票窗口3->正在卖第33张票
卖票窗口3->正在卖第32张票
卖票窗口3->正在卖第31张票
卖票窗口3->正在卖第30张票
卖票窗口3->正在卖第29张票
卖票窗口3->正在卖第28张票
卖票窗口3->正在卖第27张票
卖票窗口3->正在卖第26张票
卖票窗口3->正在卖第25张票
卖票窗口3->正在卖第24张票
卖票窗口3->正在卖第23张票
卖票窗口3->正在卖第22张票
卖票窗口3->正在卖第21张票
卖票窗口3->正在卖第20张票
卖票窗口3->正在卖第19张票
卖票窗口3->正在卖第18张票
卖票窗口3->正在卖第17张票
卖票窗口3->正在卖第16张票
卖票窗口3->正在卖第15张票
卖票窗口3->正在卖第14张票
卖票窗口3->正在卖第13张票
卖票窗口3->正在卖第12张票
卖票窗口3->正在卖第11张票
卖票窗口3->正在卖第10张票
卖票窗口3->正在卖第9张票
卖票窗口3->正在卖第8张票
卖票窗口3->正在卖第7张票
卖票窗口3->正在卖第6张票
卖票窗口3->正在卖第5张票
卖票窗口3->正在卖第4张票
卖票窗口3->正在卖第3张票
卖票窗口3->正在卖第2张票
卖票窗口3->正在卖第1张票
格式:
访问修饰符 synchronized 返回值类型 方法名称(){
可能出现线程安全问题的代码(访问共享数据的代码)
}
注意:
- 同步方法会把内部的代码块锁住,只让一个线程执行
- 同步方法的锁对象是实现类对象(this)
代码示例
public class RunnableImpl implements Runnable {
private int ticket = 100;
@Override
public void run() {
System.out.println("run:"+this);
while (true) {
sendTicket();
}
}
public synchronized void sendTicket(){
if (ticket > 0) {
// 为了提高线程安全问题出现的概率 在这里休眠一下
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "->正在卖第" + ticket + "张票");
ticket--;
}
}
}
public class SynchronizedDemo {
public static void main(String[] args) {
Runnable r = new RunnableImpl();
System.out.println("RunnableImpl:"+r);
Thread t1 = new Thread(r,"卖票窗口1");
Thread t2 = new Thread(r,"卖票窗口2");
Thread t3 = new Thread(r,"卖票窗口3");
t1.start();
t2.start();
t3.start();
}
}
输出结果
RunnableImpl:com.zsd.stage1_7_2.demo7.RunnableImpl@65ab7765
run:com.zsd.stage1_7_2.demo7.RunnableImpl@65ab7765
run:com.zsd.stage1_7_2.demo7.RunnableImpl@65ab7765
run:com.zsd.stage1_7_2.demo7.RunnableImpl@65ab7765
卖票窗口1->正在卖第100张票
卖票窗口1->正在卖第99张票
卖票窗口1->正在卖第98张票
卖票窗口1->正在卖第97张票
卖票窗口1->正在卖第96张票
卖票窗口1->正在卖第95张票
卖票窗口1->正在卖第94张票
卖票窗口1->正在卖第93张票
卖票窗口1->正在卖第92张票
卖票窗口1->正在卖第91张票
卖票窗口1->正在卖第90张票
卖票窗口1->正在卖第89张票
卖票窗口1->正在卖第88张票
卖票窗口1->正在卖第87张票
卖票窗口1->正在卖第86张票
卖票窗口1->正在卖第85张票
卖票窗口1->正在卖第84张票
卖票窗口1->正在卖第83张票
卖票窗口1->正在卖第82张票
卖票窗口1->正在卖第81张票
卖票窗口1->正在卖第80张票
卖票窗口1->正在卖第79张票
卖票窗口1->正在卖第78张票
卖票窗口1->正在卖第77张票
卖票窗口1->正在卖第76张票
卖票窗口1->正在卖第75张票
卖票窗口1->正在卖第74张票
卖票窗口1->正在卖第73张票
卖票窗口1->正在卖第72张票
卖票窗口1->正在卖第71张票
卖票窗口1->正在卖第70张票
卖票窗口1->正在卖第69张票
卖票窗口1->正在卖第68张票
卖票窗口1->正在卖第67张票
卖票窗口1->正在卖第66张票
卖票窗口1->正在卖第65张票
卖票窗口1->正在卖第64张票
卖票窗口1->正在卖第63张票
卖票窗口1->正在卖第62张票
卖票窗口1->正在卖第61张票
卖票窗口1->正在卖第60张票
卖票窗口1->正在卖第59张票
卖票窗口1->正在卖第58张票
卖票窗口1->正在卖第57张票
卖票窗口1->正在卖第56张票
卖票窗口1->正在卖第55张票
卖票窗口1->正在卖第54张票
卖票窗口1->正在卖第53张票
卖票窗口1->正在卖第52张票
卖票窗口1->正在卖第51张票
卖票窗口1->正在卖第50张票
卖票窗口1->正在卖第49张票
卖票窗口1->正在卖第48张票
卖票窗口1->正在卖第47张票
卖票窗口1->正在卖第46张票
卖票窗口1->正在卖第45张票
卖票窗口1->正在卖第44张票
卖票窗口1->正在卖第43张票
卖票窗口1->正在卖第42张票
卖票窗口3->正在卖第41张票
卖票窗口3->正在卖第40张票
卖票窗口3->正在卖第39张票
卖票窗口3->正在卖第38张票
卖票窗口3->正在卖第37张票
卖票窗口3->正在卖第36张票
卖票窗口3->正在卖第35张票
卖票窗口3->正在卖第34张票
卖票窗口3->正在卖第33张票
卖票窗口3->正在卖第32张票
卖票窗口3->正在卖第31张票
卖票窗口3->正在卖第30张票
卖票窗口3->正在卖第29张票
卖票窗口3->正在卖第28张票
卖票窗口3->正在卖第27张票
卖票窗口3->正在卖第26张票
卖票窗口3->正在卖第25张票
卖票窗口3->正在卖第24张票
卖票窗口3->正在卖第23张票
卖票窗口3->正在卖第22张票
卖票窗口3->正在卖第21张票
卖票窗口3->正在卖第20张票
卖票窗口3->正在卖第19张票
卖票窗口3->正在卖第18张票
卖票窗口3->正在卖第17张票
卖票窗口3->正在卖第16张票
卖票窗口3->正在卖第15张票
卖票窗口3->正在卖第14张票
卖票窗口3->正在卖第13张票
卖票窗口3->正在卖第12张票
卖票窗口3->正在卖第11张票
卖票窗口3->正在卖第10张票
卖票窗口3->正在卖第9张票
卖票窗口3->正在卖第8张票
卖票窗口3->正在卖第7张票
卖票窗口3->正在卖第6张票
卖票窗口3->正在卖第5张票
卖票窗口3->正在卖第4张票
卖票窗口3->正在卖第3张票
卖票窗口3->正在卖第2张票
卖票窗口3->正在卖第1张票
Lock实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
使用Lock接口的实现类ReentrantLock。
使用步骤:
- 1.创建一个成员ReentrantLock类对象
- 2.在可能发生线程安全问题的代码前调用lock()方法加锁
- 3.在可能发生线程安全问题的代码后调用unlock()方法释放锁
public class RunnableImpl implements Runnable {
// 多线程共享的数据-票数
private int ticket = 100;
// 1.创建一个成员ReentrantLock类对象
Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
// 2.在可能发生线程安全问题的代码前调用lock()方法加锁
l.lock();
if (ticket > 0) {
// 为了提高线程安全问题出现的概率 在这里休眠一下
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "->正在卖第" + ticket + "张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 3.在可能发生线程安全问题的代码后调用unlock()方法释放锁
// jdk文档中推荐在finally中释放锁,无论是否发生异常,锁都要释放
l.unlock();
}
}
}
}
}
public class LockDemo {
public static void main(String[] args) {
Runnable r = new RunnableImpl();
Thread t1 = new Thread(r,"卖票窗口1");
Thread t2 = new Thread(r,"卖票窗口2");
Thread t3 = new Thread(r,"卖票窗口3");
t1.start();
t2.start();
t3.start();
}
}
输出结果
卖票窗口1->正在卖第100张票
卖票窗口1->正在卖第99张票
卖票窗口1->正在卖第98张票
卖票窗口1->正在卖第97张票
卖票窗口1->正在卖第96张票
卖票窗口1->正在卖第95张票
卖票窗口1->正在卖第94张票
卖票窗口1->正在卖第93张票
卖票窗口1->正在卖第92张票
卖票窗口1->正在卖第91张票
卖票窗口1->正在卖第90张票
卖票窗口1->正在卖第89张票
卖票窗口1->正在卖第88张票
卖票窗口1->正在卖第87张票
卖票窗口1->正在卖第86张票
卖票窗口1->正在卖第85张票
卖票窗口1->正在卖第84张票
卖票窗口1->正在卖第83张票
卖票窗口1->正在卖第82张票
卖票窗口1->正在卖第81张票
卖票窗口1->正在卖第80张票
卖票窗口1->正在卖第79张票
卖票窗口1->正在卖第78张票
卖票窗口1->正在卖第77张票
卖票窗口1->正在卖第76张票
卖票窗口1->正在卖第75张票
卖票窗口1->正在卖第74张票
卖票窗口1->正在卖第73张票
卖票窗口1->正在卖第72张票
卖票窗口1->正在卖第71张票
卖票窗口1->正在卖第70张票
卖票窗口1->正在卖第69张票
卖票窗口1->正在卖第68张票
卖票窗口1->正在卖第67张票
卖票窗口1->正在卖第66张票
卖票窗口1->正在卖第65张票
卖票窗口1->正在卖第64张票
卖票窗口2->正在卖第63张票
卖票窗口2->正在卖第62张票
卖票窗口2->正在卖第61张票
卖票窗口2->正在卖第60张票
卖票窗口2->正在卖第59张票
卖票窗口2->正在卖第58张票
卖票窗口2->正在卖第57张票
卖票窗口2->正在卖第56张票
卖票窗口2->正在卖第55张票
卖票窗口2->正在卖第54张票
卖票窗口2->正在卖第53张票
卖票窗口2->正在卖第52张票
卖票窗口2->正在卖第51张票
卖票窗口2->正在卖第50张票
卖票窗口2->正在卖第49张票
卖票窗口2->正在卖第48张票
卖票窗口2->正在卖第47张票
卖票窗口2->正在卖第46张票
卖票窗口2->正在卖第45张票
卖票窗口2->正在卖第44张票
卖票窗口2->正在卖第43张票
卖票窗口2->正在卖第42张票
卖票窗口2->正在卖第41张票
卖票窗口2->正在卖第40张票
卖票窗口2->正在卖第39张票
卖票窗口2->正在卖第38张票
卖票窗口2->正在卖第37张票
卖票窗口2->正在卖第36张票
卖票窗口2->正在卖第35张票
卖票窗口2->正在卖第34张票
卖票窗口2->正在卖第33张票
卖票窗口2->正在卖第32张票
卖票窗口2->正在卖第31张票
卖票窗口2->正在卖第30张票
卖票窗口2->正在卖第29张票
卖票窗口2->正在卖第28张票
卖票窗口2->正在卖第27张票
卖票窗口2->正在卖第26张票
卖票窗口2->正在卖第25张票
卖票窗口2->正在卖第24张票
卖票窗口2->正在卖第23张票
卖票窗口2->正在卖第22张票
卖票窗口2->正在卖第21张票
卖票窗口2->正在卖第20张票
卖票窗口2->正在卖第19张票
卖票窗口2->正在卖第18张票
卖票窗口2->正在卖第17张票
卖票窗口2->正在卖第16张票
卖票窗口2->正在卖第15张票
卖票窗口2->正在卖第14张票
卖票窗口2->正在卖第13张票
卖票窗口2->正在卖第12张票
卖票窗口2->正在卖第11张票
卖票窗口2->正在卖第10张票
卖票窗口2->正在卖第9张票
卖票窗口2->正在卖第8张票
卖票窗口2->正在卖第7张票
卖票窗口2->正在卖第6张票
卖票窗口2->正在卖第5张票
卖票窗口2->正在卖第4张票
卖票窗口2->正在卖第3张票
卖票窗口2->正在卖第2张票
卖票窗口2->正在卖第1张票
Java线程状态定义在Thread.State
这个枚举类型中,源码如下:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
线程状态 | 意义 |
---|---|
NEW(新建状态) | 线程刚被创建,但是并未启动。还没调用start方法。 |
RUNNABLE(可运行状态) | 可运行线程的线程状态。处于可运行状态的某一线程正在 Java 虚拟机中运行,但它可能正在等待操作系统中的其他资源,比如处理器。 |
BLOCKED(阻塞状态) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
WAITING((无限等待状态) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify()或者notifyAll()才能够唤醒。 |
TIMED_WAITING(定时等待状态) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep()、Object.wait()。 |
TERMINATED(终止状态) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
为什么要处理线程间通信?
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
如何保证线程间通信有效利用资源?
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
什么是等待唤醒机制?
这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制。
代码示例
/**
* 等待和唤醒案例:线程之间的通信
*
* 模仿买包子操作:
* 创建一个顾客线程:告知老板包子的口味和数量,调用wait(),放弃cpu执行,进入WAITING((无限等待状态)。
* 创建一个老板线程:花费5秒做包子,调用notify()唤醒顾客线程,包子已经做好。
*
* 注意:
* 顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
* 保证锁对象唯一
* 只有锁对象才能调用wait()和notify()
*
* Object中的类:
* public final void wait() throws InterruptedException 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
*
* public final void notify() 唤醒在此对象监视器上等待的单个线程。
*/
public class WaitAndNotifyDemo {
public static void main(String[] args) {
// 创建锁对象,保证唯一
Object obj = new Object();
// 创建一个客户线程
new Thread() {
@Override
public void run() {
// 为了保证等待和唤醒只有一个线程在执行,这里需要使用同步
synchronized (obj) {
System.out.println("顾客:告知老板包子的口味和数量,等待老板做包子...");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("顾客:开始吃新鲜出炉的包子");
}
}
}.start();
new Thread() {
@Override
public void run() {
try {
// 老板线程休眠5秒,模仿做包子操作
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 为了保证等待和唤醒只有一个线程在执行,这里需要使用同步
synchronized (obj){
System.out.println("老板:做好包子,通知顾客");
obj.notify();
}
}
}.start();
}
}
输出结果:
顾客:告知老板包子的口味和数量,等待老板做包子...
老板:做好包子,通知顾客
顾客:开始吃新鲜出炉的包子
public class WaitParmDemo {
public static void main(String[] args) {
// 创建锁对象,保证唯一
Object obj = new Object();
// 创建一个客户线程
new Thread() {
@Override
public void run() {
// 为了保证等待和唤醒只有一个线程在执行,这里需要使用同步
synchronized (obj) {
System.out.println("顾客:告知老板包子的口味和数量,等待老板做包子...");
try {
// 进入等待状态,如果没有其他线程唤醒他,等5秒结束,自动唤醒自己
obj.wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("顾客:等了5秒还没好,我走了!!!");
}
}
}.start();
}
}
输出结果:
顾客:告知老板包子的口味和数量,等待老板做包子...
顾客:等了5秒还没好,我走了!!!
public class WaitAndNotifyAllDemo {
public static void main(String[] args) {
// 创建锁对象,保证唯一
Object obj = new Object();
// 创建一个客户线程
new Thread() {
@Override
public void run() {
// 为了保证等待和唤醒只有一个线程在执行,这里需要使用同步
synchronized (obj) {
System.out.println("顾客1:告知老板包子的口味和数量,等待老板做包子...");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("顾客1:开始吃新鲜出炉的包子");
}
}
}.start();
// 创建一个客户线程
new Thread() {
@Override
public void run() {
// 为了保证等待和唤醒只有一个线程在执行,这里需要使用同步
synchronized (obj) {
System.out.println("顾客2:告知老板包子的口味和数量,等待老板做包子...");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("顾客2:开始吃新鲜出炉的包子");
}
}
}.start();
new Thread() {
@Override
public void run() {
try {
// 老板线程休眠5秒,模仿做包子操作
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 为了保证等待和唤醒只有一个线程在执行,这里需要使用同步
synchronized (obj){
System.out.println("老板:做好包子,通知所有顾客");
obj.notifyAll();
}
}
}.start();
}
}
输出结果:
顾客1:告知老板包子的口味和数量,等待老板做包子...
顾客2:告知老板包子的口味和数量,等待老板做包子...
老板:做好包子,通知所有顾客
顾客1:开始吃新鲜出炉的包子
顾客2:开始吃新鲜出炉的包子
// 资源类
public class BaoZi {
// 皮
String pi;
// 馅
String xian;
// 包子状态 有包子:true 没包子:false 初始值是false
Boolean flag = false;
}
// 生产者包子铺类
public class BaoZiPu extends Thread {
// 创建包子成员
private BaoZi bz;
// 使用带参构造方法 对包子成员变量赋值
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
// 设置线程任务:生成包子
@Override
public void run() {
// 定义一个变量
int count = 0;
// 让包子铺一直生产包子
while (true) {
// 必须使用同步技术保证两个线程只有一个在执行
synchronized (bz) {
// 对包子的状态进行判断
if (bz.flag == true) {
try {
// 包子铺调用wait方法进入等待状态
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 被唤醒之后执行,包子铺生成包子
// 增加一些趣味性:交替生产两种包子
if (count % 2 == 0) {
// 生产 薄皮三鲜馅包子
bz.pi = "薄皮";
bz.xian = "三鲜馅";
} else {
// 生产 冰皮牛肉大葱馅包子
bz.pi = "冰皮";
bz.xian = "牛肉大葱馅";
}
count++;
System.out.println("包子铺正在生产:" + bz.pi + bz.xian + "包子");
// 生产包子需要3秒钟
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 包子铺生产好包子
// 修改包子的状态为有:true
bz.flag = true;
// 唤醒顾客线程,让顾客线程可以吃包子了
bz.notify();
System.out.println("包子铺生产好了:" + bz.pi + bz.xian + "包子,顾客可以准备吃了");
}
}
}
}
// 消费者食客类
public class Eater extends Thread {
// 创建包子成员
private BaoZi bz;
// 使用带参构造方法 对包子成员变量赋值
public Eater(BaoZi bz){
this.bz = bz;
}
@Override
public void run() {
// 定义一个变量
int count = 0;
// 死循环,让食客一直吃包子
while (true) {
// 必须使用同步技术保证两个线程只有一个在执行
synchronized (bz) {
// 对包子的状态进行判断
if (bz.flag == false) {
try {
// 食客调用wait方法进入等待状态
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 被唤醒之后执行的代码,吃包子
System.out.println("食客正在吃" + bz.pi + bz.xian + "包子");
// 食客吃完包子
// 修改包子的状态为没有:false
bz.flag = false;
// 食客唤醒包子铺生产包子
bz.notify();
System.out.println("食客已经把" + bz.pi + bz.xian + "包子吃完了,唤醒包子铺生产包子");
System.out.println("=======================================================================");
}
}
}
}
// 测试类
public class MainTest {
public static void main(String[] args) {
// 实例化包子对象
BaoZi bz = new BaoZi();
// 创建包子铺线程并开启,生产包子
new BaoZiPu(bz).start();
// 创建食客类线程并开启,吃包子
new Eater(bz).start();
}
}
输出结果:
包子铺正在生产:薄皮三鲜馅包子
包子铺生产好了:薄皮三鲜馅包子,顾客可以准备吃了
食客正在吃薄皮三鲜馅包子
食客已经把薄皮三鲜馅包子吃完了,唤醒包子铺生产包子
=======================================================================
包子铺正在生产:冰皮牛肉大葱馅包子
包子铺生产好了:冰皮牛肉大葱馅包子,顾客可以准备吃了
食客正在吃冰皮牛肉大葱馅包子
食客已经把冰皮牛肉大葱馅包子吃完了,唤醒包子铺生产包子
=======================================================================
线程池: 其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建销毁线程对象的操作,节省了时间,也无需反复创建销毁线程而消耗过多资源。
合理利用线程池能够带来三个好处:
线程池:由JDK1.5之后提供的
java.util.concurrent.Executors
:线程池的工厂类,用来生成线程池
Executors
的静态方法:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
java.util.concurrent.ExecutorService
:线程池接口
ExecutorService
的常用方法:
submit Future> submit(Runnable task) 提交一个 Runnable 任务用于执行。
void shutdown() 关闭/销毁线程池。
使用线程池中线程对象的步骤:
- 1.使用线程池的工厂类Executors里提供的newFixedThreadPool()方法创建指定线程数量的线程池
- 2.创建Runnable接口的实现类,重写run()方法,设置线程任务。
- 3.调用ExecutorService中的submit()方法,提交任务(Runnable接口实现类对象),开启线程,执行run()方法。
- 4.调用ExecutorService中的shutdown()方法,关闭线程池(一般不推荐,违背了我们使用线程池复用的初衷)。
代码示例:
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行任务");
}
}
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
// pool-1-thread-2执行任务
// pool-1-thread-1执行任务
// pool-1-thread-1执行任务
// 关闭/销毁线程池(不推荐)
es.shutdown();
// 关闭线程池后,使用线程开启任务报错
// java.util.concurrent.RejectedExecutionException
//es.submit(new RunnableImpl());
}
}