进程与多线程(入门)

news2024/9/24 11:29:27

什么是线程

要了解什么是线程,得先知道什么是程序
程序:为完成特定任务,用某种语言编写的一组指令的集合。
例如,QQ,Steam,亦或者java写的helloword。
这些都是程序

了解了程序,还得清楚什么是进程
进程:就是指运行中的程序,比如双击QQ,那么就会启动了一个进程,操作系统就会为其分配该进程所需的内存空间,当又点开了一个程序,比如谷歌浏览器,那么就会又启动一个进程。
进程是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身产生,运行中,消亡的过程。双击启动QQ就是启动了一个进程,打开任务管理器,选中结束任务,就是进程消亡了

了解了进程,再来了解下线程
线程:线程是由进程创建的,是进程的一个实体。一个进程可以有多个线程
例如:迅雷就是一个程序,打开迅雷,就是启动一个进程,而你用迅雷下载一个资源,比如下载<流浪地球>这部电影,那么这个下载任务就是一个线程,你还可以同时下载多个电影,创建多个下载任务同时下载,这就是多线程。

单线程和多线程的概念

单线程:同一时刻,只运行执行一个线程。例如有些下载器,只运行同时执行一个下载任务,必须排队下载,(Steam的下载)
多线程:同一时刻,可以执行多个线程,比如微信可以同时打开多个聊天窗口,迅雷可以同时下载多个资源

并发与并行的概念

并发:同一时刻,多个任务交替执行,但是因为切换的速度很快,造成一种是同时执行的错觉。
例如单核CPU实现的多任务就是并发。并发就是一个人操作2个键盘,但是因为切换的速度很快,看起来像是同时操作的

并行:同一时刻,多个任务同时执行,多核CPU可以实现并行
例如:两个人操作两个键盘,他们是同时打字的

但是在电脑中有时候是并发和并行同时存在的,例如两个人在同时打字,但是其中一个人打到一半去操作另一个键盘了,敲了一会又回来了

在这里插入图片描述

线程的基本使用

在Java中创建线程有两个办法
1.实现Runnable接口,重写run方法
2.继承Thread类,重写run方法

继承Thread类创建线程

注意Thread类其实也是实现了Runnable接口,Thread重写的run方法也是Runnable接口的

在这里插入图片描述

案例演示使用线程

有以下题目,使用线程完成
有一个Cat类,要求让Cat的对象,每过一秒打印输出一个“喵喵”

思路:定义Cat类,继承Thread类,重写run方法,将逻辑写在run方法里。Thread有一个sleep方法可以睡眠
然后使用Thread类继承过来的start方法,会自动触发run方法

public class test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
    }
}
class Cat extends Thread{
    @Override
    public void run() {
        while (true) {
            System.out.println("喵喵");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

追加要求:当输出8次喵喵后,停止运行
思路,创建一个int变量,每输出一次就+1,当等于8时就break退出

public class test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
    }
}
class Cat extends Thread{
    @Override
    public void run() {
        int nums = 0;
        while (true) {
            System.out.println("喵喵"+(++nums));
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (nums == 8){
                break;
            }
        }
    }
}

注意,main方法本身也是一个线程,相当于主线程,上面就是在主线程里又开了一个线程,但是并不会造成主线程的堵塞,比如cat.start();下面还有代码的话,不会等cat.start();执行完再回来执行,而是启动了Thread线程后并发或者并行的执行。

举例说明:

public class test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main"+i);
        }
    }
}
class Cat extends Thread{
    @Override
    public void run() {
        int nums = 0;
        while (true) {
            System.out.println("喵喵"+(++nums));
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (nums == 8){
                break;
            }
        }
    }
}

在这里插入图片描述
可以看到是交替执行的,单核的CPU就是并发,多核的就是并行

使用JConsole监控线程执行情况,且画出示意图

在执行的时候,打开Terminal输入JConsole,可以打开监控面板,查看线程执行情况。

注意点:当main主线程执行完毕后,这个进程并不会立刻退出,而是等所有线程都执行完毕后,才会退出
就像一个仓库管理员让他手下去关东边的灯,他自己关西边的灯,得等两个人都关了后,才离开。而不是一个人关掉后马上离开
在这里插入图片描述

在这里插入图片描述

start0方法

为什么在上面不在main方法直接调用run方法来完成呢?

这是因为run方法只是一个普通的方法,并不是创建了一个线程。如果直接在main方法中调用run方法的话,可以在JConsole监控中只能看到main线程,没有Thread线程。
而在main线程中就会造创堵塞,调用run方法下面的语句,必须得等run方法执行完毕后,才会执行。这就是没有达到多线程的效果,只是串行,没有并发或者并行。

只有使用start方法才能真正的去创建线程。
start方法底层是调用start0方法(本地方法),由start0方法调用run方法。而start0方法是JVM调动的。
当start0方法调用run方法,并不是马上执行的,而是取决于CPU。CPU会根据内存资源IO资源等等,判断让不让run方法现在执行

实现Runnable接口创建线程

上面说了继承Thread类创建线程,但是由于Java只能单继承的特性,在实际的开发中可能没办法再去继承Thread类。这时旧需要实现Runnable接口来创建线程。

但是因为如果是继承Runnable接口的话,由于Runnable接口中只有一个run方法可以重写,没有start方法来触发run方法,无法创建线程。
为了解决这个问题就需要将创建线程的对象放入倒Thread的实例中,然后直接使用Thread对象调用start方法。因为多态绑定的特性,底层还是跑的实现Runnable接口的类实例。
代码演示:
例如每隔1秒输出一个“你好”

public class test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Thread thread = new Thread(dog);
        thread.start();
    }
}
class Dog implements Runnable{
    @Override
    public void run() {
        int nums = 0;
        while (true){
            System.out.println("你好"+(++nums)+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (nums == 10 ){
                break;
            }

        }
    }
}

下面简单了解一下,Thread是怎么将dog的run方法调用起来的
当dog传入Thread时,Thread的底层使用了线程代理模式

线程代理模式,简单模拟

class ThreadProxy implements Runnable{
    private  Runnable target;

    public ThreadProxy(Runnable target) {
        this.target = target;//将传入的对象赋给target
    }

    @Override
    public void run() {
        if (target!=null){//如果传入的对象不为空
            target.run();//调用传入对象的run方法
        }
    }
    public void start(){
        start0();//调用本类的start方法
    }
    public void start0(){
        run();//调用本类的run方法
    }
}

ThreadProxy类可以当成Thread类理解,当然这里只是简单演示而已。
可以看到ThreadProxy里有一个Runnable类型的变量target
构造器会把传入的对象给到target
然后ThreadProxy的run方法会判断target是不是空,如果不是就调用target的run方法,也就是传入对象的run方法

而在main方法创建ThreadProxy实例将要创建线程的类传入时,调用start,而start又会调用start0,start0就会调用ThreadProxy的run方法,最终调用传入对象的run方法

多个子线程案例

要求从main线程创建两个子线程
第一个子线程:每秒输出“你好”,输出10次退出
第二个子线程:每秒输出“hello”,输出20次退出

public class test {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        thread1.start();
        thread2.start();
    }
}
class T1 implements Runnable{
    @Override
    public void run() {
        int nums = 0;
        while (true){
            System.out.println("你好"+(++nums)+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (nums == 10 ){
                break;
            }

        }
    }
}
class T2 implements Runnable{
    @Override
    public void run() {
        int nums = 0;
        while (true){
            System.out.println("hello"+(++nums)+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (nums == 20 ){
                break;
            }

        }
    }
}

继承Thread和实现Runnable的区别

  1. 从java的设计上来看,继承Thread或者实现Runnable接口来创建线程本质上没有区别
  2. 但是由于java只能单继承的特性,实现Runnable接口方式更加适合多个线程共享一个资源的情况,避免了单继承的限制。

线程控制

多线程售票问题

有这么一道题目,要求三个线程同时售卖票,当票<=0时就停止售卖,分别用继承Thread和实现Runnable接口两种方式实现

继承Thread

public class test3 {
    public static void main(String[] args) {
        ticket t1 = new ticket();
        ticket t2 = new ticket();
        ticket t3 = new ticket();
        t1.start();
        t2.start();
        t3.start();

    }
}
class ticket extends Thread{
    public static int tickets = 100;//初始剩余票数
    @Override
    public void run() {
        while (true) {
            if (tickets <= 0) {//当剩余票少于等于0时就停止售票
                System.out.println("停止售票");
                break;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "售出一张票" + "剩余票:" + (--tickets));
        }
    }
}

在这里插入图片描述
运行结果可以看到超卖了2张。

实现Runnable接口

public class test3 {
    public static void main(String[] args) {
        ticket t1 = new ticket();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t1);
        Thread thread3 = new Thread(t1);
        thread1.start();
        thread2.start();
        thread3.start();

    }
}
class ticket implements Runnable{
    public  int tickets = 100;//初始剩余票数
    @Override
    public void run() {
        while (true) {
            if (tickets <= 0) {//当剩余票少于等于0时就停止售票
                System.out.println("停止售票");
                break;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "售出一张票" + "剩余票:" + (--tickets));
        }
    }
}

在这里插入图片描述

可以看到上面两种方法也超卖了2张,这是因为3个子线程同时进入while循环,当还剩余1张票的时候,3个子线程都能通过if语句,但是当第一个子线程再售出1张票之后,第2个和第3个子线程由于已经通过了if语句,所以也会进行售票,从而导致超卖2张。

下面随着继续深入学习来解决超卖问题

线程终止

假设有一个子线程一直在不停的跑,我们需要在main线程让这个子线程停下来。

从上面的案例可以分析,假设这个子线程是通过whlie循环在不停的跑,而while能不停的跑是因为放进去的boolean值一直是true。
所以如果想让子线程停下来,就得把true改成false。如果要改子线程的布尔值,就得提供一个方法让main线程去操作。

代码实现

public class test4 {
    public static void main(String[] args) throws InterruptedException {
        RR rr = new RR();
        rr.start();
        Thread.sleep(1000);
        rr.setLoop(false);
    }
}
class RR extends Thread{
    boolean loop = true;
    int nums = 0;
    @Override
    public void run() {
        while(loop){
            System.out.println("滴滴"+(++nums));
        }
    }
    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

控制线程的常用方法

控制线程的常用方法一般都是Thread的方法

setName:设置线程的名称,使之与参数name相同
getName:返回该线程的名称
start:让线程开始执行,Java虚拟机底层调用该线程的start0方法
run:调用线程对象的run方法
setPriority:设置线程的优先级
getPriority:获取线程的优先级
sleep:在指定毫秒数内让当前正在执行的线程休眠(暂停执行)
interrupt:中断线程(一般用来中断休眠,唤醒线程继续执行)

常用方法注意点

  1. start方法的底层会创建新的线程,调用run,但是run方法只是一个普通的方法,不会启动新线程
  2. 线程优先级的范围:MAX_Priority 10 MIN_Priority 1 NORM_Priority 5
  3. sleep方法是线程的静态方法,让当前线程休眠

使用演示:

public class test {
    public static void main(String[] args) throws Exception{
        Cat cat = new Cat();
        cat.start();
        for (int i = 10; i > 0; i--) {
            Thread.sleep(1000);
            System.out.println("唤醒倒计时"+i);
        }
        cat.interrupt();//当输出10次i后,发出interrupted中断信号
    }
}
class Cat extends Thread{
    @Override
    public void run() {
        int nums = 0;
        Thread.currentThread().setName("CatThread");//设置线程名字
        Thread.currentThread().setPriority(MIN_PRIORITY);//设置线程优先级
        while (true) {
            System.out.println("喵喵"+(++nums));
            try {
                sleep(50000);
            } catch (InterruptedException e) {
                System.out.println("中断了睡眠");//当接收到interrupted要求中断时,就catch这个语句,然后重新进入循环
                System.out.println(Thread.currentThread().getName());//输出线程的名字
                System.out.println(Thread.currentThread().getPriority());//输出线程优先级
            }
            if (nums == 50){
                break;
            }
        }
    }
}

线程插队

除了上面的方法还有两个方法比较重要

yield:线程的礼让,让出CPU,让其他线程执行,但礼让的时间不确定,所以不一定礼让成功
join:线程的插队,插队的线程一旦插队成功,则肯定先执行完插入的线程所有任务,再回来执行被插队的线程任务

案例演示:

在main主线程执行数包子1~50,同时创建子线程数包子1 ~ 70.

public class test2 {
    public static void main(String[] args) throws InterruptedException {
        steamed s = new steamed();
        s.start();
        int num = 0;
        while (true){
            if (num == 10){
                s.join();//当主线程数到10个包子的时候,就让子线程插队先执行掉
            }
            System.out.println("主线程数包子"+(++num));
            if (num == 50){
                break;
            }
        }
    }
}
class steamed extends Thread{
    int nums = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("子线程数包子"+(++nums));
            if (nums == 70){
                break;
            }
        }
    }
}

线程插队练习
要求:
主线程每隔1秒输出 hi ,输出10次
当输出到hi 5时,启动一个子线程(实现Runnable),每隔1秒输出hello,输出10次hello后退出。
子线程推出后,主线程再继续执行输出

public class test2 {
    public static void main(String[] args) throws InterruptedException {
        steamed s = new steamed();
        Thread thread = new Thread(s);
        int num = 0;
        while (true){
            if (num == 5) {
                thread.start();
                thread.join();
            }
            if (num == 10){
                break;
            }
            System.out.println("hi"+(++num));
            Thread.sleep(1000);
        }
    }
}
class steamed implements Runnable{
    int nums = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("hello"+(++nums));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (nums == 10){
                System.out.println("子线程结束");
                break;
            }
        }
    }
}

用户线程和守护线程

用户线程:也叫工作线程,结束方式是当线程的任务执行完或者通知结束
守护线程:一般是为用户线程服务的,结束方式是当所有的用户线程结束后,守护线程就会自动结束

常见的守护线程:垃圾回收机制

例如在main方法每秒输出一个hi,共输出10次。然后创建一个子线程每秒输出一个hello,共输出50次
按照正常的情况,肯定是main线程先结束,子线程线程继续运行。但是只要将子线程设置成一个守护线程,那么只要main线程结束,子线程就会结束

设置守护线程。Thread.setDaemon(true);

案例演示

public class test5 {
    public static void main(String[] args) throws InterruptedException {
        guard guard = new guard();
        guard.setDaemon(true);
        guard.start();
        for (int i = 1; i < 10; i++) {
            Thread.sleep(1000);
            System.out.println("hi"+(i));
        }
    }
}
class guard extends Thread{
    int num = 0;
    @Override
    public void run() {
        while (true){
            if (num == 50){
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hello"+(++num));
        }
    }
}

线程的生命周期

一个线程在创建-启动-消亡,会有好几种状态
JDK中用Thread.State枚举,表示了线程的几种状态

NEW:尚未启动的线程
RUNNABLE:在Java虚拟机中执行的线程,此状态还细分为两种Ready,准备执行但还没执行,Running正在执行
BLOCKED:被阻塞等待监视器锁定的线程
WATING:正在等待另一个线程执行特定动作的线程
TIMED_WATING:等待另一个线程执行动作达到指定时间的线程
TERMINATED:已退出的线程

线程的状态会因为各种方法对线程的操作,改变线程的状态

图解
在这里插入图片描述

代码演示查看线程状态

Thread.getState(),可以获取线程状态

public class test5 {
    public static void main(String[] args) throws InterruptedException {
        guard guard = new guard();
        System.out.println("状态="+guard.getState());
        guard.start();
        while (guard.getState() != Thread.State.TERMINATED){
            Thread.sleep(1000);
            System.out.println("状态="+guard.getState());
        }
    }
}
class guard extends Thread{
    int num = 0;
    @Override
    public void run() {
        while (true){
            if (num == 10){
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hello"+(++num));
        }
    }
}

状态=NEW
状态=TIMED_WAITING
hello1
状态=TIMED_WAITING
hello2
状态=TIMED_WAITING
hello3
状态=RUNNABLE
hello4
hello5
状态=RUNNABLE
状态=RUNNABLE
hello6
hello7
状态=RUNNABLE
状态=TIMED_WAITING
hello8
hello9
状态=RUNNABLE
状态=RUNNABLE
hello10
状态=TERMINATED

线程同步

线程同步机制:
在多线程开发中,一些敏感的数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多只能有一个线程访问,以保证数据的完整性

简单的说:线程同步就是一个线程在对一个内存进行操作时,其他线程都无法对这个内存地址进行操作,直到该线程完成操作,其他线程才可以对该线程操作。

就像只有一个马桶的卫生间,有很多人都要上厕所,但是必须一个个排队,进去一个就锁门。别人进不去

synchronized

synchronized关键字就相当于这个锁,synchronized可以修饰代码块,也可以放在方法声明中:

synchronized(对象){
//需同步代码
}

public synchronized 返回类型 方法名 (){
//需同步代码
}

使用synchronized修饰的方法,在同一时间只运行一个线程进入,将其他线程堵塞在后面,一个线程操作完出来了,另一个才能进去。因此这个特性可以用来解决上面的多线程售票超卖的问题

synchronized解决多线程售票超卖问题

继承Thread类

public class test3 {
    public static void main(String[] args) {
        ticket t1 = new ticket();
        ticket t2 = new ticket();
        ticket t3 = new ticket();
        t1.start();
        t2.start();
        t3.start();

    }
}
class ticket extends Thread{
    public static int tickets = 100;//初始剩余票数
    public synchronized static void seep(){
            if (tickets <= 0) {//当剩余票少于等于0时就停止售票
                System.out.println("停止售票");
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "售出一张票" + "剩余票:" + (--tickets));
    }
    @Override
    public void run() {
        while (tickets >0) {
            seep();
        }
    }
}

需要将synchronized修饰的方法写成静态的不然就是同时打开3个seep方法在售票,依旧会超卖。因为继承Thread类创建了3个对象
如果是实现Runnable接口就只需要创建一个对象就能创建3个线程,就不用写成静态的

实现Runnable接口的

public class test3 {
    public static void main(String[] args) {
        ticket t1 = new ticket();
        new Thread(t1).start();
        new Thread(t1).start();
        new Thread(t1).start();

    }
}
class ticket implements Runnable{
    public static int tickets = 100;//初始剩余票数
    public synchronized  void seep(){
            if (tickets <= 0) {//当剩余票少于等于0时就停止售票
                System.out.println("停止售票");
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "售出一张票" + "剩余票:" + (--tickets));
    }
    @Override
    public void run() {
        while (tickets >0) {
            seep();
        }
    }
}

互斥锁

上面用synchronized解决了多线程售票超卖的问题。通俗的说就是在不能多线程一起操作的地方给他加一把锁,只有拿到这个锁,才能进去操作,当一个线程拿到锁进去操作,剩下的线程就没有锁可以拿了,自然就无法进去。

在这里插入图片描述

如果是一个循环,那么一个线程拿到锁之后,进去操作完,就会返回来继续和其他线程抢锁

基本介绍

  1. Java语言中,引入了互斥锁的概念,用于保证共享数据操作的完整性
  2. 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任何同一时刻,只能有一个线程访问该对象。
  3. 关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任何时刻只能有一个线程访问
  4. 同步的局限性:会导致程序的效率降低
  5. 同步方法(非静态)的锁可以是this,也可以是其他对象,但是必须是同一个对象,不然就相当于有很多把锁,那就会有很多线程一起进入
  6. 同步方法(静态的)锁,为当前类本身,用类名.class表示,因为静态比对象创建更前面,无法找到对象加锁

注意点:这个锁是加在对象或者类身上的,相当于一个管理员带着这把锁。只有找到管理员拿到他身上的锁,才能进去。当锁被一个线程拿走了,其他线程就没锁了。

如果是继承Thread类创建的线程,就得将同步方法变成静态的,因为在启动的时候是创建一个对象启动一次,如果不变成静态的,就相当于有多个管理员多把锁。相当于只有一个马桶但是有很多门。当然静态同步方法就得将锁放在类本身上

如果是实现Runnable接口创建的线程,同步方法不用变成静态,因为启动的时候只创建一个对象,所以同步方法是多个线程共享的,相当于只有一个管理员一个锁,锁可以放在对象身上。

细节:
同步方法如果没有使用static修饰,默认锁对象为this
如果方法使用static修饰,默认锁对象是当前类本身,类.class

注意上面说的锁是一种非公平锁,不会比较谁等待的时间久下一个就是谁拿到锁,也有可能一个线程拿到锁执行完回来后,又是它拿到锁

线程死锁

上面了解到了互斥锁,下面了解一下一种风险,死锁。

死锁:多个线程同时占用了对象的锁资源,但不肯想让,导致一直堵塞BLOCKED,形成死锁。

通俗的说就是
A线程手里有一把锁,但是想要继续执行,需要B线程手里的锁。
B线程手里也有一把锁,但是想要继续执行,需要A线程手里的锁。

代码演示:

public class test6 {
    public static void main(String[] args) {
        deadlock A = new deadlock(true);
        deadlock B = new deadlock(false);
        A.start();
        B.start();
    }
}
class deadlock extends Thread{
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean loop;

    public deadlock(boolean loop) {
        this.loop = loop;
    }

    @Override
    public void run() {
        if (loop){
            synchronized (o1){
                System.out.println("A线程拿到o1,进入1");
                synchronized (o2){
                    System.out.println("A线程拿到o2,进入2");
                }
            }
        }else {
            synchronized (o2){
                System.out.println("B线程拿到o2,进入3");
                synchronized (o1){
                    System.out.println("B线程拿到o1,进入4");
                }
            }
        }
    }
}

在这里插入图片描述

从上面代码可以分析,
当传入的是true时会先拿到o1锁,再去抢o2锁,才能执行完毕
反之当传入的是false时会先拿到o2锁,再去抢o1锁,才能执行完毕

只要两个线程执行run方法的时间是同时的就会出现死锁,o1锁被A拿着,B执行不了下面的,o2锁被B拿着,A执行不了下面的。就会一直卡着堵塞BLOCKED,形成死锁。在开发者要避免此种情况

释放锁

当一个线程抢到锁进入同步代码块操作时,此时锁是被该线程拿到的,别人拿不到,正常情况下,只有当该线程将语句执行完,才会释放锁,但是也有有其他情况会导致提前释放锁

以下情况会导致释放锁

  • 线程将同步代码块执行完毕
  • 当前线程在同步代码块中遇到break或者return
  • 在执行同步代码块时遇到未处理的Error或者Exception,导致异常结束
  • 在执行同步代码块时执行了线程对象的wait()方法,当前线程暂停,且释放锁

以下情况不会导致提前释放锁

  • 同步代码块调用Thread.sleep()和Thread.yield()方法,不会释放锁
  • 同步代码块时,其他线程调用了该线程的suspend()方法,将线程挂起

多线程练习

一:按照要求编程
1)在main线程中启动两个线程
2)第一个子线程循环随机打印100以内的整数
3)直到第二个子线程从键盘读取了"Q"命令,第一个子线程就停止

思路: random方法随机生成整数,Scanner键盘按Q就让第一个子线程停止

public class test7 {
    public static void main(String[] args) {
        A a = new A();
        new Thread(a).start();
        B b = new B();
        new Thread(b).start();
    }
}
class A implements Runnable{
    static boolean loop = true;
    public void setLoop(Boolean loop){
        this.loop = loop;
    }
    @Override
    public void run() {
        while (loop){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(new Random().nextInt(100));//随机打印0~100的整数
        }
    }
}

class B  implements Runnable  {
    @Override
    public void run() {
        while (new A().loop){
            System.out.println("请输入字符:");
            char i = new Scanner(System.in).next().toUpperCase().charAt(0);
            if (i == 'Q'){
                new A().setLoop(false);
                break;
            }
        }
    }
}

二:按照要求编程
1)有2个用户分别从同一个卡上取钱(总余额10000)
2)每次都取1000,当余额不足时,就不能取款了
3)不能出现超取现象

public class test8 {
    public static void main(String[] args) {
        ATM atm = new ATM();
        new Thread(atm).start();
        new Thread(atm).start();
        new Thread(atm).start();
    }
}
class ATM implements Runnable{
    static int wallet = 10000;
    public void withdrawMoney() throws InterruptedException {
        synchronized (this){
            if (wallet <= 0){
                System.out.println("余额不足");
                return;
            }
            Thread.sleep(500);
            wallet-=1000;
            System.out.println("线程"+Thread.currentThread().getName()+"取了1000,余额剩余"+wallet);
        }
    }
    @Override
    public void run() {
        while (wallet > 0){
            try {
                withdrawMoney();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/400297.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

d3.js绘制饼状图,悬浮出现字以及点击事件

代码以及注释如下&#xff1a; const width 300; // 定义圆的宽度 const height 300; // 定义圆的高度 const radius Math.min(width, height) / 2; // 算出半径 const color d3.scaleOrdinal() .range(["#98abc5", "#8a89a6", "#6b486b&qu…

【MySQL高级篇】第05章_存储引擎

第05章_存储引擎 1. 查看存储引擎 查看mysql提供什么存储引擎 show engines;2. 设置系统默认的存储引擎 查看默认的存储引擎 show variables like %storage_engine%; #或 SELECT default_storage_engine;修改默认的存储引擎 如果在创建表的语句中没有显式指定表的存储引擎…

一分钟成为签到达人!Redis BitMap轻松解决,Spring Boot带你飞

如何实现签到功能&#xff0c;尤其是如何实现高效的签到与统计&#xff0c;是开发者们需要考虑的问题。在本篇文章中&#xff0c;我们将介绍如何利用Spring Boot整合Redis BitMap实现签到与统计。 Redis BitMap简介 在介绍如何利用Redis BitMap实现签到与统计之前&#xff0c;…

unity动画--动画绑定,转换,用脚本触发

文章目录如何制作和添加动画大概过程示例图将多组图片转化为动画放在对象身上实现动画之间的切换使用脚本触发Parameters(Trigger)如何制作和添加动画 大概过程示例图 将多组图片转化为动画放在对象身上 首先&#xff0c;我们要为我们要对象添加animator 然后我们要设置对应的…

计算机网络-应用层

文章目录前言概述Https协议(443)Http协议(80)HttpsTLS/SSL 协议TLS的四次握手总结前言 本博客仅做学习笔记&#xff0c;如有侵权&#xff0c;联系后即刻更改 科普&#xff1a; 概述 Https协议(443) 参考网址 Http协议(80) 谈到Https必然要先将httpHTTP 请求报文结构 请求…

谷粒学院开发(一):基础准备

商业模式 常见商业模式 B2C模式&#xff1a; 两个角色&#xff1a; 管理员&#xff1a;增加&#xff0c;修改&#xff0c;删除普通用户&#xff1a;查询 商家到用户&#xff0c;自己制作大量自有版权的视频&#xff0c;放在自有平台上&#xff0c;让用户付费。 这是这个项目使…

Linux下查看图片中某点的像素X、Y坐标

在做目标检测、目标追踪的任务过程中&#xff0c;我们会用到一些开源的数据集&#xff0c;比如MOT16多目标追踪数据集。这些数据集会提供数据标注文件gt.txt,里面的内容如下1,1,912,484,97,109,0,7,12,1,912,484,97,109,0,7,13,1,912,484,97,109,0,7,14,1,912,484,97,109,0,7,1…

[Openwrt]procd实现hotplug机制介绍

Linux处理hotplug事件方法kobject_uevent() 产生 uevent 事件(lib/kobject_uevent.c 中), 产生的 uevent 先由 netlink_broadcast_filtered() 发出, 最后调用 uevent_helper[] 所指定的程序来处理.uevent_helper[] 里默认指定 "/sbin/hotplug", 但可以通过 /sys/kern…

spring boot starter 实现生成行为验证码验证

最近公司有一个验证用户行为的需求&#xff0c;因此实现了一个用户行为验证码的starter&#xff0c;具体效果如下&#xff1a;代码结构如下&#xff1a;common 下面放的是公共文件枚举类generator 下面放的是生成行为验证码的相关类与扩展接口resource 下面放的是加载解析行为图…

若依代码生成器的使用

一、代码生成器的使用1.新建maven模块原则上&#xff0c;我们的业务代码和若依系统本身的系统代码是要做隔离的&#xff0c;一方面是易于之后随着若依系统升级而升级&#xff0c;另一方面则是纯粹的合理性考虑。这里新建一个ruoyi-business模块作为业务代码模块&#xff0c;新建…

Mac环境安装python

一、介绍&#xff1a; Python是跨平台的&#xff0c;它可以运行在Windows、Mac和各种Linux/Unix系统上。在Windows上写Python程序&#xff0c;放到Linux上也是能够运行的。 要开始学习Python编程&#xff0c;首先就得把Python安装到你的电脑里。安装后&#xff0c;你会得到Pyt…

LearnOpenGL-光照-4.光照贴图

本人刚学OpenGL不久且自学&#xff0c;文中定有代码、术语等错误&#xff0c;欢迎指正 我写的项目地址&#xff1a;https://github.com/liujianjie/LearnOpenGLProject 文章目录光照贴图漫反射贴图例子1镜面光贴图例子2 采样镜面光贴图小结什么是光照贴图光照贴图如何影响颜色光…

开源一个通用的 HTTP 请求前端组件

像 Postman 这样可视化的 HTTP 请求工具是调试 API 不可或缺的利器。Postman 虽好但也越来越重&#xff0c;而且如果要整合到其他工具中&#xff0c;显然 Postman 又不是一个可行的方案。于是我想打造一个简单的前端组件&#xff08;widget&#xff09;&#xff0c;它是一个标准…

天池 DeepRec CTR 模型性能优化大赛 - 夺冠技术分享

作者&#xff1a;niceperf 团队 (李扬, 郭琳) 大家好&#xff0c;我们是 niceperf 团队&#xff0c;在天池 DeepRec CTR 模型性能优化大赛中&#xff0c;很荣幸取得了冠军的成绩 (Top 1/3802)。这篇文章复盘一下我们的参赛经验&#xff0c;希望对大家有所启发。 1.背景介绍 …

KDZD5035系列电缆试验油杯

一、概述 武汉凯迪正大总结十多年的局放试验经验&#xff0c;开发生产了KDZD5035系列电缆试验油杯终端&#xff0c;具有使用方便&#xff0c;性能可靠&#xff0c;本身局放量小等优点&#xff0c;与早期落地式油杯相比&#xff0c;可为用户节约大量的试验成本。 KDZD5520交流…

单片机学习笔记之点阵(8x8)

心血来潮&#xff0c;想捡一下丢了很久的单片机&#xff0c;纪录一下单片机学习简单的点阵显示&#xff0c;及踩到的䟘&#xff0c;找到吃灰很久的普中科技开发板&#xff08;非广告&#xff0c;为毕设学习买的&#xff09;。 1. 使用工具 使用开发板&#xff1a; 普中科技开发…

Hive---自定义函数

Hive自定义函数 文章目录Hive自定义函数定义自定义函数步骤创建一个Maven工程&#xff0c;导入依赖创建自定义函数类在 hive 的命令行窗口创建函数创建临时函数创建永久函数UDF打成 jar 包上传到服务器/opt/soft/hive312/lib/目录下将 jar 包添加到 hive 的 classpath建临时函数…

python数据类型与数据结构

目录 一、数据类型 1.1变量与常量 1.1.1变量 1.1.2常量 1.2字符串类型 1.3整数与浮点数 1.4List列表 1.5 元组tuple 1.6字典dict 二、字符串格式化 三、数据输入和类型转换 四、简单列表习题练习 一、数据类型 变量类型&#xff1a; 整数int&#xff08;4字节&#x…

IR-825 Biotin,IR 825 Biotin,IR825 Biotin,IR-825可以进行修饰生物素基团

IR825 Biotin&#xff0c;IR 825 Biotin&#xff0c;IR-825 Biotin | 生物素IR825荧光染料&#xff0c; 荧光染料IR825生物素&#xff0c;IR-825近红外染料 | CAS&#xff1a;N/A | 纯度&#xff1a;95%1.IR825 Biotin试剂信息&#xff1a;CAS&#xff1a;N/A外观&#xff1a;固…

字符串的使用

数组字符串转换 joinToString列表转成字符串 val str list.joinToString(",")split(“,”)字符串转成列表 val list1 str.split(",")subString()字符串截取 substring(0,2)这个只含开头不含结尾&#xff0c;索引从0开始 substring(2)这个表示截掉前两…