Java多线程基础

目录

一、线程的基本使用

(一)创建线程的两种方式

(二)线程简单案例(Thread)

问题:main函数与开启的线程是否是阻塞的,即线程运行时,main函数等待线程运行结束?

问题:为什么不直接调用cat的run方法,而是通过start来使用?

start解析

 (三)线程简单案例(Runnable)

(四)线程简单案例(多线程)

二、线程终止

三、线程的常用方法

(一)常用方法一

(二)常用方法二

(三)用户线程和守护线程

四、线程生命周期

五、线程的同步

六、互斥锁

七、线程死锁

八、释放锁

(一)下面操作会释放锁

(二)下面操作不会释放锁


一、线程的基本使用

(一)创建线程的两种方式

在Java中线程使用有两种方式:

① 继承Thread类,重写run方法

② 实现Runnable接口,重写run方法

(二)线程简单案例(Thread)

由主函数开启一个线程,每秒钟输出一句话

public class Thread01 {
    public static void main(String[] args) {
        Cat cat = new Cat(); // 创建对象当成线程使用
        cat.start(); // 调用start方法开启线程
    }
}
// 1.继承Thread类后就可以当作线程使用
// 2.重写run方法,实现自己的业务
// 3.Thread类实现了Runnable的run方法
class Cat extends Thread {
    static int count = 0;
    @Override
    public void run() {  // 重写run方法,实现业务
        while(true) {
            count ++;
            // 每隔一秒,输出一句话
            System.out.println("我是帅哥" + count);
            // 让该线程休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count == 8) break;
        }
    }
}

问题:main函数与开启的线程是否是阻塞的,即线程运行时,main函数等待线程运行结束?

验证

(1)方式一

首先改进main函数,运行

    public static void main(String[] args) throws InterruptedException {
        Cat cat = new Cat(); // 创建对象当成线程使用
        cat.start(); // 调用start方法开启线程
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程运行" + i);
            Thread.sleep(1000);
        }
    }

查看两个线程的名字

System.out.println("主线程运行" + i + "线程名字" + Thread.currentThread().getName());
System.out.println("我是帅哥" + count + "线程名字" + Thread.currentThread().getName());

说明主线程与开启的线程是并行的,即多线程

 (2)方式二

使用Jconsole(jdk自带的线程监控工具)查看线程,首先扩大输出语句的次数方便我们验证

public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        Cat cat = new Cat(); // 创建对象当成线程使用
        cat.start(); // 调用start方法开启线程
        for (int i = 0; i < 60; i++) {
            System.out.println("主线程运行" + i);
            Thread.sleep(1000);
        }
    }
}
// 1.继承Thread类后就可以当作线程使用
// 2.重写run方法,实现自己的业务
// 3.Thread类实现了Runnable的run方法
class Cat extends Thread {
    static int count = 0;
    @Override
    public void run() {  // 重写run方法,实现业务
        while(true) {
            count ++;
            // 每隔一秒,输出一句话
            System.out.println("我是帅哥" + count);
            // 让该线程休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count == 80) break;
        }
    }
}

运行main函数后,在Terminal输入Jconsole

 main线程和Thread-0线程都在运行

main线程运行结束后,Thread-0线程还在继续运行 

 Thread-0线程运行结束,此时连接中断

总结:当main函数运行结束时,不意味着main函数开启的线程也结束。

问题:为什么不直接调用cat的run方法,而是通过start来使用?

解释:如果调用cat,run()来执行,此时run方法里输出的线程名字将会是main,意味着并没有开启线程,而且此时是串行化的执行,需要run方法运行结束才会继续执行main下面的内容

验证

    public static void main(String[] args) throws InterruptedException {
        Cat cat = new Cat(); // 创建对象当成线程使用
        //cat.start(); // 调用start方法开启线程
        cat.run(); // 直接调用run方法
        for (int i = 0; i < 60; i++) {
            System.out.println("主线程运行" + i + "线程名字" + Thread.currentThread().getName());
            Thread.sleep(1000);
        }
    }

start解析

查看start方法中,我们可以发现调用了start0方法,该方法是一个native(本地)方法,由JVM调用,底层是c/c++实现,真正实现多线程的效果, 是 start0(), 而不是 run

public synchronized void start() {
    。。。
    start0();
    。。。
}

private native void start0();

 (三)线程简单案例(Runnable)

java是单继承的,在某些情况下一个类可能已经继承了父类,此时无法通过继承Thread类创建线程,因此可以通过实现Runnable接口来创建线程 ,这里使用了代理模式,通过Thread类进行代理,最终调用的是实现Runnable接口的dog的run方法。       

public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        // 此时dog没有start方法
        // 创建Thread对象,将实现Runnable方法的dog放入,调用start方法
        Thread thread = new Thread(dog);
        thread.start();
    }
}
class Dog implements Runnable {

    static int count = 0;
    @Override
    public void run() {
        while(true) {
            System.out.println("狗叫" + (++ count) + " 线程名称" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

(四)线程简单案例(多线程)

public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread1 = new Thread(t1);
        thread1.start(); // 启动线程1
        Thread thread2 = new Thread(t2);
        thread2.start(); // 启动线程2
    }
}
class T1 implements Runnable {

    static int count = 0;
    @Override
    public void run() {
        while(true) {
            System.out.println("t1..." + (++ count) + " 线程名称" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count == 10) {
                break;
            }
        }
    }
}
class T2 implements Runnable {

    static int count = 0;
    @Override
    public void run() {
        while(true) {
            System.out.println("t2..." + (++ count) + " 线程名称" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count == 10) {
                break;
            }
        }
    }
}

总结继承Thread   VS    接口Runnable

从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,都是start()-->start0(), 从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口

实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable

使用继承方式的好处是,在 run() 方法内获取当前线程直接使用 this 就可以了,无须使用Thread.currentThread0方法;不好的地方是 Java 不支持多继承,如果继承了Thread类那么就不能再继承其他类。另外任务与代码没有分离,当多个线程执行一样的任务时需要多份任务代码,而 Runable 则没有这个限制。 

二、线程终止

当线程完成任务后会自动退出,也可以通过使用变量控制run方法退出的方式停止线程,即通知方式

public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        ThreadExit threadExit = new ThreadExit();
        threadExit.start();

        // 休眠5秒再通知threadExit线程
        Thread.sleep(5 * 1000);
        // 将flag置为false,停止threadExit线程
        threadExit.setFlag(false);
    }
}
class ThreadExit extends Thread {
    static int count = 0;
    private Boolean flag = true;
    @Override
    public void run() {
        while(flag) {
            System.out.println("线程运行" + (++count) + " 线程名字" + this.getName());
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void setFlag(Boolean flag) {
        this.flag = flag;
    }
}

三、线程的常用方法

(一)常用方法一

  1. setName  //设置线程名称,使之与参数 name 相同
  2. getName  //返回该线程的名称
  3. start  //使该线程开始执行; Java 虚拟机底层调用该线程的 start0 方法
  4. run  //调用线程对象 run 方法,直接调用不会创建新的线程
  5. setPriority  //更改线程的优先级
  6. getPriority  //获取线程的优先级
  7. sleep //线程的静态方法,在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  8. interrupt  //中断线程,但没有真正终止线程,一般用于中断正在休眠的线程

线程优先级

案例:

public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.setPriority(1);
        t.start();
        // 休眠5秒叫醒线程t
        Thread.sleep(5000);
        System.out.println("线程" + t.getName() + "的优先级为:" + t.getPriority());
        t.interrupt();
    }
}
class T extends Thread {
    @Override
    public void run() {
        while(true) {
            System.out.println("线程" + this.getName() + "吃一百个包子");
            try {
                System.out.println("睡个觉继续吃...");
                sleep(20 * 1000);
            } catch (InterruptedException e) {
                System.out.println("被叫醒了,继续吃");
            }
        }
    }
}

(二)常用方法二

  1. yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
  2. join: 线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务

join解释图

案例: main线程创建一个子线程,每隔1s 输出 hello输出 20次,主线程每隔1秒,输出 hi,输出 20次.要求两个线程同时执行,当主线程输出 5次后,就让子线程运行完毕,主线程再继续

public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        t1.start();

        for (int i = 1; i <= 10; i++) {
            System.out.println("main线程吃 " + "吃第" + i + "个包子");
            Thread.sleep(1000);
            if(i == 5) {
                System.out.println("老大" + t1.getName() + "先吃");
                t1.join();   // 让t1先运行完
                System.out.println("老大" + t1.getName() + "吃完了");
            }
        }
    }
}
class T1 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(this.getName() + "吃第" + i + "个包子");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 yield

    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        t1.start();

        for (int i = 1; i <= 10; i++) {
            System.out.println("main线程吃 " + "吃第" + i + "个包子");
            Thread.sleep(1000);
            if(i == 5) {
                System.out.println("老大" + t1.getName() + "先吃");
//                t1.join();
                Thread.yield();  // 线程礼让
                System.out.println("老大" + t1.getName() + "吃完了");
            }
        }
    }

(三)用户线程和守护线程

  • 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
  • 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
  • 常见的守护线程:垃圾回收机制 

案例:子线程循环次数大于main线程,当main线程结束后,子线程也需要结束

public class ProtectThread {
    public static void main(String[] args) throws InterruptedException {
        T4 t4 = new T4();
        t4.setDaemon(true);
        t4.start();
        for(int i = 0; i < 5; i ++) {
            System.out.println("我是用户线程(工作线程)");
            Thread.sleep(1000);
        }
    }
}

class T4 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("我是守护线程...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

四、线程生命周期

JDK 中用 Thread.State 枚举表示了线程的几种状态

有的书籍会说7种状态,是将Runnable状态分为了Ready(就绪态)和Running(运行态) 

 查看线程状态

public class ThreadState_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        System.out.println(t.getName() + " 状态 " + t.getState());
        t.start();
        while (Thread.State.TERMINATED != t.getState()) {
            System.out.println(t.getName() + " 状态 " + t.getState());
            Thread.sleep(500);
        }
        System.out.println(t.getName() + " 状态 " + t.getState());
    }
}
class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 2; i++) {
                System.out.println("hi " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}

五、线程的同步

在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作.

可以 通过加锁实现线程的同步,比如Synchronized关键字

1、同步代码块

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

2、同步方法

Synchronized还可以放在方法声明中,表示整个方法为同步方法

public synchronized void m() {
    // 同步代码段    
}

六、互斥锁

  1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
  2. 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
  3. 关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时, 表明该对象在任一时刻只能由一个线程访问
  4. 同步的局限性:导致程序的执行效率要降低
  5. 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
  6. 同步方法(静态的)的锁为当前类本身

注意事项和细节:

  1. 同步方法如果没有使用static修饰:默认锁对象为this
  2. 如果方法使用static修饰,默认锁对象:当前类.class
  3. 实现的落地步骤:
  • 需要先分析上锁的代码
  • 选择同步代码块或同步方法
  • 要求多个线程的锁对象为同一个即可! 

七、线程死锁

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

案例:

妈妈:你先完成作业,才让你玩手机

小明:你先让我玩手机,我才完成作业

public class DeadLock {
    public static void main(String[] args) {
        //模拟死锁现象
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A 线程");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B 线程");
        A.start();
        B.start();
    }
}
class DeadLockDemo extends Thread {
    static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 static
    static Object o2 = new Object();
    private boolean flag;
    public DeadLockDemo(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {

        if(flag) {
            synchronized (o1) {
                System.out.println(this.getName() + "进入了1" + " 请求2");
                synchronized (o2) {
                    System.out.println(this.getName() +" 请求2");
                }
            }

        } else {
            synchronized (o2) {
                System.out.println(this.getName() + "进入了3" + " 请求4");
                synchronized (o1) {
                    System.out.println(this.getName() +" 请求4");
                }
            }
        }
    }
}

八、释放锁

(一)下面操作会释放锁

(二)下面操作不会释放锁

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DOUBLETROUBLE 1

文章目录 DOUBLETROUBLE: 1实战演练一、前期准备1、相关信息 二、信息收集1、nmap探测目标靶机端口2、扫描目标网址目录3、访问网站&#xff0c;发现secret下有个图片4、将图片下载5、查看图片所含内容6、破解密码并查看7、登陆邮箱8、创建反弹shell9、上传反弹shell10、监听11…

失业五个月,终于有offer了!但这家公司的风评惨不忍睹,要接吗?

往年&#xff0c;程序员们找工作可以说是不怎么费力的&#xff0c;不少求职者还会比对几家offer&#xff0c;看薪酬、看加不加班、看通勤时间等等等等&#xff0c;最后选择自己最满意的那一家过去。 但是今年&#xff0c;情况确实完全不一样&#xff0c;用网友的话形容就是“往…

不同厂家对讲机耳塞耳挂/领夹型988对讲机如何写频改频点/频率能互相通信

988型号都是很多厂家代工出来的,代工出来默认的频点都不一样,有可能买回来的2个不同厂家生产的对讲机,这样它们要能通讯,必须要同频点才能互通,它一般出厂设定16个频道,长按+和-键来切换频道。 需要用到typeC 的写频线,其实是用CH430芯片的usb写频线,可以找厂家要写频线…

文件上传之,waf绕过(24)

上传参数名解析&#xff1a;明确哪些东西可以修改 content-disposition:一般可更改 表单的数据 name:表单参数值&#xff0c;不能更改 表单提交的值 filename&#xff1a;文件名&#xff0c;可以修改 上传的文件名 content-type&#xff1a;文件mime&#xff0c;…

数据库索引结构(1)概念

常见的索引 主键和二级索引 MySQL学习笔记-主键索引和二级索引_mysql中主键索引和二级索引的区别_爱因诗贤的博客-CSDN博客 MYSQL-主键索引与二级索引_mysql二级索引存在哪个文件_青苔小榭的博客-CSDN博客 采用主键索引的好处&#xff1a;如果元素的位置发生修改&#xff0c;那…

【随笔记】全志 T507 PF4 引脚无法被正常设置为中断模式的问题分析

相关信息 硬件平台&#xff1a;全志T507 系统版本&#xff1a;Android 10 / Linux 4.9.170 问题描述&#xff1a;PF4 无法通过标准接口设置为中断模式&#xff0c;而 PF1、PF2、PF3、PF5 正常可用。 分析过程 一开始以为是引脚被其它驱动占用引起&#xff0c;或者该引脚不具…

Mybatis中处理特殊SQL处理逻辑

文章目录 0、前言1、模糊查询2、动态表名3、获取自增的组件4、批量删除 0、前言 在MyBatis中可能会有一些特殊的SQL需要去执行&#xff0c;一般就是模糊查询、批量删除、动态设置表名、添加功能获取自增的主键这几种&#xff0c;现在分别来进行说明。 为了方便演示 &#xff0…

OA管理痛点解决:从“硬编码”到“低代码”

低代码开发平台是一种逐渐流行起来的软件开发方式&#xff0c;它可以以快速且简单的方式构建各种应用程序&#xff0c;从而帮助企业快速响应市场变化和满足不断变化的业务需求。在企业的日常管理工作中&#xff0c;OA系统是一种非常常见的应用程序&#xff0c;它可以帮助企业管…

C++每日一练:饿龙咆哮-逃离城堡(避坑指南)非负整数求和

文章目录 前言一、题目二、解题代码及思路1、思路2、代码 三、非负整数求和总结 前言 饿龙这一题要说难度嘛&#xff0c;还真是挺简单的&#xff0c;但要满分也是有坑的&#xff01;本文就记录了笔者解题过程&#xff0c;希望能对读者使用C编程有所启发。至于非负整数求和代码…

redis高级篇三(分片集群)

一)进行测试Sentinel池: 集群的定义:所谓的集群&#xff0c;就是通过增加服务器的数量&#xff0c;提供相同的服务&#xff0c;从而让服务器达到一个稳定、高效的状态 之前的哨兵模式是存在着一些问题的&#xff0c;因为如果主节点挂了&#xff0c;那么sentinel集群会选举新的s…

斯坦福、Nautilus Chain等联合主办的 Hackathon 活动,现已接受报名

由 Stanford Blockchain Accelerator、Zebec Protocol、 Nautilus Chain、Rootz Lab 共同主办的黑客松活动&#xff0c;现已接受优秀项目提交参赛申请。 在加密行业发展早期&#xff0c;密码极客们就始终在对区块链世界基础设施&#xff0c;在发展方向的无限可能性进行探索。而…

如何用Python进行屏幕录制?

文章目录 引言gpt3.5给出的代码更换截图函数——ImageGrab.grab禁用imshow解决递归现象摄像头录制代码后期需求 引言 关于屏幕录制这个功能需求&#xff0c;之前用过基于ffmpeg的Capture录屏软件&#xff0c;但是fps拉高以后会变得很卡&#xff0c;声音也同样出现卡顿。也自己…

nodej+vues汽车销售4s店服务平台商城系统购物车积分兑换7z9d2

在经济快速发展的带动下&#xff0c;汽车服务平台的发展也是越来越快速。用户对汽车服务信息的获取需求很大。在互联网飞速发展的今天&#xff0c;制作一个汽车服务平台系统是非常必要的。本系统是借鉴其他人的开发基础上&#xff0c;用MySQL数据库和nodejs定制了汽车服务平台系…

【MySQL】事务

事务是一组操作的集合,我们将一组操作视为一个整体,所以事务里面的操作的时候要么同时成功,要么同时失败,之所以会有事务也是因为我们在实际生活中会用到 最典型的例子就是转账操作:A向B进行转账,A这边扣款成功的同时B那边一定是收款成功的,如果没有事务的话就会出现A扣款成功但…

LMS,RGB,XYZ色彩空间转换

前言 首先需要指明本文中描述的R,G,B并非通常的sRGB中的三个分量R,G,B&#xff0c;而是波长分别为700nm&#xff0c;546.1nm&#xff0c;435.8nm的单色红光&#xff0c;单色绿光&#xff0c;单色蓝光。sRGB中的RGB中的红色、绿色、蓝色已经不是单色光了。虽然习惯上大家都叫RGB…

网络安全里的主要岗位有哪些?小白如何快速入门?

入门Web安全、安卓安全、二进制安全、工控安全还是智能硬件安全等等&#xff0c;每个不同的领域要掌握的技能也不同。 当然入门Web安全相对难度较低&#xff0c;也是很多人的首选。主要还是看自己的兴趣方向吧。 本文就以下几个问题来说明网络安全大致学习过程&#x1f447; 网…

SpringCloud微服务调用方式(RestTemplate)

服务调用方式 RPC和HTTP 无论是微服务还是SOA&#xff0c;都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢&#xff1f; 常见的远程调用方式有以下2种&#xff1a; RPC&#xff1a;Remote Produce Call远程过程调用&#xff0c;类似的还有 。自定义数据格式&am…

learn C++ NO.4 ——类和对象(2)

1.类的6个默认成员函数 1.1.默认成员函数的概念 在 C 中&#xff0c;如果没有显式定义类的构造函数、析构函数、拷贝构造函数和赋值运算符重载函数&#xff0c;编译器会自动生成这些函数&#xff0c;这些函数被称为默认成员函数。 class Date { };初步了解了默认成员函数&am…

STL-常用算法(二.拷贝 替换 算术 集合)

开篇先附上STL-常用算法(一)的链接 STL-常用算法&#xff08;一.遍历 查找 排序&#xff09;_小梁今天敲代码了吗的博客-CSDN博客 目录 常用拷贝和替换算法&#xff1a; copy函数示例&#xff1a;&#xff08;将v1容器中的元素复制给v2&#xff09; replace函数示例&#…

Java 9 - 18 各个版本新特性总结

【 Java 9 - 18 各个版本新特性总结&#xff0c;B站视频介绍】https://www.bilibili.com/video/BV1PT411P7Wn?vd_source5a3a58ca0e99223ffb58cddf2f3a7282 一、模块化引入 模块是 Java 9 中新增的一个组件&#xff0c;可以简单理解为是package的上级容器&#xff0c;是多个pa…