多线程Java多线程学习笔记 | 多线程基础知识
Posted Fxtack
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程Java多线程学习笔记 | 多线程基础知识相关的知识,希望对你有一定的参考价值。
Java多线程学习笔记 | 多线程基础知识
文章目录
一. 线程与进程
-
进程是一个应用程序,线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。
-
在 java 程序运行时,先启动 JVM 进程,然后JVM进程将启动一个线程调用 main 方法。同时 JVM 还会启动一个GC线程。因此一个 Java 程序至少存在两个线程并发。
-
进程间内存独立不共享,而线程间堆内存与方法区内存共享,栈不共享,一个线程一个栈
-
主线程(执行main方法的线程)结束后,Java 程序进程可能任然在运行。因为主线程结束,其他线程依然可以继续运行。
二. Java中创建线程
1.编写继承 Thread 的类,重写 run 方法
package org.example;
public class ThreadTest
public static void main(String args[])
MyThread myThread = new MyThread();
//start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间
//这段代码任务完成后,start()方法瞬间就结束了
//线程启动成功,自动调用run()方法,并且run()方法在分支栈底部
myThread.start();
for(int i = 0 ; i < 100 ; i++)
//输出用于测试,主线程
System.out.println("The main thread:"+i);
class MyThread extends Thread
@Override
void run()
for(int i = 0 ; i < 100 ; i++)
//输出用于测试,新创建的线程
System.out.println("The second thread:"+i);
- 注意
start()
方法的作用是分配新的分支栈并启动run()
方法(由JVM完成)。若直接在主方法中调用run()
方法只会执行在主线程的栈中执行run()
方法 start()
方法只有瞬间结束或者说不持续运行,才能执行start()
方法以下的主线程中的代码。
2. 编写实现 Runnable 的类,重写 run 方法
package org.example;
public class ThreadTest
public static void main(String args[])
//创建可运行对象
MyRunnable myRunnable = new MyRunnable();
//将可运行对象传入线程类,使其封装为一个线程,从而执行线程
Thread myThread = new Thread(myRunnable);
myThread.start();
for(int i = 0 ; i < 100 ; i++)
System.out.println("The main thread:" + i);
//定义一个实现Runnable接口的类重写run()方法
class MyRunnable implements Runnable
@Override
void run()
for(int i = 0 ; i < 100 ; i++)
System.out.println("The second thread:" + i);
- 若使用接口的线程创建方式,则这个类可以继承其他类。
3. 其他方式的创建
-
匿名内部类创建线程
package org.example; public class ThreadTest public static void main(String args[]) //此处是使用实现了Runnable接口的匿名内部类对象创建了线程对象 Thread myThread = new Thread(new Runnable public void run() for(int i = 0 ; i < 100 ; i++) System.out.println("The thread:" + i); );
-
Lambda 表达式创建线程(Java8)
package org.example; public class ThreadTest public static void main(String args[]) //Thread接口只有一个待实现的方法,因此可视为函数接口来使用Lambda表达式 Thread myThread = new Thread(()-> for(int i = 0 ; i < 100 ; i++) System.out.println("The thread:" + i); );
三. 线程的生命周期
- 由 new 出来的线程对象,进入新建状态
- 调用线程对象的
start()
方法后,进入就绪状态,又称为可运行状态。此时的线程具有抢夺CPU时间片的权利,即抢夺执行权。 - 当一个处于就绪状态的线程抢夺到CPU时间片后,开始执行
run()
方法,run()
方法的执行标志着线程进入运行状态 - 当前的CPU时间片耗尽之后,线程重新进入就绪状态,抢夺CPU时间片。直到再次获得CPU时间片,则再次进入运行状态,接着
run()
方法上次运行的状态继续运行。就绪状态与运行状态的切换由JVM调度 - 若遇到阻塞事件,发生阻塞事件的线程进入阻塞状态。阻塞状态的线程会放弃之前占有的CPU时间片
- 若线程的阻塞事件结束,则该现场重新进入就绪状态,抢夺时间片
- 当
run()
方法运行结束,线程进入死亡状态
四. 获取线程对象
1. 线程名称
-
线程的默认名称
支线线程的默认名字规律为
Thread-0
Thread-1
…
Thread-n
按照创建的顺序进行命名
主线程的名字为:main
-
获取线程的名称
thread.getName()
-
设置线程的名称
thread.setName()
2. 获取线程对象
-
获取当前线程
通过 Thread 类中的静态方法
currentThread()
获取当前线程。Thread currentThread = Thread.currentThread();
该方法在什么线程中调用,得到的就是什么线程的对象。如在主线程中调用,获取的对象则为主线程对象。
五. 线程操作
1. 线程睡眠,阻塞状态
-
sleep方法
通过 Thread 中的静态方法
sleep()
来使线程进入休眠(阻塞)状态。sleep方法的参数值为休眠的毫秒数。在什么线程中调用该方法,什么线程就会进入休眠状态。try //静态方法 Thread.sleep(1000 * 5); catch(InterruptedException e) e.printStackTrace();
-
sleep阻塞唤醒
对线程对象进行
interrupt()
唤醒sleep的线程thread.interrupt();
在用
Interrupt()
方法唤醒后,在唤醒线程的sleep()
方法处将会出现 InterruptedException 异常
2. 线程终止
-
使用
stop()
强行终止线程thread.stop()
该方法是强行终止线程。该方法已过时,使用该方法会丢失数据。
-
使用传递值控制线程程序化的终止
在线程内创建一个变量,线程的执行会轮询该变量,若变量的值改变到某状态时,则程序化的结束run方法,从而实现程序化的结束线程。
package org.example; class MyRunnable implements Runnable boolean isRun = true; @Override public void run() for(int i = 0 ; i < 10 ; i++) //如果在该线程运行的某时间段获取的isRun为false,则运行else语句块 if(isRun) System.out.println("The thread is running" + i); try Thread.sleep(1000 * 5); catch (InterruptedException e) e.printStackTrace(); else //在else语句块内可对该线程中的资源进行回收保存,从而避免数据的丢失 //run方法结束,线程进入死亡状态 return;
六. 线程调度模型
1. 常见的线程调度模型
-
抢占式调度模型
Java 的多线程采用的就是抢占式的调度模型,按照优先级的不同,优先级高的线程更有可能抢到CPU 时间片
-
均分式调度模型
平均分配 CPU 时间片。每个线程占有的 CPU 时间长度一样,平均分配
2. Java中的线程调度操作
-
Java线程的优先级
-
默认优先级,最低优先级,最高优先级
Java中的最高线程优先级为10,最低为1,默认为5
-
获取一个线程的优先级
thread.getPriority()
获取thread线程的优先级 -
设置一个线程的优先级
thread.setPriority(10)
设置线程thread的优先级为10
-
-
线程让步
static void yield()
让步方法的方法签名
调用方法与 sleep()
方法相同
Thread.yield()
让步方法的调用
暂停当前正在执行的线程对象(处于运行状态的线程),并执行其他线程。yield()
方法不会让线程进入阻塞状态而是让线程进入就绪状态。该线程的让步只是一瞬间,很快该线程又有机会抢夺到 CPU 时间片重新进入运行状态。
-
线程合并
thread.join()
线程合并的调用(在有别于 thread 线程的其他线程中调用)若在主线程中运行该代码,将会使得 thread 线程与主线程线程合并,主线程将会等待 thread 线程执行完成再继续执行该代码以下的主线程中剩下的任务。相当于单线程
该方法并不会消除 thread 线程栈的存在,而是等待 thread 线程运行结束
七. 多线程的安全
满足三个条件时,会出现多线程的数据安全问题:
- 多线程并发
- 存在多线程共享的数据
- 共享数据会被修改
在 Java 的三大变量(实例变量存储于堆中,静态变量存储于方法区,局部变量存储于栈中),局部变量不存在线程安全问题。因为局部变量在栈中不共享。
1. 线程同步
线程排队执行,此时多线程不能并发,只能排队等候执行。这种机制被称为线程同步机制
2. 同步编程模型,异步编程模型
-
异步编程模型
线程t1月t2,各自执行,互不干扰,不等候,这种编程模型被称为异步编程模型。即多线程并发,效率高
-
同步编程模型
线程t1与t2,在t1执行的时候,必须等待t2线程执行结束,或者说在t2执行的时候,必须等待t1的执行结束。即线程排队,效率较低。所谓同步就是排队。
3. Java中实现线程同步
使用关键字 synchronized 创建 synchronized 语句块来实现多线程操作数据时的线程同步
//synchronized语句块
synchronized(Object)
//操作
-
小括号中填入的是线程共享的数据或对象。若要对t1与t2操作的数据或对象进行线程同步则填入的是t1与t2共同操作的数据对象。若是n个线程都进行同步,则填入n个线程将会共同访问的对象,进行线程同步。
-
实现原理
- 假设 t1 和 t2 并发,开始执行,直到 synchronized 语句块必然有先后。
- 假设 t1 先执行,遇到了 synchronized,这时候自动找共享对象的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在执行过程中一直都是占有这把对象锁。直到同步代码块结束,这把锁才释放。
- 假设 t1 占有这把锁,此时 t2 也遇到了 synchronized 关键字,也会去占有后面共享对象的这把锁,结果这把锁被 t1 占有,t2 只能等待 t1 执行完并释放该锁。直到锁释放,t2 得到这把锁,然后 t2 占有该锁并进入同步代码块执行程序
-
锁池 lockpool
运行当中的线程遇到了 synchronized 关键字,会放弃 CPU 时间片,进入 lockpool 中寻找共享对象的对象锁。若没有找到共享对象的对象锁,则在锁池中等待,直到找到共享对象锁(例如其他线程释放了共享对象的对象锁)。若找到了共享对象锁则进入就绪状态,继续抢夺 CPU 时间片