目录
线程相关概念
-
程序:是一些指令的集合,也就是我们写的代码
-
进程:就是运行中的程序,操作系统会为一个进程分配内存空间,分配资源。
进程是程序的一次执行过程,是动态过程:有它自身的产生、存在和消亡的过程
-
线程:由进程创建的,是进程的一个实体,一个进程可以拥有多个线程
-
单线程:一个时刻只允许一个线程
-
多线程:一个时刻可以有多个线程
-
-
并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,单核CPU实现的多任务就一定是并发
-
并行:同一个时刻,多个任务可以同时执行。需要多核CPU实现并行
- 电脑中并发和并行会同时存在
线程的创建
Thread
通过继承Thread类,调用class.start()方法开启一个线程
package com.pc.threadUse;
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//创建Cat,可以当做线程使用
Cat cat = new Cat();
/**这里不用run的原因,是因为run方法就是一个普通的方法,相当于函数调用
* 不会真正的启动一个线程,也就是会阻塞主线程。
* */
cat.start();//启动线程
/**追源码发现,start方法使用的是start0()这个方法,
* start0()是native方法,由JVM调用,最终交给操作系统进行统一调度
* 涉及到操作系统的调度知识
* start0()是真正启用线程的函数
* */
//main启动一个子线程后,Thread-0,主线程不会阻塞在这里
System.out.println("主线程继续执行" + Thread.currentThread().getName());
for (int i=0; i < 10; i++) {
System.out.println("主线程");
Thread.sleep(1000);
}
}
}
class Cat extends Thread {
int time = 0;
@Override
public void run() {
//创建Cat对象,可以当做线程使用
while (time < 80) {
System.out.println("喵喵,我是一只小猫咪" + (++time) + "线程名称:" + Thread.currentThread().getName());
//休眠一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Runnable
通过实行Runable接口来创建一个线程,需要new Thread来调用start方法开启线程。
package com.pc.threadUse;
//使用Runable开发线程
public class thread02 {
public static void main(String[] args) {
Dog dog=new Dog();
//dog.start(); 这里不能调用start
Thread thread=new Thread(dog);
thread.start();
}
}
class Dog implements Runnable {
int count = 0;
@Override
public void run() {
while (count!=10) {
System.out.println("小狗汪汪叫" + (++count) +" "+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
区别
-
没有本质区别,底层都调用start0();
-
实现Runable接口的方式更加适合多个线程共享一个资源的情况,避免了单继承的限制
-
Runable,Thread的设计使用了代理模式。
-
代理模式
-
概念:一个类代表另一个类的功能。
-
优点:职责清晰、高扩展性
-
缺点:造成请求的处理速度变慢、实现代理模式需要额外的工作,有些代理模式的实现非常复杂
-
-
线程的创建流程图
一个进程的开启,会启动主线程,主线程会开启新的子线程,子线程中也可以创建另一个线程……
一个进程的消亡,是等该进程中所有的线程都消亡。
如图:
注意点:主线程消亡进程不一定消亡,线程中也可以开启新的线程
线程常用方法
第一类:
-
setName(); 设置线程名称
-
getName(); 活动线程名称
-
start(); 启动一个线程
-
run(); 线程真正执行的方法
-
setPriority 更改线程优先级(优先级范围 1-10,1最低,10最高)
-
getPriority 获得线程优先级
-
sleep 休息(毫秒)
-
interrupt 中断线程(但没有终止线程,所以一般用于中断正在休眠的线程)
第二类:
-
yield 线程礼让,让其他线程先执行,但礼让的时间不确定,所以也不一定成功
-
join 线程插队,一旦插队成功,肯定先执行完插入的线程的所有任务
两种线程模式:
-
用户线程:独立于其他线程,自己运行。默认的线程模式
-
守护线程:其他用户线程结束,守护线程也会结束,Thread.setDeamon(true)可以将Thread设置成守护线程
线程的生命周期:
一般的,线程有6个状态。
-
New 尚未启动的线程
-
Runable 可运行状态
-
TimeWaiting 休眠状态,计时等待
-
Waiting 等待状态,等待别的线程运行完
-
Blocked 上锁等待
-
Teminated 结束
其中Runable状态又分为Ready 就绪状态,Running 正在运行状态。因此有些地方称线程有七种状态。
各状态转换如图:
死锁
概念
- 一个线程拿到锁A而得不到锁B,另一个线程拿到锁B而拿不到锁A,而线程1,2释放锁的条件为拿到锁A和锁B,这种情况程序就运行不下去了,称为死锁
释放锁
以下情况会释放锁:
-
同步代码块执行完毕
-
同步代码块中遇到break,return
-
同步代码块中遇见了Error或者Exception异常
-
同步代码块中执行了wait(),线程暂停,释放锁
以下情况不会释放锁:
-
当前线程的sleep()和yield()方法
-
其他线程调用了该线程的suspend()方法将该线程挂起。(尽量避免使用suspend()和resume()方法,这两种方法已经被淘汰)
多线程售票问题
遇见的问题
多个线程同时操作一个敏感数据-->剩余车票,会导致数据的更新不及时而导致超售。也就是剩余车票变为负数的问题。
解决方案
利用线程同步机制
-
概念:在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就需要使用同步访问技术,保证数据在任何同一时刻,最多只能有一个线程访问,以保证数据的完整性
-
方法:使用Synchronized关键字,写同步代码块synchronized(对象){}
-
原理:互斥锁。synchronized修饰的代码块,调用时会拿到“互斥锁”,结束后,将“互斥锁”返回给底层,多个线程继续去抢这个“锁”,拿到锁的线程进入代码块。
-
局限性:导致程序的执行效率降低
-
注意点:
-
静态方法加锁,synchronized(对象){}需要改为-->synchronized(类名.class){}
-
细节:要让多个线程共用一个对象的一把锁,不要锁错对象!
-
具体实现的代码:
package com.pc.syn;
public class sellTicket {
public static void main(String[] args) throws InterruptedException {
//synchronized关键词
SellTicket03 sellTicket02=new SellTicket03();
Thread thread = new Thread(sellTicket02);
thread.start();
new Thread(sellTicket02).start();
new Thread(sellTicket02).start();
}
}
//使用synchronized
class SellTicket03 implements Runnable{
private static int ticketNum=100; //多个线程共享
private boolean loop=true;
//这个代码块会被三个线程“同步访问”
private synchronized void sell(){
if(ticketNum<=0){
System.out.println("售票结束....");
loop=false;
return;
}
//休眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()+":售出了一张票"+"剩余票数:"+(--ticketNum));
}
@Override
public void run() {
while (loop){
//没抢到“锁”的线程,会被阻塞在这里
sell();
}
}
}