java多线程-Java基础宋红康学习笔记
1、基本概念
- 程序:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象
- 进程:是程序的依次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程----生命周期
- 如:运行中的QQ,运行中的MP3播放器
- 程序是静态的,进程是动态的
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
- 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器,线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间->他们从同一堆中分配对象,可以访问相同的变量和对象。这就是的线程间通信更简单,高效。但多个线程操作共享的系统资源可能会就会带来安全的隐患
2、多线程的创建方式(一)
- 创建一个继承Thread类的子类
- 重写Thread类的run()方法--->将此线程执行的操作生命在run()中
- 创建Thread类的子类的对象
- 通过此对象调用start()方法
- 有两个作用
- 启动当前线程
- 调用当前线程的run()
- 有两个作用
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//问题一:
//如果直接调用对象里面的run()方法,则不会产生另外的进程,程序至始至终都只有main()方法这一个线程在执行
myThread.start();//在此之前只有主线程,在此之后有两个线程
//问题二:在启动一个线程,不可以让已经start()的线程去执行,否则会报异常
//这个时候需要再创建一个新的对象去执行start()
myThread.start();
}
}
小技巧:可以通过匿名子类的方式创建线程
public class ThreadTest {
public static void main(String[] args) {
//通过匿名子类的方式快速创建线程
new Thread(){
@Override
public void run() {
System.out.println("匿名内部内");
}
}.start();
·
}
}
3、Thread类的有关方法(1)
- void start(): 启动线程,并执行对象的run()方法
- run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中。线程在被调度时执行的操作
- String getName(): 返回线程的名称
- void setName(String name):设置该线程名称
//给线程命名
//1、通过setName()
HelloThread h1 = new HelloThread();
h1.setName("线程一")
//2、通过构造器
- static Thread currentThread(): 返回当前线程。在Thread子类中就 是this,通常用于主线程和Runnable实现类
4、Thread类的有关方法(2)
- static void yield():线程让步 ,释放当前CPU的执行权
- 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
- 若队列中没有同优先级的线程,忽略此方法
- join() :在线程a中调用线程b的join(),此时线程a就进入阻塞状态,知道线程b完全执行完以后,线程a才结束阻塞状态
- 低优先级的线程也可以获得执行
- static void sleep(long millis):(指定时间:毫秒)
- 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后 重排队。
- 抛出InterruptedException异常
- stop(): 已过时 ,强制线程生命期结束,不推荐使用
- boolean isAlive():返回boolean,判断线程是否还活着
5、线程的优先级
- 线程的优先级等级
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5-->默认的优先级
- 设计的方法
- getPriority():返回线程的优先级
- setPriority(int newPriority):改变线程的优先级
- 说明
- 线程创建时继承父线程的优先级
- 优先级知识获得调度的概率低,并非一定是在高优先级线程之后才被调度
6、多线程的创建方式(二)
多线程的创建方式二:实现Runnable接口
- 创建一个实现Runnable接口的类
- 实现类去实现Runnable中的抽象方法:run()
- 创建实现类的对象
- 将此脆响作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start()
package com.company;
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("i"+i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread p1 = new MyThread();
// new Thread(p1).start();
Thread thread1 = new Thread(p1);
thread1.start();
// 在创建一个线程执行循环过程,不用再去创建MyThread类的对象
Thread thread2 = new Thread(p1);
thread2.start();
for (int i = 0; i < 50; i++) {
System.out.println("j"+i);
}
}
}
7、比较创建线程的两种方式
开发中:优先选择 实现Runnable接口的方式
原因:
- 实现的方式没有类的单继承的局限性
- 实现的方式更适合来处理多个线程有共享数据的情况
联系:Thread类也实现了Runnable接口
相同点:两种方式都需要重写run()方法,将线程要执行的逻辑声明在run()方法中
8、线程的生命周期
- 新建:当一个Thread类或其子类的对象被声明创建时,新的线程对象处于新建状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已经具备了运行的条件,只是没有分配到CPU的资源
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被认为挂起或执行输入输出操作时,让CPU并临时终止自己的执行,进入阻塞状态
- 死亡:线程完成了它全部工作或线程被提前强制地中止或出现异常导致结束
9、线程的同步
- 问题的提出
- 多个线程执行的不确定性引起执行结果的无稳定
- 多个线程对账本的共享,会造成操作的不完整性,会破坏数据
例题:买票
class Windows implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+"买票,票号为:"+ticket);
ticket -- ;
}else{
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Windows p1 = new Windows();
Thread thread1 = new Thread(p1);
Thread thread2 = new Thread(p1);
Thread thread3 = new Thread(p1);
thread1.start();
thread2.start();
thread3.start();
}
}
- 问题:买票过程中,出现了重票,错票-->出现了线程安全问题
- 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
- 如果解决:当一个线程a在操作ticket的时候,其他线程不能参与进来,直到线程a的执行完
- 在java中,我们通过同步机制来解决线程安全问题
解决方式:
- 同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
操作共享数据的代码,即为需要被同步的代码
共享数据:多个线程共同操作的变量,比如:ticket
同步监视器,俗称:锁。任何一个类的对象都可以充当锁
要求:多个线程必须要公用同一把锁
synchronized (this){ if(ticket > 0){ System.out.println(Thread.currentThread().getName()+"买票,票号为:"+ticket); ticket -- ; }else{ break; } }
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
- 同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步
总结:
- 同步方法任然涉及到同步监视器,只是不需要我们声明
- 非静态的同步方法,同步监视器是this
- 静态的同步方法,同步监视器是本身类
- Lock方式
package com.Java线程.Java1;
import java.util.concurrent.locks.ReentrantLock;
/*
* 解决线程安全的方式三:Lock锁 jdk5.0新增
*
* 1、面试题 synchronized 与 Lock的异同
* 相同:都可以解决线程安全问题
* 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
* Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
*
* 优先使用顺序:
* Lock -> 同步代码块(已经进入了方法体,分配了相应的资源)->同步方法(在方法体之外)
*
* 2、面试题:如何解决线程安全问题,有几种解决方法
* 2种 synchronized方式和Lock方式
* 3种 同步代码块方式 同步方法方式 Lock方式
* */
class Window implements Runnable{
private int ticket = 100;
//1、实例化ReentrantLock对象
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
//2、调用lock()方法
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"票号:"+ticket);
ticket --;
}else
break;
}finally {
// 3、调用解锁的方法
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window window = new Window();
Thread t1 = new Thread(window);
Thread t2 = new Thread(window);
Thread t3 = new Thread(window);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
10、线程的通信
package com.Java线程.java2;
/*
* 线程通信
* 使用两个线程打印1-100.线程1,线程2 交替打印
*
* 涉及到的三个方法
* wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
* notify():一旦执行此方法,就会唤醒被wait()的一个线程。如果有多个线程被wait(),就唤醒优先级高的那个线程
* notifyAll():一旦执行此方法,就会唤醒所有被wait()的一个线程
*
* 说明:
* 1、wait() notify() notifyAll()三个方法必须使用在同步代码块或同步方法中
* 2、wait() notify() notifyAll()三个方法 的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现异常
* 3、wait() notify() notifyAll()三个方法时定义在java.lang.Object类中
* */
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while(true){
synchronized (this){
notify();//唤醒等待的线程
if(number<100){
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
try {
//使得调用wait()方法的线程进入阻塞
wait();//线程wait()时会释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}else
break;
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
11、线程的创建方式(jdk5.0新增)
11.1、新增方式一:实现Callable接口
与使用Runnable相比,Callable功能更强大些
- 相比run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutrueTask类,比如获取返回结果
package com.Java线程.java2; /* * 创建线程的方式三:实现Callable接口----jdk5.0新增 * * 如何理解Callable接口的方式创建多线程比实现Runnable接口创建多线程要强大 * 1、call()可以有返回值 * 2、call()可以抛出异常,被外面的操作捕获,获取异常的信息 * 3、call方法支持泛型 * */ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; //1、创建一个实现Callable接口的实现类 class NumThread implements Callable{ //2、实现call方法,将此线程需要执行的操作声明在call()方法中 @Override public Object call() throws Exception { int sum = 0; for (int i = 1; i < 100; i++) { if(i % 2 ==0){ System.out.println(i); sum += i; } } return sum; } } public class ThreadNew { public static void main(String[] args) { //3、创建Callable接口实现类的对象 NumThread numThread = new NumThread(); //4、将此Callable接口的实现类的对象作为参数传递到FutureTask的对象 FutureTask futureTask = new FutureTask(numThread);//FutureTask实现了Runnable接口 //5、将FutureTask类的对象作为参数传递到类的构造器中,创建Thread对象,并start() new Thread(futureTask).start(); try { //6、获取Callable中call方法的返回值 //get()方法的返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值 Object sum = futureTask.get(); System.out.println(sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
11.2、新增方式二:使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放会池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具
好处:提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
package com.Java线程.java2; /* * 创建线程的方式四: 使用线程池 * */ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class NumberThread implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0) System.out.println(Thread.currentThread().getName()+i); } } } public class ThreadPool { public static void main(String[] args) { //1、提供指定线程数量的线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); //2、执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象 executorService.execute(new NumberThread());//适用于使用Runnable //3、关闭连接池 executorService.shutdown();//关闭线程池 // executorService.submit();//适合适用于Callable } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现