第一章 多线程
1.1并发于并行
- 并发:指两个或多个事件在同一个时间段内发生。
- 并行:指两个或多个事件在同一时刻发生(同时发生)。
在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的效率。
注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。
1.2线程与进程
-
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
-
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
进程概念:
线程概念:
线程调度:
-
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
-
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
-
设置线程的优先级
-
抢占式调度详解
大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。
实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
-
主线程
/*
主线程:执行主(main)方法的线程
单线程程序:java程序中只有一个线程
执行从main方法开始,从上到下一次执行
JVM执行main方法,main方法会进入栈内存中
JVM会找到操作系统开辟一条main方法通向cup的执行路径
cpu就可以通过这个路径来执行main方法
而这个路径称为main(主)路径
*/
public class Demo1MainThread {
public static void main(String[] args) {
//单线程程序会从主线程中从上到下一次执行
Person person = new Person("张三");
person.run();
//定义循环,执行20次
for(int i=0; i<20000; i++){
System.out.println("main"+"-->"+i);
}
}
}
1.3创建线程类
/*
创建多线程程序的第一种方式:创建Thread类的子类
java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须要继承Thread类
实现步骤:
1.创建一个Thread类的子类
2.在Thread子类中重写Thread类中的run方法,设置线程任务(开启线程做什么)
3.创建Thread线程类子类对象
4.用对象调用Thread类中的start方法,开启新的线程,执行run方法
void start()使该线程开始执行:java虚拟机调用该线程的run方法
结果是两个线程并发的运行:当前线程(main线程)和新创建的线程,并且执行其run方法
注意:
多次启动一个线程是非法的,特别是当一个线程已经结束执行后,不能再重新启动
java程序属于抢占式调度,哪个线程的优先级高,哪个线程优先执行,同级线程,随机选择一个执行
*/
/*
多线程随机打印的原理:
1.JVM执行main方法时,让OS开辟一条main方法通向cpu的路径
1.1当main中创建一个new MyThread()对象,则OS开辟了一条通向cpu的新路径
1.1.1然后调用start方法,接着调用run方法
对于cpu而言,就有两条路径去执行程序,因为是同优先级,所以会随机执行
*/
public class Demo1Thread {
//3。创建一个Thread子类的对象
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("main-->"+i);
}
}
}
自定义线程子类:
//1.创建一个Thread类的子类
public class MyThread extends Thread{
//重写run方法
@Override
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("run-->"+i);
}
}
}
获取线程名字
/*
获取线程的名称:
1.使用Thread类中的方法getName()
String getName()返回该线程的名称
2.可以调用Thread中方法,获取当前正在执行的线程,使用线程中的getName方法获取线程的名称
static Thread currentThread() 返回当前正在运行的线程对象的引用
*/
public class MyThread2 extends Thread{
@Override
public void run() {
// String name = getName();
// System.out.println(name);
System.out.println(currentThread().getName());
}
}
1.4创建线程的方式二Runnable
/*
创建多线程程序的第二种方式:实现Runnable接口
java.lang.Runnable
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
java.lang.Thread类的构造方法
Thread(Runnable target) 分配新的 Thread 对象。
Thread(Runnable target, String name) 分配新的 Thread 对象。
实现步骤:
1.创建一个Runnable接口的实现类
2.在实现类中重写Runnable接口的run方法,设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法
实现Runnable接口创建多线程程序的好处:
1.避免了单继承的局限性
一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:用来开启新线程
*/
实现步骤:
1.创建一个Runnable接口的实现类
2.在实现类中重写Runnable接口的run方法,设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法
public class Demo01Runnable {
public static void main(String[] args) {
RunnableImp run = new RunnableImp();
Thread thread = new Thread(run);
thread.start();
//传入一个Runable实现类的对象到Thread构造参数中,使其真正成为一个线程
Thread t=new Thread(new RunnableImp2());
t.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"--》"+i);
}
}
}
Runnable实现类:
实现Runnable接口
public class RunnableImp implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"--》"+i);
}
}
}
1.5Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免java中的单继承的局限性。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
- 线程池只能放入实现Runnable或Callable类线程,不能直接放入继承Thread的类。
扩充:在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用
java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程
1.6匿名内部类的方式实现线程创建
匿名内部类方式实现线程的创建:
匿名:没有名字
内部类:写在其他类内部的类
匿名内部类的作用:简化代码
1.把子类继承父类,重写父类方法,创建子类对象一步完成
2.把子类实现接口,重写接口方法,创建子类对象合成一步
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
public class Demo01InnerClassThread {
public static void main(String[] args) {
//1.把子类继承父类,重写父类方法,创建子类对象一步完成
new Thread(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"黑马");
}
}
}.start();
// 2.把子类实现接口,重写接口方法,创建子类对象合成一步
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"程序员");
}
}
}){}.start();
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + "传智播客");
}
}
};
new Thread(runnable).start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"main");
}
}
}
第二章线程安全
2.1线程安全
/*
实现买票案例
*/
public class RunnableImp implements Runnable {
private int ticket=100;
@Override
public void run() {
//窗口永远开着
while (true){
//为了提高线程安全问题出现的概率,让抢夺大cpu资源的线程睡眠,丧失掉cpu的资源
//有票 可以买
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
开启多个线程同时卖票(模拟多个窗口同时买票)
public class Demo01Ticket {
public static void main(String[] args) {
RunnableImp r = new RunnableImp();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
出现线程安全问题:会出现卖出同样的票,甚至不存在的票
2.2线程同步
2.2.1解决线程安全的第一种方式:synchronized
卖票案例出现了线程安全问题
卖出了不存在的票和重复的票
解决线程安全问题的一种方案:使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
public class RunnableImp implements Runnable {
private int ticket=100;
Object obj=new Object();
@Override
public void run() {
while (true){
synchronized (obj){
if(ticket>0){
//为了提高线程安全问题出现的概率,让抢夺大cpu资源的线程睡眠,丧失掉cpu的资源
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
测试代码
public class Demo01Ticket {
public static void main(String[] args) {
RunnableImp r = new RunnableImp();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
2.2.3解决线程安全的第二种方式:同步方法
卖票案例出现了线程安全问题
卖出了不存在的票和重复的票
解决线程安全问题的第二种方案:使用同步方法
使用步骤:
1。把访问公共资源的代码抽取出来,放到一个方法中
2.在这个方法中加入关键字synchronized修饰符
格式:
修饰符 synchronized 返回值类型 方法名(){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
1.同步方法中也有锁对象,即实现类对象 new RunnableImp
也就是this
2.静态方法中无法用this对象,因为Static方法要先于对象创建
需要用类的class对象
public class RunnableImp implements Runnable {
private static int ticket=100;
Object obj=new Object();
@Override
public void run() {
//判断同步方法的锁对象是谁
System.out.println("this:"+this);
while (true){
payTicketStatic();
}
}
/*
同步方法中也有锁对象
这个对象就是是实现类对象 new RunnableImp();
也就是this
*/
public /*synchronized */void payTicket(){
synchronized (this){
if(ticket>0){
//为了提高线程安全问题出现的概率,让抢夺大cpu资源的线程睡眠,丧失掉cpu的资源
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
/*
静态方法
*/
public static synchronized void payTicketStatic(){
/*
因为此方法为静态方法,所以不能用this对象,因为Static要先于创建对象
需要用类的class类对象
*/
synchronized (RunnableImp.class){
if(ticket>0){
//为了提高线程安全问题出现的概率,让抢夺大cpu资源的线程睡眠,丧失掉cpu的资源
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
2.5解决线程安全的第三种方式:Lock锁
卖票案例出现了线程安全问题
卖出了不存在的票和重复的票
解决线程安全问题的三种方案:使用Lock锁
java.util.concurrent.locks.Lock接口
Lock实现提供了比使用 synchronized方法和语句可获得的更广泛的锁定操作
Lock接口中的方法:
void lock():获取锁
void unlock():释放锁
实现类:java.uitl.concurrent.locks.ReentrantLock implements Lock 接口
使用步骤:
1.在成员位置创建一个ReentrantLock对象
2.在可能出现线程安全问题的代码前调用lock()方法获取锁
3.在可能出现线程安全问题的代码后调用unlock()方法释放锁
public class RunnableImp implements Runnable {
private int ticket=100;
ReentrantLock l=new ReentrantLock();
@Override
public void run() {
while (true){
//为了提高线程安全问题出现的概率,让抢夺大cpu资源的线程睡眠,丧失掉cpu的资源
l.lock();
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
l.unlock();
}
}
}
测试代码
public class Demo01Ticket {
public static void main(String[] args) {
RunnableImp r = new RunnableImp();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
第三章 线程状态概述
3.1线程状态概述
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,
有几种状态呢?在API中 java.lang.Thread.State 这个枚举中给出了六种线程状态:
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
TimedWaiting(计时 | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态 将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ckW97vC0-1604820457038)(D:\学习资料\java自学\2020Java学习路线\2020Java学习路线\02_Java进阶\day06_线程、同步\resource\线程的状态图.bmp)]
3.2Timed Waiting(计时等待)
进入到TimeWaiting(计时等待)有两种方式
1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
唤醒的方法:
void notify() 唤醒在此对象监视器上等待的单个线程。
void notifyAll() 唤醒在此对象监视器上等待的所有线程。
public class Demo02Wait {
public static void main(String[] args) {
Object obj=new Object();
new Thread(){
@Override
public void run() {
//消费则一直购买
while (true){
//使用同步代码块,保证只能有一个线程在执行
synchronized (obj){
System.out.println("告知老板购买的包子数和种类,然开排队等待");
try {
//调用同一对象的wait方法,使此线程进入到WAITING状态(无限等待)
obj.wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//wait()方法后的代码
System.out.println("领取包子,开始吃");
System.out.println("------------------");
}
}
}
}.start();
}
}
等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
注意:
1.顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
2.同步使用的对象必须唯一
3.只有锁对象才能调用wait()和Notify方法
Object类中的方法:
void wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void notify()
唤醒在此对象监视器上等待的单个线程。
会继续执行wait方法之后的代码
public class Demo01WaitAndNotify {
public static void main(String[] args) {
//1.创建锁对象,保证唯一
Object obj=new Object();
//2.创建一个客户线程(消费者)
new Thread(){
@Override
public void run() {
//消费则一直购买
while (true){
//使用同步代码块,保证只能有一个线程在执行
synchronized (obj){
System.out.println("告知老板购买的包子数和种类,然开排队等待");
try {
//调用同一对象的wait方法,使此线程进入到WAITING状态(无限等待)
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//wait()方法后的代码
System.out.println("领取包子,开始吃");
System.out.println("------------------");
}
}
}
}.start();
//创建一个生产者线程做包子
new Thread(){
//重写父类中的run方法
@Override
public void run() {
//老板要一直做包子
while (true){
//花了5秒钟做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//使用同步代码保证只有一个线程在执行
synchronized(obj){
System.out.println("5秒钟后,老板将包子做好了,开始叫号");
//唤醒同一对象中进入无线等待的线程(消费者线程),让其领包子
obj.notify();
}
}
}
}.start();
}
}
唤醒的方法:
void notify() 唤醒在此对象监视器上等待的单个线程。
void notifyAll() 唤醒在此对象监视器上等待的所有线程。
public class Demo02WaitAndNotifyAll {
public static void main(String[] args) {
//1.创建锁对象,保证唯一
Object obj=new Object();
//2.创建一个客户线程(消费者)
new Thread(){
@Override
public void run() {
//消费则一直购买
while (true){
//使用同步代码块,保证只能有一个线程在执行
synchronized (obj){
System.out.println("客户1告知老板购买的包子数和种类,然开排队等待");
try {
//调用同一对象的wait方法,使此线程进入到WAITING状态(无限等待)
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//wait()方法后的代码
System.out.println("领取包子,开始吃");
System.out.println("------------------");
}
}
}
}.start();
//2.创建一个客户线程(消费者)
new Thread(){
@Override
public void run() {
//消费则一直购买
while (true){
//使用同步代码块,保证只能有一个线程在执行
synchronized (obj){
System.out.println("客户2告知老板购买的包子数和种类,然开排队等待");
try {
//调用同一对象的wait方法,使此线程进入到WAITING状态(无限等待)
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//wait()方法后的代码
System.out.println("领取包子,开始吃");
System.out.println("------------------");
}
}
}
}.start();
//创建一个生产者线程做包子
new Thread(){
//重写父类中的run方法
@Override
public void run() {
//老板要一直做包子
while (true){
//花了5秒钟做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//使用同步代码保证只有一个线程在执行
synchronized(obj){
System.out.println("5秒钟后,老板将包子做好了,开始叫号");
//唤醒同一对象中进入无线等待的线程(消费者线程),让其领包子
//obj.notify();
obj.notifyAll();
}
}
}
}.start();
}
}
第四章等待唤醒机制
1.1线程间的通信
生产包子的例子
包子铺线程生产包子,吃货线程消费包子。
当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),
并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。
接下来,吃货线程能否进一步执行则取决于锁的获取情况。
如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),
并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。
吃货:
public class ChiHuo extends Thread{
private BaoZi bz;
public ChiHuo(String name,BaoZi bz){
super(name);
this.bz=bz;
}
@Override
public void run() {
while (true){
synchronized (bz){
//判断是否有包子
if(bz.flag==false){
//没有包子,吃货线程则陷入等待
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//有包子
System.out.println("吃货正在吃"+bz.pi+bz.xian+"包子");
bz.flag=false;
//唤醒包子对象监听器下的包子铺线程
bz.notify();
}
}
}
}
}
public class BaoZiPu extends Thread{
private BaoZi bz;
public BaoZiPu(String name,BaoZi bz){
super(name);
this.bz=bz;
}
@Override
public void run() {
int count = 0;
while (true){
synchronized (bz){
//判断是否有包子
if(bz.flag==true){
//如果有包子,则包子铺陷入waiting状态
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//没有包子
System.out.println("包子铺开始做包子");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count%2 == 0){
// 冰皮 五仁
bz.pi = "冰皮";
bz.xian = "五仁";
}else{
// 薄皮 牛肉大葱
bz.pi = "薄皮";
bz.xian = "牛肉大葱";
}
count++;
bz.flag=true;
//唤醒包子对象监听器下的吃货线程
bz.notify();
}
}
}
}
}
包子对象
包子铺线程生产包子,吃货线程消费包子。
当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),
并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。
接下来,吃货线程能否进一步执行则取决于锁的获取情况。
如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),
并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。
public class BaoZi {
String pi;
String xian;
boolean flag = false;//包子资源的状态
public BaoZi() {
}
public BaoZi(String pi, String xian, boolean flag) {
this.pi = pi;
this.xian = xian;
this.flag = flag;
}
}
测试类
public class Demo {
public static void main(String[] args) {
BaoZi baoZi = new BaoZi("冰皮", "五仁", true);
new ChiHuo("张三",baoZi).start();
new BaoZiPu("李四包子铺",baoZi).start();
}
}
第五章线程池
概述
**线程池:**其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:
例子
线程池:JDK1.5之后提供的
java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
参数:
int nThreads:创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
java.util.concurrent.ExecutorService:线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个 Runnable 任务用于执行
关闭/销毁线程池的方法
void shutdown()
线程池的使用步骤:
1.使用线程池工厂类Executors里面提供的静态方法newFixedThreadPoo生产一个指定线程数量的线程池
放回一个线程接口的实现类(面向接口编程)
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
4.调用ExecutoeService中的shutdown方法销毁线程池(不建议使用)
public class Demo01ThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
//调用一个线程池中的某个线程执行实现类中的run方法,线程可以重复利用
executorService.submit(new RunnableImp());
executorService.submit(new RunnableImp());
executorService.submit(new RunnableImp());
/*
//销毁线程池则会无法使用线程
executorService.shutdown();
executorService.submit(new RunnableImp()); //此时无法在调用线程 java.util.concurrent.RejectedExecutionException
*/
}
}
package 线程.ThreadPool;
/*
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
*/
public class RunnableImp implements Runnable{
@Override
public void run() {
{
System.out.println(Thread.currentThread().getName()+"创建了一个新的线程执行");
}
}
}
第六章ThreadLocal
概述:threadLocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定的线程可以得到存储数据
作用:用于解决线程安全
ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。
public class Demo01ThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
//调用一个线程池中的某个线程执行实现类中的run方法,线程可以重复利用
executorService.submit(new RunnableImp());
executorService.submit(new RunnableImp());
executorService.submit(new RunnableImp());
/*
//销毁线程池则会无法使用线程
executorService.shutdown();
executorService.submit(new RunnableImp()); //此时无法在调用线程 java.util.concurrent.RejectedExecutionException
*/
}
}
package 线程.ThreadPool;
/*
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
*/
public class RunnableImp implements Runnable{
@Override
public void run() {
{
System.out.println(Thread.currentThread().getName()+"创建了一个新的线程执行");
}
}
}
第六章ThreadLocal
概述:threadLocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定的线程可以得到存储数据
作用:用于解决线程安全
ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。
ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。