线程

线程

此系列笔记来源于

BiliBili韩顺平老师的Java基础课


进程

1、进程是指运行中的程序,操作系统会为其分配内存空间。

2、进程是程序的一次执行过程,或是正在运行的一个程序,是动态过程:有它自身的产生、存在和消亡的过程

线程

1、线程由进程创建,是进程的一个实体

2、一个进程可以拥有多个线程

1、单线程:

同一个时刻,只允许执行一个线程

2、多线程:

同一个时刻,可以执行多个线程

3、并发:

同一个时刻,多个任务交替执行,造成一种 ”貌似同时“ 的错觉。简单地说,单核cpu实现的多任务就是并发

4、并行:

同一个时刻,多个任务同时执行。多核cpu可以实现并行

//返回到Java虚拟机的可用的处理器数量
Runtime runtime = Runtime.getRuntime();        
System.out.print(runtime.availableProcessors());

线程基本使用

示意图

image-20220417115045882

方式一:继承 Thread类

方式二:实现 Runnable接口

继承 Thread类

说明

1、当一个类继承了Thread类,就可以作为一个线程来使用

2、我们会重写 run 方法,写上自己的业务代码

3、run 是 Thread类实现了 Runnable接口的 run方法

//基本使用
public class Yra {
    public static void main(String[] args) {
        wuhu wh = new wuhu();
        wh.start();  //启动线程
//      wh.run(); //run方法只是一个普通方法,并未真正的开启一个新的线程

    }
}

class wuhu extends Thread {
    @Override
    //java中实现真正的多线程是start中的start0()方法,run()方法只是一个普通的方法
    public void run() { //重写run方法
        int cnt = 0;
        while (true) {
            System.out.println(++ cnt);
            //异常捕获 快捷键:ctrl + alt + t
            try {
                Thread.sleep(1000); //休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (cnt == 3) {
                System.out.println("结束");
                break;
            }
        }
    }
}

PS:

1、可以用 JConsole 监控线程执行情况

2、java中实现真正的多线程是start中的start0()方法,run()方法只是一个普通的方法

3、start()方法调用 start0()方法后,该线程不一定会立马执行,只是将线程变成了可运行状态,具体什么时候执行,取决于CPU,由CPU统一调度

4、start0() 是本地方法,由JVM调用,底层是C/C++实现

实现 Runnable接口

说明

1、java是单继承的,当一个类可能已经继承了某个弗雷,这时再用继承Thread类方法来创建线程显然不可能

public class Yra {
    public static void main(String[] args) {
        wuhu a = new wuhu();
        //新建一个Thread对象,把实现了Runnable接口的a放入Thread,a不能直接调用start()方法
        Thread thread = new Thread(a);
        thread.start();
    }
}

class wuhu implements Runnable {
    @Override
    public void run() { //重写run方法
        int cnt = 0;
        while (true) {
            System.out.println(++ cnt);
            //异常捕获 快捷键:ctrl + alt + t
            try {
                Thread.sleep(1000); //休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (cnt == 3) {
                System.out.println("结束");
                break;
            }
        }
    }
}

PS:

1、这里底层用到了【静态 】代理模式(设计模式)

image-20220418132407719

线程终止

在线程类里面加个控制线程内容是否执行的变量和set该变量的方法,main方法中调用该方法来终止线程

如loop变量 和 setLoop方法,在main方法中调用setLoop方法来改变loop的值,以终止while循环

线程常用方法

第一组:

1、setName //设置线程名称

2、getName //返回该线程名称

3、start //使该线程开始执行,JVM底层调用该线程的 start0方法

4、run //调用线程对象的 run方法

5、setPriority //更改线程的优先级

6、getPriority //获取线程的优先级

7、sleep //在指定的毫秒数内让当前正在执行的线程休眠

8、interrupt //中断线程

PS:

1、start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程

2、线程优先级:

pulic static final int MAX_PRIORITY = 10;

pulic static final int MIN_PRIORITY = 1;

pulic static final int NORM_PRIORITY = 5;

3、interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠的线程(唤醒)

4、sleep:线程的静态方法,使当前线程休眠

try {
    Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
	//当获取到中断异常后,即调用了该对象的interrupt方法,就为直接中断休眠,可以加上业务逻辑代码
}

第二组:

1、yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功

2、join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务

用户线程和守护线程

1、用户线程:也叫工作线程,当线程的任务执行完或通知方式结束

2、 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。常见的守护线程:垃圾回收机制

xxx.setDaemon(true); //将子线程设为守护线程

线程的生命周期

1、可以用Thread.State查看线程的状态

2、线程可以处于一下状态之一

image-20220418193323236

3、线程的状态转换图

image-20220418193505589

线程同步机制 Synchronized

介绍

1、在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。

2、也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。

同步具体方法

//1、同步代码块
synchronized(对象) { //得到该对象的锁,才能操作同步代码
    //需要被同步代码
}

//2、synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m(String m) {
    //需要被同步的代码
}

如买票,如何多个线程卖票最后剩余的票不会小于0呢?

可以给sell方法加个synchronized(保证每次只有一个线程买票),然后在sell方法开始判断票的数量。

public class Yra {
    public static void main(String[] args) {

        Shop a = new Shop();
        new Thread(a).start();
        new Thread(a).start();
        new Thread(a).start();
    }
}

class Shop implements Runnable {
    private int rest = 10;
    private static boolean loop = true;
    public synchronized void sell() {
        if (rest <= 0) {
            loop = false;
            return;

        } else {
            System.out.println(Thread.currentThread().getName() + "还剩" + --rest + "张票");
        }
    }

    @Override
    public void run() {
        while (loop) {
            sell();
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

同步原理

image-20220418203255015

各线程争夺对象上的锁,抢到锁后执行相关代码,执行完后把锁还回去。如果第一次t1先抢到,t1还回去后仍然会去抢锁

互斥锁

介绍

1、java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性

2、每个对象都对应于一个可称为“互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象

3、关键字synchronized 来与对象的互斥锁联系。

4、同步的局限性:导致程序的执行效率要降低

5、同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个独享)

public synchronized void sell() {} 是一个同步方法,这时锁在 this对象上

6、同步方法(静态的)的锁为当前类本身

7、也可以在代码块上写 synchronized,同步代码块,互斥锁还是在 this对象上

public synchronized void sell() {
        synchronized (this) { //如果是静态方法,那么this需要换成类名.class
            if (rest <= 0) {
                loop = false;
                return;

            } else {
                System.out.println(Thread.currentThread().getName() + "还剩" + --rest + "张票");
            }
        }
    }

PS:

1、同步方法如果没有使用static修饰:默认锁对象为this

2、如果同步方法使用static修饰,默认锁对象:当前类.class

3、实现的落地步骤:

(1)需要先分析上锁的代码

(2)选择同步代码块或同步方法

(3)要求多个线程的锁对象为同一个即可

线程的死锁

介绍

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程时一定要避免死锁的发生

例:

if (flag) {
    synchronized(o1) {
		synchronized(o2) {
        }
    }
} else {
   synchronized(o2) {
		synchronized(o1) {
        }
    } 
}

释放锁

下面操作会释放锁

1、当前线程的同步方法、代码块执行结束

2、当前在同步代码块、方法中遇到break、return

3、当前线程在同步代码块、方法中出现了未处理的 Error 或 Exception,导致异常结束

4、当前线程在同步代码块、方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁

下面操作不会释放锁

1、线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁

2、线程执行同步代码块或同步方法时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁

提示:应景连通管避免使用 suspend() 和 resume() 来控制线程,方法不再推荐使用;——

定义了一个Shot线程,给每个坦克加个射击方法,调用这个方法就可以触发一个线程来发射子弹,给我们的面板也添加一个线程,用来不断刷新重绘使得导弹能有移动的效果

posted @   Yra  阅读(108)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
点击右上角即可分享
微信分享提示