Java学习笔记之多线程
1 进程与线程
-
程序:指令和数据的有序集合,本身无任何运行的含义,是静态的概念
-
进程:执行程序的一次执行过程,是一个动态的概念,时系统资源分配的单位
-
线程:CPU 调度和执行的单位,一个进程中可以包含多个线程,其本身就是独立的执行路径
- 在一个进程中,若开辟了多个线程,则运行顺序是由 CPU 中的调度器安排的,人为无法干预
- 对统一资源进行操作时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,内存控制不当会造成数据不一致
2 线程的创建
2.1 创建方式
1 继承 Thread 类
public class ThreadInfo extends Thread{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("这是多线程" + i);
}
}
public static void main(String[] args) {
ThreadInfo threadInfo = new ThreadInfo();
threadInfo.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主进程" + i);
}
}
}
执行结果
结论:线程开启不一定立即执行,由 CPU 进行调度
2 实现 Runnable 接口
public class ThreadInfo2 implements Runnable{
public static void main(String[] args) {
ThreadInfo threadInfo = new ThreadInfo();
//推荐使用,避免 Java 单继承的局限性
new Thread(threadInfo).start();
for (int i = 0; i < 1000; i++) {
System.out.println("主进程" + i);
}
}
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("多线程" + i);
}
}
}
多线程同时使用同一个资源的情况下,线程不安全,会导致数据紊乱
以抢车票为例
public class ThreadInfo3 implements Runnable{
private int ticketNum = 10;
@Override
public void run() {
while (ticketNum > 0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNum-- + "张票");
}
}
public static void main(String[] args) {
ThreadInfo3 ticket = new ThreadInfo3();
new Thread(ticket, "A").start();
new Thread(ticket, "B").start();
new Thread(ticket, "C").start();
new Thread(ticket, "D").start();
}
}
可以看到,B 和 D 同时抢到了同一张票,发生了数据紊乱
龟兔赛跑例子
public class Race implements Runnable{
private static String winner;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
boolean flag = isGameOver(i);
if (flag) break;
System.out.println(Thread.currentThread().getName() + "到了第" + i + "米");
}
}
private boolean isGameOver(int steps){
if (winner != null) return true;
if (steps == 99){
winner = Thread.currentThread().getName();
System.out.println("winner is " + winner);
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "兔子").start();
new Thread(race, "乌龟").start();
}
}
3 实现 Callable 接口
public class ThreadInfo4 implements Callable<Boolean> {
@Override
public Boolean call(){
for (int i = 0; i < 5; i++) {
System.out.println("多线程" + i);
}
return true;
}
public static void main(String[] args) throws Exception {
ThreadInfo4 info1 = new ThreadInfo4();
ThreadInfo4 info2 = new ThreadInfo4();
ThreadInfo4 info3 = new ThreadInfo4();
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> res1 = ser.submit(info1);
Future<Boolean> res2 = ser.submit(info2);
Future<Boolean> res3 = ser.submit(info3);
//获取结果
boolean r1 = res1.get();
boolean r2 = res2.get();
boolean r3 = res3.get();
//关闭服务
ser.shutdown();
}
}
2 静态代理
- 代理对象和真是对象都要实现同一个接口
- 代理对象要代理真实角色
- 代理对象可以实现很多真实对象无法实现的功能
- 使用代理后,真实对象可以专注于自己的事
public class ProxyStatic {
public static void main(String[] args) {
new MarryCompany(new You()).happyMarry();
}
}
interface Marry{
void happyMarry();
}
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("今天结婚了,很开心");
}
}
//婚庆公司就是一个代理对象
class MarryCompany implements Marry {
private final Marry target;
public MarryCompany(Marry target) {
this.target = target;
}
@Override
public void happyMarry() {
before();
this.target.happyMarry(); //真实对象
after();
}
public void before(){
System.out.println("结婚之前,布置现场");
}
public void after(){
System.out.println("结婚之后,算流水");
}
}
3 Lambda 表达式
-
为什么使用 Lambda 表达式
- 为了避免匿名内部类定义过多
- 代码看起来更简洁
- 去掉了无意义代码,只留下核心
-
函数式接口定义
- 任何接口,如果只包含唯一一个抽象方法,那么就是一个函数式接口
- 对于函数式接口,就可以通过 lambda 表达式来创建该接口的对象
(params) -> expression[表达式] (params) -> statement[语句] (params) -> {statements}
问:平时实现函数式接口,怎么实现?
答:外部类 -> 静态内部类 -> 局部内部类 -> 匿名内部类(依次简化)
- 使用外部类实现
public class LambdaInfo {
public static void main(String[] args) {
MyLike myLike = new MyLike();
myLike.lambda();
}
}
interface ILike {
void lambda();
}
class MyLike implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda");
}
}
- 简化,使用静态内部类实现
public class LambdaInfo {
public static void main(String[] args) {
MyLike myLike = new MyLike();
myLike.lambda();
}
static class MyLike implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda");
}
}
}
interface ILike {
void lambda();
}
- 再简化,使用局部内部类实现
public class LambdaInfo {
public static void main(String[] args) {
class MyLike implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda");
}
}
MyLike myLike = new MyLike();
myLike.lambda();
}
}
interface ILike {
void lambda();
}
- 再简化,使用匿名内部类实现
public class LambdaInfo {
public static void main(String[] args) {
ILike like = new ILike() {
@Override
public void lambda() {
System.out.println("i like lambda");
}
};
like.lambda();
}
}
interface ILike {
void lambda();
}
- 更加简化,使用 lambda 表达式
public class LambdaInfo {
public static void main(String[] args) {
ILike like = () -> {
System.out.println("i like lambda");
};
like.lambda();
}
}
interface ILike {
void lambda();
}
- 当 lambda 表达式内部是多行代码时,大括号不能去掉,要用大括号包裹代码块
public class LambdaInfo {
public static void main(String[] args) {
ILike like = (a, b, c) -> {
System.out.println("i like lambda" + a);
System.out.println("i like lambda" + b + c);
};
like.lambda(1, true, "");
}
}
interface ILike {
void lambda(int a, boolean b, String c);
}
- 当 lambda 表达式内部只有一行代码时,可以省略大括号
public class LambdaInfo {
public static void main(String[] args) {
ILike like = (a, b, c) -> System.out.println("i like lambda" + a);
like.lambda(1, true, "");
}
}
interface ILike {
void lambda(int a, boolean b, String c);
}
- lambda 表达式的参数类型,要么都要留,要么都省略
public class LambdaInfo {
public static void main(String[] args) {
//都要留
ILike like1 = (int a, boolean b, String c) -> System.out.println("i like lambda" + a + b + c);
//都不留
ILike like = (a, b, c) -> System.out.println("i like lambda" + a + b + c);
like1.lambda(1, true, "");
like2.lambda(1, true, "");
}
}
interface ILike {
void lambda(int a, boolean b, String c);
}
4 线程状态
4.1 五大状态
-
创建状态:
Thread t = new Thread();
一旦被创建,就进入了创建状态 -
就绪状态:当调用
start()
方法时,线程立即进入就绪状态,但不意味着立即调度执行 -
运行状态:CPU 调用该线程,则进入运行状态
-
阻塞状态:运行途中遇到
sleep()、wait()
等方法时,线程进入阻塞状态,当阻塞事件结束后,CPU 重新调用该线程,再次进入运行状态 -
死亡状态:线程中断或者结束,就进入了死亡状态,无法再次启动
4.2 停止线程
- 不推荐使用
jdk
提供的stop()
、destory()
,已经废弃 - 推荐方式:设置一个标志位来终止线程
public class StopThread implements Runnable{
private boolean flag = true;
@Override
public void run() {
int count = 0;
while (flag){
System.out.println("run...Thread" + count++);
}
}
public void myStop(){
this.flag = false;
}
public static void main(String[] args) {
StopThread stopThread = new StopThread();
new Thread(stopThread).start();
for (int i = 0; i < 300; i++) {
System.out.println("main...Thread" + i);
if (i == 168){
stopThread.myStop();
System.out.println("线程已经终止");
}
}
}
}
4.3 线程休眠
sleep(int times)
指定当前线程阻塞的毫秒数sleep
存在异常Interruptedexception
sleep
时间达到后线程进入就绪状态sleep
可以模拟网络延时,倒计时等- 每一个对象都有一个锁,
sleep
不会释放锁
public class SleepThread implements Runnable{
@Override
public void run() {
int num = 10;
Date startTime = new Date(System.currentTimeMillis());
while (num-- > -1){
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
startTime = new Date(System.currentTimeMillis());
}
}
public static void main(String[] args) {
new SleepThread().run();
}
}
4.4 线程礼让
- 礼让线程,让当前正在执行的线程暂停但不阻塞
- 将线程从运行状态转为就绪状态
- 让 CPU 重新调度,礼让不一定成功
public class YieldThread{
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield, "a").start();
new Thread(myYield, "b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "线程停止执行");
}
}
4.5 线程强制执行
Join
合并线程,等待此线程执行完,其他线程才可继续执行,该线程执行时,其他线程阻塞- 类似于插队(尽量少使用,会让线程阻塞)
public class JoinThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程 vip 插队了" + i);
}
}
public static void main(String[] args) throws InterruptedException {
JoinThread joinThread = new JoinThread();
Thread thread = new Thread(joinThread);
thread.start();
for (int i = 0; i < 10; i++) {
if (i == 3) {
thread.join();
}
System.out.println("主线程" + i);
}
}
}
4.6 查看线程状态
NEW
:尚未启动的线程处于此状态RUNNABLE
:在 Java 虚拟机中执行的线程处于此状态BLOCKED
:被阻塞等待监视器锁定的线程处于此状态WAITING
:正在等待另一个线程执行特定动作的线程处于此状态TIMED_WAITING
:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态TERMINATED
:已退出的线程处于此状态
public class StatusThread{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 2; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread Finished...");
});
Thread.State state = thread.getState();
System.out.println(state);
thread.start();
state = thread.getState();
System.out.println(state);
while (state != Thread.State.TERMINATED){
Thread.sleep(100);
state = thread.getState();
System.out.println(state);
}
}
}
4.7 线程优先级
- Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
- 线程的优先级用数字表示,范围从 1~10
- Thread. MIN_PRIORITY =1
- Thread. MAX_PRIORITY= 10
- Thread. NORM_PRIORITY =5
- 使用
getpriority()
、setpriority(int num)
方式改变或获取优先级 - 线程必须先设置优先级,然后再启动
- 线程优先级低,并不意味着获得调度的概率低,并不是优先级低就不会被调用
public class PriorityThread {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "->" + Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
t1.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.setPriority(Thread.NORM_PRIORITY);
t2.start();
t3.setPriority(7);
t3.start();
t4.setPriority(3);
t4.start();
t5.setPriority(Thread.MAX_PRIORITY);
t5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "->" + Thread.currentThread().getPriority());
}
}
4.8 守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程(
main
线程等)执行完毕 - 虚拟机不用等待守护线程(
gc
垃圾回收线程等)执行完毕
public class DaemonThread {
public static void main(String[] args) {
God god = new God();
My my = new My();
Thread thread = new Thread(god);
thread.setDaemon(true);
thread.start();
new Thread(my).start();
}
}
class God implements Runnable {
@Override
public void run() {
while (true) System.out.println("荒天帝守护着你");
}
}
class My implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("You Living -> " + i );
}
System.out.println("You Dead!");
}
}
5 线程同步
-
并发:多个线程同时操作同一个资源
-
为了保证数据在方法中被访可时的正确性,在访问时加入锁机制
synchronized
,当一个线程获得对象的排它锁,独占资源,其他线程必须等待使用后释放锁即可。存在以下问题:- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竟争下,加锁,释放锁会导致比较多的上下文切換和调度延时,引起性能冋题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
三大不安全案例
- 买票
public class UnsafeTickets {
public static void main(String[] args) {
Tickets tickets = new Tickets();
new Thread(tickets, "你").start();
new Thread(tickets, "我").start();
new Thread(tickets, "他").start();
}
}
class Tickets implements Runnable {
int ticketNum = 10;
boolean flag = true;
@Override
public void run() {
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void buy() throws InterruptedException {
if (ticketNum < 0){
flag = false;
return;
}
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张票" );
}
}
- 银行取钱
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100, "存款");
new Bank(account, 50, "You").start();
new Bank(account, 100, "Girl").start();
}
}
class Account{
int money;
String name;
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
class Bank extends Thread {
Account account;
int drawingMoney;
int nowMoney;
public Bank(Account account, int drawingMoney, String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run(){
if (account.money - drawingMoney < 0){
System.out.println(Thread.currentThread().getName() + "钱不够");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -= drawingMoney;
nowMoney += drawingMoney;
System.out.println(account.name + "余额为:" + account.money);
System.out.println(this.getName() + "手里的钱:" + nowMoney);
}
}
- ArrayList
public class UnsafeList{
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!