一、线程的介绍
1.1.程序
为完成特定任务,用某种语言编写的一组指令的集合。(代码)
1.2.进程
- 进程就是指运行中的程序,启动一个进程,操作系统就会为该进程分配内存空间。
- 进程是程序的一次执行过程,或是正在运行的一个程序,是动态过程:有它自己的产生、存在和消亡的过程
1.3.线程
- 线程是由进程创建的,是进程的一个实体
- 一个进程可以拥有多个线程
1.31.单线程
同一个时刻,只能执行一个线程
1.32.多线程
同一个时刻,可以进行多个线程。(比如一个QQ进程,可以同时打开多个连天窗口)
1.33.并发
同一时刻,多个任务交替进行。简单来说,单核CPU实现的多任务就是并发。
1.34.并行
同一时刻,多个任务同时执行。多核CPU可以实现并行。
注:并发和并行可同时进行
二、线程的基本使用
2.1.创建线程的两种方式
2.11继承 Thread 类,
重写run 方法;
2.22实现Runnable接口,
重写run 方法。
Java中,类是单继承,在某些情况下,一个类可能已经继承了某个父类,这个时候就可以通过实现Runnable接口来创建线程。
例1:继承Thread类,创建线程
要求:1.编写一个线程,该线程每隔2秒,在控制台输出"你好!"
2.改进上述线程,当输出10次"你好!",线程关闭
package thread.thread02;
/**
* @author yeye
* @desc 通过继承Thread类创建线程
* 1.编写一个线程,该线程每隔2秒,在控制台输出"你好!"
* 2.改进上述线程,当输出10次"你好!",线程关闭
* @date 2024/8/24 10:52
*/
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//创建一个Cat对象,可以看做是一个线程
Cat cat = new Cat();
//启动线程,最终会执行cat的run()方法
cat.start();
//当main线程启动一个子线程Thread-0,主线程不会阻塞,会继续执行
System.out.println("主线程继续执行"+"线程名:"+Thread.currentThread().getName());
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行"+i+"次");
//让主线程休眠1秒
Thread.sleep(1000);
}
}
}
/**
* 如果一个类继承了Thread类,那么这个类就成为一个线程类。
* 线程类要重写run()方法,该方法是线程的入口方法。
* Thread类中的run()方法 实际上实现类Runnable接口的run()方法。
*/
class Cat extends Thread{
int times = 0;
@Override
public void run() { //重写run()方法,写上线程要执行的代码
while (true){
//该线程每隔2秒,在控制台输出"你好!"
System.out.println("你好!"+(++times)+"线程名:"+Thread.currentThread().getName());
//让线程休眠2秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//当输出10次"你好!",线程关闭
if(times == 100){
break;
}
}
}
}
例2:实现Runnable接口,创建线程
要求:编写一个程序,每隔1秒,在控制台输出"你好",当输出10次后,自动退出,使用实现Runnable接口的方法实现。这里底层使用了设计模式,是静态代理。
package thread.thread02;
/**
* @author yeye
* @desc 通过实现 Runnable 接口创建线程
* @date 2024/8/24 12:09
*/
public class Thread02 {
public static void main(String[] args) {
Person person = new Person();
//person.start();不能调用start方法
//创建Thread对象,把person对象(实现Runnable接口),放入Thread
Thread thread = new Thread(person);
thread.start();
}
}
class Person implements Runnable { // 实现 Runnable 接口,创建线程
int count = 0;
@Override
public void run() { // 重写 run() 方法-普通方法,没有正真实现多线程
while(true){
System.out.println("你好"+(++count)+Thread.currentThread().getName());
//休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count ==10){
break;
}
}
}
}
例3:多线程执行
要求:编写一个程序,创建两个线程,一个线程每隔1秒输出"今天很热!",输出10次退出,另一个线程每隔1秒输出"今天不冷",输入5次退出。
package thread;
/**
* @author yeye
* @desc main线程启动两个线程
* 创建两个线程,一个线程每隔1秒输出"今天很热!",输出10次退出,
* 另一个线程每隔1秒输出"今天不冷",输入5次退出。
* @date 2024/8/24 14:54
*/
public class Thread03 {
public static void main(String[] args) {
A1 a1 = new A1();
A2 a2 = new A2();
Thread thread = new Thread(a1);
Thread thread2 = new Thread(a2);
thread.start();
thread2.start();
}
}
class A1 implements Runnable{
int count = 0;
@Override
public void run() {
//每隔1秒输出"今天很热!",输出10次退出
while (true){
System.out.println("今天天气很热! "+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
class A2 implements Runnable{
int count = 0;
@Override
public void run() {
//每隔1秒输出"今天不冷",输入5次退出。
while(true) {
System.out.println("今天天气不冷!"+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
break;
}
}
}
}
2.23继承Thread和实现Runnable的区别
-
从Java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上来说没有区别——都是通过stare()方法调用stare0()方法实现多线程;
-
实现Runnable接口方式更适合多线程共享资源的情况,并且避免了单继承的限制
例:实现三个窗口同时买票,票数一共100张。
1.通过继承Thread的方式
package thread.ticket;
/**
* @author yeye
* @desc
* @date 2024/8/24 16:03
*/
public class SellTicket01 {
public static void main(String[] args) {
TicketWindow tw1 = new TicketWindow();
TicketWindow tw2 = new TicketWindow();
TicketWindow tw3 = new TicketWindow();
tw1.start();
tw2.start();
tw3.start();
}
}
//继承Thread类的方式,实现三个窗口同时买票
class TicketWindow extends Thread {
public static int ticketNum =100; //让多个线程共享ticketNum
@Override
public void run() {
while(true) {
if (ticketNum > 0) {
System.out.println("窗口" + Thread.currentThread().getName() + "售出1张票,还剩" + (--ticketNum)+"张票");
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else{
System.out.println("窗口"+Thread.currentThread().getName()+"票已售空,明天再来!");
break;
}
}
}
}
2.通过实现Runnable接口的方式
package thread.ticket;
/**
* @author yeye
* @desc
* @date 2024/8/24 16:03
*/
public class SellTicket02 {
public static void main(String[] args) {
TicketWindow02 window1 = new TicketWindow02();
new Thread(window1).start();
new Thread(window1).start();
new Thread(window1).start();
}
}
//继承Thread类的方式,实现三个窗口同时买票
class TicketWindow02 extends Thread {
public int ticketNum =100; //让多个线程共享ticketNum
@Override
public void run() {
while(true) {
if (ticketNum > 0) {
System.out.println("窗口" + Thread.currentThread().getName() + "售出1张票,还剩" + (--ticketNum)+"张票");
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else{
System.out.println("窗口"+Thread.currentThread().getName()+"票已售空,明天再来!");
break;
}
}
}
}
2.2.线程终止
- 当线程完成任务后,会自动终止;
- 使用变量来控制run()方法退出的方式停止线程,即通知方式
例1:启动一个线程t,要求在main线程中停止线程t
package thread.exit_thread;
/**
* @author yeye
* @desc 启动一个线程t,要求在main线程中停止线程t
* @date 2024/8/24 16:54
*/
public class ThreadExit01 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
//主线程休眠10秒后,在通知t线程退出
Thread.sleep(10000);
/**
* 如果希望main线程控制t线程终止,必须可以修改loop
* 让t退出run()方法,从而终止t线程
*/
t.setLoop(false);
}
}
class T extends Thread {
private int count = 0;
//设置一个控制变量
public Boolean loop = true;
@Override
public void run() {
while (loop) {
System.out.println("线程t运行" + (++count));
//休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setLoop(Boolean loop) {
this.loop = loop;
}
}
2.3线程常用的方法
2.31常用方法1:
1.setName () //设置线程的名称
2.getName() //返回该线程的名称
3.start() //启动线程;Java虚拟机底层调用该线程的start0()方法
4.run() //调用线程对象的run 方法
5.setPriority() //更改线程的优先级
6.getPriority() //获取线程的优先级
7.sleep() //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
8.interrupt() //中断线程,并没有正真中断线程,而是中断正在休眠的线程
注意:
-
start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新线程
-
线程优先级的范围
MAX_PRIORITY - 10 MIN_PRIORITY - 1 NORM_PRIORITY - 5
-
interrupt,中断正在休眠的线程
-
sleep:线程的静态方法,使当前方法休眠
例1:上述1-8方法的演示
package thread.method;
/**
* @author yeye
* @desc
* @date 2024/8/24 17:51
*/
public class ThreadMethod01 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.setName("椰椰");
t.setPriority(Thread.MAX_PRIORITY);
t.start(); //启动子线程
//主线程打印5个"beautiful",然后中断子线程的休眠
for(int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("beautiful");
}
System.out.println(t.getName()+"线程的优先级"+t.getPriority());
t.interrupt(); //中断子线程的休眠
}
}
class T extends Thread{
@Override
public void run() {
while(true){
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "背单词" + i);
}
try {
System.out.println(Thread.currentThread().getName() + "休眠10秒");
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被中断了");
}
}
}
}
3.32常用方法2:
1.yield: 线程的礼让。让出CPU,让其他线程执行,但礼让的线程时间不确定,所以也不一定礼让成功
2.join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插队的线程所有的任务
例: join()方法和yield方法的演示,主线程吃10个包子,子线程吃10个包子,主线程让子线程先吃。
package thread.method;
/**
* @author yeye
* @desc
* @date 2024/8/24 22:25
*/
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
Test t = new Test();
t.start(); //启动线程
for(int i=1; i<=10; i++) {
Thread.sleep(1000);
System.out.println("主线程吃了"+i+"个包子");
if(i == 5) {
System.out.println("主线程让子线程先吃");
//线程礼让,让子线程先执行,不一定成功
//Thread.yield(); //让出CPU,让子线程先执行
//join,线程插队,让子线程先执行完毕,再执行主线程
t.join(); //让子线程执行完毕,等待子线程结束
System.out.println("子线程结束, 继续执行主线程");
}
}
}
}
class Test extends Thread {
@Override
public void run() {
for(int i=1; i<=10; i++) {
try {
Thread.sleep(1000); //休眠1秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程吃了"+i+"个包子");
}
}
}
实例:线程插队练习
要求:1.主线程每隔1s,输出 我要减肥,一共十次;
2.到输出到第五次时,启动子线程(要求实现Runnable接口),每隔1秒输出 减肥好累,等子线程输出10次退出;
3.主线程继续输出 我要减肥,直到主线程退出。
package thread.method;
/**
* @author yeye
* @desc
* @date 2024/8/24 23:31
*/
public class ThreadMethodTest01 {
public static void main(String[] args) throws InterruptedException {
TT tt = new TT();
Thread thread = new Thread((tt));
for(int i = 1;i <=10;i++){
System.out.println("我要减肥"+i);
if(i == 5) {
System.out.println("不想减肥了,好累");
thread.start(); //启动子线程
thread.join(); //礼让子线程结束,继续执行主线程
System.out.println("子线程结束,继续执行主线程");
}
Thread.sleep(1000);
}
}
}
class TT implements Runnable {
@Override
public void run() {
while (true) {
for(int i =1;i<=10;i++){
System.out.println("减肥好累" + i);
//休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}
2.32常用方法3:
1.用户线程:也叫工作线程,当线程的任务执行完或以通知方法结束
2.守护线程:一般为工作线程服务,当所有用户线程结束,守护线程自动结束
常见的守护线程:垃圾回收机制
例:
package thread.method;
/**
* @author yeye
* @desc
* @date 2024/8/25 0:26
*/
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果我们希望当主线程结束后,子线程可以自动结束,只需将子线程设置为守护线程即可
myDaemonThread.setDaemon(true);
myDaemonThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("重新开始减肥");
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread {
@Override
public void run() {
for(;;){
System.out.println("减肥失败");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.4.线程的生命周期
线程状态:
- NEW:尚未启动的线程处于此状态;
- RUNNABLE:在JAVA虚拟机中执行的线程处于此状态,由于调度机的调度处理,可分为READY和RUNNING;
- WAITING:正在等待另一个线程执行特定动作的线程处于此状态;
- TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的状态处于次状态
- BLOCKED:被阻塞等待监视器锁定的线程处于此状态;
- TERMINATED:已退出的线程处于次状态。
演示:
package thread.threadstate;
/**
* @author yeye
* @desc
* @date 2024/8/25 10:49
*/
public class ThreadState01 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getName()+"状态是:"+t.getState());
t.start();
while(Thread.State.TERMINATED != t.getState()){
System.out.println(t.getName()+"状态是:"+t.getState());
Thread.sleep(1000);
}
System.out.println(t.getName()+"状态是:"+t.getState());
}
}
class T extends Thread {
@Override
public void run() {
while(true){
for(int i=0;i<10;i++){
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}
2.5线程同步
2.51.线程同步机制
-
同步:在多线程编程,一些敏感的数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多只有一个线程访问,以保证数据的完整性。
-
也可以理解为:线程同步,即有一个线程对内存进行操作时,其他线程都不可以对这个内存地址经行操作,直到该线程完成操作,其他线程才能对该线程进行操作。
2.52.同步具体方法 ——synchronized
-
同步代码块
synchronized (对象){ //得到对象的锁,才能操作同步代码块 //需要同步的代码块 }
-
synchronized还可以放在方法的声明中,以表示整个方法为同步方法
//例如
public synchronized void t(String name){ //同步方法
//需要同步的代码块
}
例:用第二种方法完善三个售票窗口卖票问题
package thread.synchronized_;
/**
* @author yeye
* @desc
* @date 2024/8/25 13:15
*/
public class SellTicket03 {
public static void main(String[] args) {
TicketSeller03 ticketSeller = new TicketSeller03();
new Thread(ticketSeller).start();//第一个窗口
new Thread(ticketSeller).start();//第二个窗口
new Thread(ticketSeller).start();//
}
}
//实现Runnable接口,使用synchronized实现线程同步
class TicketSeller03 implements Runnable {
private int ticketNumber = 100;
public Boolean loop = true; //控制run方法变量
public synchronized void sell(){ //同步方法,在同一时刻,只能有一个线程来执行sell方法
if (ticketNumber <= 0) {
System.out.println("票已售完");
loop = false;
return;
}
System.out.println("窗口"+Thread.currentThread().getName()+"售出1张票,还剩" + ticketNumber-- + "张票");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() { //同步方法,在同一时刻,只能有一个线程来执行run方法
while(loop) {
sell();
}
}
}
2.6互斥锁
2.61.基本介绍
- Java中,引用对象互斥锁的概念,用来保证共享数据操作的完整性;
- 每个对象都对应一个可称为"互斥锁"的标记,这个标记用来保证在任一时刻,只有一个线程访问该对象;
- 关键字synchronized与对象的互斥锁联系。当某个对象用synchronized修饰时,表明这个对象在任一时刻只能被一个线程访问;
- 同步的局限性:导致程序的执行效率降低;
- 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象);
- 同步方法(静态的)的锁为当前类本身。类.class
2.62注意事项
- 如果同步方法没有使用static修饰,默认锁对象是this;
- 如果同步方法使用static修饰,默认锁对象是:当前类.class
- 实现互斥锁的步骤:
- 需要先分析上锁的代码;
- 选择同步方法或同步代码块;
- 要求多个线程的锁为同一个。
例:使用互斥锁解决售票问题
2.52例题——同步方法、下述例题——同步代码块
package thread.synchronized_;
/**
* @author yeye
* @desc
* @date 2024/8/25 13:15
*/
public class SellTicket02 {
public static void main(String[] args) {
TicketSeller02 ticketSeller = new TicketSeller02();
new Thread(ticketSeller).start();//第一个窗口
new Thread(ticketSeller).start();//第二个窗口
new Thread(ticketSeller).start();//
}
}
//实现Runnable接口,使用synchronized实现线程同步
class TicketSeller02 implements Runnable {
private int ticketNumber = 100;
public Boolean loop = true; //控制run方法变量
Object object = new Object();//互斥锁对象
// //同步方法(静态方法)的锁是对象本身,锁是TicketSeller02.class
// public synchronized static void maipiao() {}
// //静态方法中,实现同步代码块,锁是TicketSeller02.class
// public static void maipiao2() {
// synchronized (TicketSeller02.class) {
// System.out.println("hello");
// }
public void sell(){
synchronized (/**this*/object) { //同步代码块,互斥锁还是this对象,也可以是其他对象,如object
if (ticketNumber <= 0) {
System.out.println("票已售完");
loop = false;
return;
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出1张票,还剩" + ticketNumber-- + "张票");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() { //同步方法,在同一时刻,只能有一个线程来执行run方法
while(loop) {
sell();
}
}
}
2.7线程的死锁
2.71基本介绍
多个线程都占用对方的资源锁,但不肯想让,导致线程死锁。
2.72应用案例
小明:你先让我玩手机,我才完成作业
妈妈:你先完成作业,才让你玩手机
例:
package thread.deadlock;
/**
* @author yeye
* @desc
* @date 2024/8/25 15:10
*/
public class ThreadDeadLock01 {
public static void main(String[] args) {
//模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("现象A");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("线程B");
A.start();
B.start();
}
}
class DeadLockDemo extends Thread {
public Object ob1 = new Object(); //保证多线程,共享同一个对象,这里使用static修饰
public Object ob2 = new Object();
Boolean flag;
public DeadLockDemo(Boolean flag) {
this.flag = flag;
}
@Override
public void run() {
//如果flag为true,线程A就会先得到ob1的对象锁,然后尝试获取ob2的对象锁
//如果线程A得不到ob2的锁,就会发生Blocked
//如果flag为false,线程B就会先得到ob2的对象锁,然后尝试获取ob1的对象锁
if (flag) {
synchronized (ob1) { //对象互斥锁,下面就是同步代码块
System.out.println(Thread.currentThread().getName() + "进入1");
synchronized (ob2) {
System.out.println(Thread.currentThread().getName() + "进入2");
}
}
}else {
synchronized (ob2) {
System.out.println(Thread.currentThread().getName() + "进入3");
synchronized (ob1) {
System.out.println(Thread.currentThread().getName() + "进入4");
}
}
}
}
}
2.8释放锁
2.81释放锁的情况
-
当线程的同步方法、同步代码块执行结束
如:上厕所,上完厕所,打开门出来
-
当前线程在同步方法、同步代码块中遇到break、return
如:上厕所,突然地震,跑出来
-
当前线程在同步方法、同步代码块中出现了未处理的Exception或Error,导致异常结束
如:上厕所,没带纸,出来拿纸
-
当前线程在同步方法、同步代码块中执行了对象的wait()方法,当前线程暂停。并释放锁
如:上厕所,感觉不强烈,出来酝酿,等会再进去
2.82不会释放锁的情况
-
当前线程执行同步方法、同步代码块,调用Thread.sleep()、Thread.yield()方法,只会暂停当前线程,不会释放锁
如:玩手机,玩累了,休息一下眼睛,然后继续玩
-
当前线程执行同步方法、同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁