多线程(完整!内含面试题讲解,很重要)

一、 多线程

1.1 程序,进程,线程

程序:一堆命令的集合,完成某个特定任务,是静态的,保存在硬盘中

进程:是程序的一次执行过程,就是把程序载入内存中执行,就是进程,是动态的

线程:是进程进一步细化,是程序内部的一条执行分支

如果一个进程同一时间执行多个线程,就是支持多线程

我们现在的java程序,运行的时候,main就是执行的开始,在栈内存中开启main栈帧的时候,就是开启了一个线程

一个线程就是一个栈及栈中的链式栈帧调用,当栈底元素弹栈后,则线程终止

以main方法为栈底元素的栈帧,称为主线程

CPU时间片,把CPU5次执行的时间当成一个基本单位,然后给每个进程分配时间片,这个由操作系统决定

一般是根据我们设定的优先级进行分配

1.2 并行和并发

并发:同时发生,一个CPU同时执行多个任务

并行:同时执行,多个CPU同时执行多个任务

1.3 单核CPU和多核CPU

a) 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费 才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时 间单元特别短,因此感觉不出来。

b) 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)

c) 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

1.4 多线程优缺点和应用场景

背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?

多线程程序的优点:

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  2. 提高计算机系统CPU的利用率
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和

修改

  • 程序需要同时执行两个或多个任务。

  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。

  • 需要一些后台运行的程序时。

1.5 线程创建

创建线程有两种方式,但是启动线程只有一种方式

1 继承thread类,并覆写run方法

2 实现runnable接口,并实现run方法

3 启动只有一种,就是调用线程对象的start()方法

1.5.1 thread

image-20240815183636945

1.5.2 runnable

[外链图片转存中…(img-jHxUcH2w-1723811221488)]

1.5.3 继承和实现的区别

public class *Thread* extends Object implements Runnable

区别

  • 继承Thread:线程代码存放Thread子类run方法中。
  • 实现Runnable:线程代码存在接口的子类的run方法。

实现方式的好处

  • 避免了单继承的局限性
  • 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线 程来处理同一份资源。

1.6 优先级和常用方法

1.6.1 优先级概述

线程的优先级等级

a) MAX_PRIORITY:10

b) MIN _PRIORITY:1

c) NORM_PRIORITY:5

涉及的方法

d) ***getPriority() :***返回线程优先值

e) ***setPriority(int newPriority) :***改变线程的优先级

说明

f) 线程创建时继承父线程的优先级

g) 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

1.6.2 使用方式

[外链图片转存中…(img-Nw1cXkg5-1723811221488)]

image-20240815184041375

1.7 生命周期

JDK中用Thread.State类定义了线程的几种状态

要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五 种状态

新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建 状态

***就绪:***处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源

***运行:***当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线 程的操作和功能

***阻塞:***在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态

***死亡:***线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

image-20240815184201210

1.8 线程控制

image-20240815184220642

1.8.1 sleep

[外链图片转存中…(img-qHSfPF78-1723811221489)]

1.8.2 线程停止stop

image-20240815184317907

[外链图片转存中…(img-iLGwVjUL-1723811221490)]

1.8.3 线程合并join

image-20240815184350933

image-20240815184356887

1.8.4 yield

thread.yield静态方法,暂停当前正在执行的进程,并让其他进程执行

1 静态,就意味着写在哪个进程,就暂停在哪个进程

2 只给相同优先级让位

  • 按下Win+R键,输入“msconfig”并回车打开系统配置。
  • 选择“引导”选项卡,然后点击“高级选项”。
  • 在处理器数下拉菜单中选择“1”,然后点击确定并重启电脑。

[外链图片转存中…(img-DA4ZGPTo-1723811221490)]

1.9 线程同步

1.9.1 概述

问题的提出

多个线程执行的不确定性引起执行结果的不稳定

多个线程对账本的共享,会造成操作的不完整性,会破坏数据

image-20240815184714173

1.9.2 不同步带来的影响

[外链图片转存中…(img-KJqoxqFT-1723811221491)]

image-20240815184747519

image-20240815184756304

[外链图片转存中…(img-7YTt9xvN-1723811221491)]

[外链图片转存中…(img-WUkWfr6y-1723811221492)]

image-20240815184816491

1.9.3 解决方法

当多个线程,有可能同时操作某一个数据的时候(非查询操作),为了保证数据的一致性和正确性,需要进行同步执行

是一种数据安全机制

1.9.3.1 方法锁

synchronized修饰符,用于给方法加锁,多个线程不能同时访问该对象中加锁的方法

如果访问一个对象中加锁的成员方法/对象语句块,则改对象中"所有加锁的成员方法及对象语句块锁,全部锁定"

如果访问一个类中加锁的静态方法/类语句块锁,则该类中"所有加锁的静态方法及类锁,全部锁定"

synchronized(对象){}对象语句块锁

synchronized(类.class){} 类语句块锁

[外链图片转存中…(img-OTsrRQkJ-1723811221492)]

1.9.3.2 语句块锁

​ // 方法锁,该方法别的线程进不来,在方法外排队等待

​ // 如果方法中代码比较多,而且仅仅只有一部分代码需要同步,那么使用方法锁会大大降低运行效率

​ // 此时可以使用语句块锁,只锁需要同步的代码即可

image-20240815185830950

1.10 lock

1.10.1 概述

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同

​ 步锁对象来实现同步。同步锁使用Lock对象充当。

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象 加锁,线程开始访问共享资源之前应先获得Lock对象。

  • ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和 内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以 显式加锁、释放锁。

1.10.2 使用

[外链图片转存中…(img-Xsskk3RV-1723811221493)]

1.10.3 优缺点

Lock锁是显示锁(需要手动加锁和解锁)

Synchronized是隐式锁,自动加锁解锁

使用Lock锁,JVM可以花费更少的时间来调度线程,并且提供了很多子类,有更好的扩展性

1.11 定时器任务

1.11.1 概述

定时器,计划任务,会开启新线程进行计时,时间到了之后,会开启新线程来执行规定的任务

1.11.2 使用

image-20240815190049011

二、死锁

2.1 概述

死锁:就是在执行的过程中,都遇到对方进入加锁的方法中,从而导致大家都访问不了的状态

原理:2个线程 2个对象

1 a线程的执行完成,需要先后嵌套锁定两个对象,并且该线程会先锁定o1,在锁定o2

2 b线程的执行完成,需要先后嵌套锁定两个对象,并且该线程会先锁定o2,在锁定o1

3 当a线程锁定o1后,要去锁定o2 的时候,发现o2已经被锁定了,所以进入等待状态,等待o2交出锁

4 当b线程锁定o2后,要去锁定o1的时候,发现o1已经被锁定了,所以进入等待状态,等待o1交出锁

2.2 代码实现

public class Thread_01_DeadLock {
	public static void main(String[] args) {
		Object o1  = new Object();
		Object o2 = new Object();
		Thread t1 = new T1(o1,o2);
		Thread t2 = new Thread(new T2(o1, o2));
		t1.start();
		t2.start();
	}
}

class T1 extends Thread {
	Object o1;
	Object o2;

	public T1(Object o1, Object o2) {
		this.o1 = o1;
		this.o2 = o2;
	}

	@Override
	public void run() {
		synchronized (o1) {
			System.out.println("t1锁定o1");
			try {
				// 加睡眠,是为了保证一定死锁
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (o2) {
				System.out.println("t1锁定o2");
			}
		}
		System.out.println("t1执行完成");
	}
}

class T2 implements Runnable {
	Object o1;
	Object o2;

	public T2(Object o1, Object o2) {
		this.o1 = o1;
		this.o2 = o2;
	}

	@Override
	public void run() {

		synchronized (o2) {
			System.out.println("t2锁定o2");
			try {
				// 加睡眠,是为了保证一定死锁
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (o1) {
				System.out.println("t2锁定o1");
			}
		}
		System.out.println("t2执行完成");
	
	}
}

三、线程通信

3.1 概述

image-20240816200827499

Object中的方法

wait():该线程进入等待状态(挂起状态),会交出持有的锁,当贝唤醒的时候就,进入就绪状态

进入执行状态的时候,会接着挂起的地方,继续执行’

无参和传入0,表示不会自动醒,必须等着线程唤醒它

可以传入long类型的毫秒数,多久之后自动醒

notify();随机唤醒一个在该对象中挂起的线程

notifyAll();唤醒所有在该对象中挂起的线程

以上方法,只能用在成员方法中,并且该方法必须有锁(synchronized方法锁或synchronized语句块锁)

3.2 使用方式

public class Thread_02_Wait {
	public static void main(String[] args) {
		Num num = new Num();
		Thread t1 = new Thread(new PrintThread(num));
		Thread t2 = new Thread(new PrintThread(num));
		t1.setName("t1");
		t2.setName("t2");
		t1.start();
		t2.start();
	}
}
class PrintThread implements Runnable{
	Num num ; 
 public PrintThread(Num num) {
	 this.num=num;
	}
	@Override
	public void run() {
		while (true) {
			num.print();
		}
	}
}
class Num{
	int num = 1;
	public synchronized void print(){
		System.out.println(Thread.currentThread().getName()+" --> "+num);
		num++;
		this.notifyAll();
		try {
			Thread.sleep(500);
			this.wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

3.3 生产者和消费者

https://www.cnblogs.com/ldddd/p/15770457.html

image-20240816201448521

3.4 代码实现(重点,基本面试必考)

public class Thread_03_ProducerConsumer {
	public static void main(String[] args) {
		SynStack ss = new SynStack();
		Thread p1 = new Thread(new Producer(ss));
		Thread p2 = new Thread(new Producer(ss));
		Thread p3 = new Thread(new Producer(ss));
		Thread c1 = new Thread(new Consumer(ss));
		Thread c2 = new Thread(new Consumer(ss));
		Thread c3 = new Thread(new Consumer(ss));
		p1.setName("p1");
		p2.setName("p2");
		p3.setName("p3");
		c1.setName("c1");
		c2.setName("c2");
		c3.setName("c3");
		p1.start();
		p2.start();
		p3.start();
		c1.start();
		c2.start();
		c3.start();
	}
}

class Producer implements Runnable {
	private SynStack ss;

	public Producer(SynStack ss) {
		this.ss = ss;
	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			char ch = (char) (i + 'a');
			ss.push(ch);
		}
	}
}

class Consumer implements Runnable {
	private SynStack ss;

	public Consumer(SynStack ss) {
		this.ss = ss;
	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			ss.pop();
		}
	}
}

class SynStack {
	// 缓冲区
	char[] data = new char[6];
	// 库存
	int count = 0;

	// 生产方法
	// 生产者 : 库存满了,就等待
	// 消费者 : 没有库存了,就等待
	public synchronized void push(char ch) {
		// 判断库存是否满
		while (count == data.length) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		// 判断消费者是否进入挂起
		if (count == 0) {
			this.notifyAll();
		}
		data[count] = ch;
		count++;
		System.out.println(Thread.currentThread().getName() + "生产了 : " + ch
				+ " , 剩余库存 : " + count);
	}

	// 0
	public synchronized char pop() {
		while (count == 0) {
			try {
				this.wait(); // c1  c2
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		if (count == data.length) {
			this.notifyAll();
		}
		count--;
		char ch = data[count];
		System.out.println(Thread.currentThread().getName() + "消费了 : " + ch
				+ " , 剩余库存 : " + count);
		return ch;
	}
}

四、单例模式

image-20240816201649607

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值