一、进程的概念
进程:每一个正在运行的程序---打开任务管理器可以看到正在运行的程序(进程)
依靠CPU执行的
二、线程的概念
线程:进程中的执行单元
三、进程和线程的分析
进程分析图:
进程中的线程分析图:
1. CPU执行程序(进程)的分析:已经加载到内存中的进程必须要获得CPU分配的时间片段(获取了CPU的执行权),进程才能够被执行
2. 进程中的线程执行分析:首先是进程获取CPU分配的时间片段,那么把这个时间片段又要进行细分,
3. 每一个线程都有机会获得细分之后的时间片段,谁先获取了时间片段就先执行这个线程(还是归根于要获取CPU的执行权)
四、为什么打开了多个应用程序之后电脑会明显卡顿
1.CPU分配给应用程序的时间片的概率会变低
2.开启多个应用程序占用的内存空间会变多
五、为什么要使用多线程??
一个进程中多个线程(任务)同时执行,进程才能够更加良好的运行(提高效率)
所有线程都可以共享进程中的内存空间
六、主方法是单线程的还是多线程的?
代码演示
package com.bianyiit.cast;
public class XianChengDemo1 {
public static void main(String[] args) {
//主方法是多线程还是单线程??
for (int i = 0; i < 5; i++) {
System.out.println("讲课"+i);
}
for (int i = 0; i < 5; i++) {
System.out.println("课后辅导学生"+i);
}
for (int i = 0; i < 5; i++) {
System.out.println("管理班级"+i);
}
}
}
//输出结果:
讲课0
讲课1
讲课2
讲课3
讲课4
课后辅导学生0
课后辅导学生1
课后辅导学生2
课后辅导学生3
课后辅导学生4
管理班级0
管理班级1
管理班级2
管理班级3
管理班级4
结果分析:主方法是从上至下依次执行的,是单线程
七、JVM运行的时候是单线程还是多线程??
package com.bianyiit.anli;
public class JVMDemo {
public static void main(String[] args) {
new Demo1();
new Demo2();
new Demo3();
}
}
//题目的结果:JVM是多线程的
原因解释:
JVM在执行主方法的同时,会自动调用finalize()进行垃圾回收,还有进行资源处理的一些线程
八、程序中创建线程的两种方法
错误示例:创建一个类继承Thread类,然后调用run()执行
package com.bianyiit.cast;
public class ThreadDemo1 {
public static void main(String[] args) {
//Thread:线程,JVM运行执行多线程
//方法一;创建一个类继承Thread类,重写run方法
//创建线程,给定每一个线程具体的任务
BianYiIt bt1 = new BianYiIt("教师讲课"); //教学部
BianYiIt bt2 = new BianYiIt("课后辅导"); //就业辅导部
//让三个线程同时执行
bt1.run();
bt2.run();
}
}
class BianYiIt extends Thread{
//标记任务 task
private String task;
public BianYiIt(String task) {
this.task = task;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(task+(i+1));
}
}
}
//输出结果:
教师讲课1
教师讲课2
教师讲课3
教师讲课4
教师讲课5
课后辅导1
课后辅导2
课后辅导3
课后辅导4
课后辅导5
原因分析:
1.使用run(),这是(单线程)并不是线程同时执行,还是一个线程执行完之后,下一个线程才开始执行
2.执行线程的方法不是run(),是start()
方法一:继承Thread类,并且实现run()
package com.bianyiit.cast;
public class ThreadDemo1 {
public static void main(String[] args) {
//Thread:线程,JVM运行执行多线程
//方法一;创建一个类继承Thread类,重写run方法
//创建线程,给定每一个线程具体的任务
BianYiIt bt1 = new BianYiIt("教师讲课"); //教学部
BianYiIt bt2 = new BianYiIt("课后辅导"); //就业辅导部
BianYiIt bt3 = new BianYiIt("管理班级"); //学工部
//让三个线程同时执行
bt1.start();
bt2.start();
bt3.start();
}
}
class BianYiIt extends Thread{
//标记任务 task
private String task;
public BianYiIt(String task) {
this.task = task;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(task+(i+1));
}
}
}
//输出结果:
课后辅导1
管理班级1
管理班级2
管理班级3
教师讲课1
管理班级4
课后辅导2
管理班级5
教师讲课2
教师讲课3
课后辅导3
课后辅导4
课后辅导5
教师讲课4
教师讲课5
使用这种继承Thread的方式的优点
代码简洁
使用这种继承Thread的方式有两个弊端
1.没有完全面向对象开发(继承Thread,通过任务类的构造方法创建一个任务类对象(这个对象是线程类对象),通过线程类对象调用start())
2.由于java中只能单继承,所以,这种方式的线程不能继承其他类
方法二:实现Runnable接口,并且实现run()
1. static Thread currentThread()返回对当前正在执行的线程对象的引用
2. 通过线程对象调用getName()----获取当前对象的名字
//RunnableDemo.java
package com.bianyiit.cast;
public class RunnableDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5 ; i++) {
//获取线程的名字---首先要获取当前的线程对象
/*static Thread currentThread()
返回对当前正在执行的线程对象的引用 */
Thread thread = Thread.currentThread();
//通过线程对象调用getName()----获取当前对象的名字
System.out.println(thread.getName());
//System.out.println(Thread.currentThread().getName());
}
}
}
3.通过线程对象调用setName()----设置当前对象的名字
package com.bianyiit.cast;
import javafx.concurrent.Task;
public class RunnableTest {
public static void main(String[] args) {
//创建任务对象
RunnableDemo task1 = new RunnableDemo();
//创建了三个线程对象同时将这一个任务对象作为参数
Thread thread1 = new Thread(task1); //教学部
thread1.setName("教学部");
Thread thread2 = new Thread(task1); //就业辅导部
thread2.setName("就业辅导部");
Thread thread3 = new Thread(task1); //学工部
thread3.setName("学工部");
//执行线程
thread1.start();
thread2.start();
thread3.start();
}
}
//输出结果:
教学部
学工部
学工部
学工部
学工部
学工部
就业辅导部
就业辅导部
就业辅导部
就业辅导部
就业辅导部
教学部
教学部
教学部
教学部
使用这种实现Runnable的方式有两个优点
1.采用面向对象的思想去封装了任务(第二种方式:实现Runnable 通过任务类的构造方法创建一个任务类对象(这个对象不是线程类对象),将这个任务类对象作为线程类的构造方法的参数去创建一个线程类对象,通过线程类对象调用start())
2.他没有继承任何类,所以以后任务对象可以继承任意类
使用这种实现Runnable的方式的弊端
代码稍微复杂
九、创建线程的两种方式的总结
1.写一个类继承Thread,重写了run(),把线程中的任务当做参数传递
2.写一个任务类去实现Runnable接口,实现了run(),多个线程共享同一个资源
3.不同的线程做的任务都不相同的时候,建议使用第一种方式
4.多个线程做的任务相同(共享了同一个资源),使用第二种方式---实际开发用的比第一种方式要多
十、使用多线程打印1-100
package com.bianyiit.anli;
public class Demo1 {
public static void main(String[] args) {
//使用多线程打印1-100
printTask pt = new printTask();
Thread td1=new Thread(pt);
td1.setName("person1:");
Thread td2=new Thread(pt);
td2.setName("person2:");
Thread td3=new Thread(pt);
td3.setName("person3:");
td1.start();
//td1.start();//td1启动了两次start 会报异常java.lang.IllegalThreadStateException(非法线程状态异常)
td2.start();
td3.start();
}
}
class printTask implements Runnable{
@Override
public void run() {
for (int i = 1; i < 100 ; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
小细节:
1.线程只能启动一次,如果启动多次会报异常java.lang.IllegalThreadStateException(非法线程状态异常)
2.多个线程可以同时执行同一个任务,共享同一个资源
十一、经典面试题:线程的状态转换
线程的状态转换图:
1 初始化状态(创建线程对象)--t1.start()-->就绪状态 -线程是可以执行的,但是没有执行权,没有分配到CPU的时间片段*/
2 就绪状态--(运行过程)-->执行状态 -线程是可以执行的,已经有了CPU的执行权*/
3 执行状态--(线程执行完毕/JVM挂了)-->死亡状态*/
4 执行状态--(CPU可能遇到了一些突发情况/wait()-等待/sleep()-休眠)-->阻塞状态*/
5 阻塞状态--(如果想要继续运行/notify()-唤醒/sleep到不需要唤醒,休息时间一到会自动进入就绪状态)-->就绪状态*/
6 执行状态--yeil()/这时线程已经获得了时间片段,将这个时间片段让出来,重现回到就绪状态,等待下一个时间片段-->就绪状态*/
注意:进程里面创建了很多的线程,这些线程没有立马去执行,而是要获取CPU的执行权之后才能去执行
十二、多线程买票案例
需求:用五个线程模拟五个售票窗口,共同卖100张火车票,每个线程打印卖第几张票
分析:1.要有100张票 2.需要五个窗口 3.五个窗口同时卖一百张票
代码演示
package com.bianyiit.cast;
public class ProblemDemo {
public static void main(String[] args) {
//多线程买票案例
//需求:用五个线程模拟五个售票窗口,共同卖100张火车票,每个线程打印卖第几张票
/*
* 分析:要有100张票
* 需要五个窗口
* 五个窗口同时卖一百张票*/
//1.先定义一个类---卖一百张票,卖完之后窗口关闭
//2.创建五个线程,同时卖票---五个窗口共享的资源是100张票
//创建五个窗口
SellTicket tc=new SellTicket();
Thread t1=new Thread(tc);
t1.setName("窗口一正在卖第");
Thread t2=new Thread(tc);
t2.setName("窗口二正在卖第");
Thread t3=new Thread(tc);
t3.setName("窗口三正在卖第");
Thread t4=new Thread(tc);
t4.setName("窗口四正在卖第");
Thread t5=new Thread(tc);
t5.setName("窗口五正在卖第");
//五个窗口同时卖票
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
//任务类---用来卖一百张票
class SellTicket implements Runnable{
//定义一百张票
int ticket=100;
@Override
public void run() {
//买票
while (true) {
if(ticket>0){ //当票大于0时继续买票
try {
//让售票员休息一会
//因为run()重写了父类的run(),不能抛给run()
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ticket+"张票");
ticket--;
}else{ //当票小于0时关闭窗口
break;
}
}
}
}
//输出结果:(这里只输出从第25张票开始卖票的结果)
窗口四正在卖第25张票
窗口五正在卖第24张票
窗口二正在卖第23张票
窗口三正在卖第22张票
窗口一正在卖第21张票
窗口二正在卖第20张票
窗口五正在卖第19张票
窗口一正在卖第19张票
窗口四正在卖第19张票
窗口三正在卖第16张票
窗口三正在卖第15张票
窗口四正在卖第14张票
窗口一正在卖第13张票
窗口五正在卖第12张票
窗口二正在卖第11张票
窗口三正在卖第10张票
窗口五正在卖第9张票
窗口一正在卖第8张票
窗口四正在卖第7张票
窗口二正在卖第6张票
窗口三正在卖第5张票
窗口二正在卖第4张票
窗口四正在卖第3张票
窗口一正在卖第2张票
窗口五正在卖第1张票
窗口一正在卖第0张票
窗口四正在卖第-1张票
窗口二正在卖第-2张票
窗口三正在卖第-3张票
出现的问题
当线程使用sleep()之后出现了两个问题:
1.卖了重复的票----窗口五正在卖第19张票 窗口一正在卖第19张票 窗口四正在卖第19张票
解释:
1.1当票数等于2的时候,首先窗口三获得了执行权,正想卖的时候,CPU马上切换到了窗口二
1.2窗口二就卖了一张,票还剩下1张,然后窗口三继续卖票,还是面对的是原先的二张,卖了一张,也还剩一张
2.出现了负数----窗口三正在卖第-1张票,窗口二正在卖第-2张票
解释:
2.1 当ticket大于0的时候,t1,t2,t3都可以进这个判断语句进行卖票
2.2 ticket=1的时候,当t1过来,一看有票,就进来了,肚子开始疼了,就先离开了
2.3 这时t2过来了,一看邮票,也进来了,肚子也疼了,也离开了 这时ticket还是等于1
2.4 这时t3过来了,一看有票,也进来了,它不肚子疼,开始卖最后一张票,ticket=0
2.5 这时t1回来了,因为之前看到有票,不知道t3已经卖票了,继续卖票,ticket=-1;
2.6 这时t2回来了,因为之前看到有票,不知道t1和t3已经卖票了,继续买票,ticket=-2;
2.7 因为t1和t2已经进入了判断语句,只是休眠了,当它回来的时候,面对的还是肚子疼之前的票数
十三、多线程买票解决方案–synchronized锁机制
如何解决线程睡一会的阻塞状态的问题?
//如何解决线程睡一会的阻塞状态的问题?
/*坐火车:
* 1.张三想上厕所,看到厕所的灯是绿色的,就进去了,把门锁住,灯变成了红色
* 2.李四也想上厕所,看到了厕所的灯是红色的,只能憋着
* 3.张三出来了,把锁打开,一开门,灯就变绿了,李四进去了,把锁锁住,灯又变红了
* 4.老王来了,看到灯是红色的,也只能憋着,李四把锁打开,开了门,然后肚子又开始疼了
* 5.又重新把锁锁住,灯又变红了*/
//当某个人在享用某个东西的时候,为了避免让别人使用,我们就把它锁住
//锁住之后,只有自己使用完之后,别人才能接着使用
//上锁(提高程序的安全性)---synchronized (锁对象){多个线程执行的代码}
//synchronized(修饰符):修饰代码块和方法--同步代码块和同步方法
/*同步代码块的格式:
* synchronized (线程共享的资源)
* {
* 需要锁住的多个线程可以同时执行的代码
* }*/
package com.bianyiit.cast;
import static java.lang.Thread.sleep;
public class SolveProblemDemo {
public static void main(String[] args) {
MaiPiao mp=new MaiPiao();
Thread t1=new Thread(mp);
t1.setName("窗口一正在卖第");
Thread t2=new Thread(mp);
t2.setName("窗口二正在卖第");
Thread t3=new Thread(mp);
t3.setName("窗口三正在卖第");
t1.start();
t2.start();
t3.start();
}
}
class MaiPiao implements Runnable{
int ticket=25;
Object obj=new Object();
@Override
public void run() {
while (true) {
/*加了锁之后,当某个线程执行到这里的时候,其它线程给我老老实实等着这个线程执行完
看下一个CPU执行权分配给谁,谁就执行,其它线程还是一样的在外面等着它执行完*/
synchronized (obj) {
if(ticket>0){
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ticket+"张票");
ticket--;
}else{
break;
}
}
}
}
}
//输出结果:
窗口一正在卖第25张票
窗口三正在卖第24张票
窗口二正在卖第23张票
窗口三正在卖第22张票
窗口一正在卖第21张票
窗口一正在卖第20张票
窗口三正在卖第19张票
窗口二正在卖第18张票
窗口二正在卖第17张票
窗口二正在卖第16张票
窗口三正在卖第15张票
窗口一正在卖第14张票
窗口一正在卖第13张票
窗口一正在卖第12张票
窗口三正在卖第11张票
窗口二正在卖第10张票
窗口三正在卖第9张票
窗口三正在卖第8张票
窗口三正在卖第7张票
窗口三正在卖第6张票
窗口三正在卖第5张票
窗口三正在卖第4张票
窗口三正在卖第3张票
窗口一正在卖第2张票
窗口一正在卖第1张票
注意:
1 加了synchronized锁之后,当某个线程执行到这里的时候,其它线程给我老老实实等着这个线程执行完
2 看下一个CPU执行权分配给谁,谁就执行,其它线程还是一样的在外面等着它执行完