• 技术文章 >Java >java教程

    Java知识点总结之多线程基础

    WBOYWBOY2022-11-15 20:08:41转载155
    本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于多线程的相关内容,同一时刻,可以执行多个线程。例如:使用迅雷可以同时下载多个文件,下面一起来看一下,希望对大家有帮助。

    php入门到就业线上直播课:进入学习

    推荐学习:《java视频教程

    一、线程相关概念

    1、程序:是为完成特定任务、用某种语言编写的一组指令的集合。简单的说:就是我们写的代码

    2、进程:是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为 该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。

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

    3、线程:是进程创建的,是进程的实体,一个进程可以有多个线程,例如使用迅雷下载文件,迅雷相当于进程,下载的文件相当于线程。

    4、单线程:同一时刻,只允许执行一个线程

    5、多线程:同一时刻,可以执行多个线程。例如:使用迅雷可以同时下载多个文件。

    6、并发:同一时刻,多个任务交替进行。单核CPU实现的多任务就是并发。

    7、并行:同一时刻,多个任务同时进行。多核CPU可以实现并行,当任务较多时,并发和并行有可能同时发生。

    二、线程基本使用

    创建线程有两种方法:

    1、继承Thread类,重写run方法。

    2、实现Runnable接口,重写run方法。

    注意:Thread类实现了Runnable接口。

    (一) 继承Thread类,重写run方法

    public class Thread01 {
        public static void main(String[] args) throws InterruptedException {
            Cat cat = new Cat();
            cat.start();
            System.out.println("主线程" + Thread.currentThread().getName());
            for (int i = 1; i <= 5; i++) {
                System.out.println("主线程i=" + i);
                Thread.sleep(1000);
            }
        }}class Cat extends Thread{
        int times = 0;
        @Override    public void run() {
            while (true) {
                System.out.println("你好" + (++times) + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (times == 5){
                    break;
                }
            }
        }}

    1、在继承Thread类,重写了run()方法后,在main方法中需要创建对象,并调用start()方法,启动线程。

    2、使用start()方法,会调用重写的run()方法。

    3、如果main方法中,start()方法后面还有执行语句,并且run()方法中也有执行语句,main线程中会启动一个子线程Thread-0,主线程不会阻塞,主线程与子线程会交替执行。

    注意:如果主线程执行完毕,但子线程未执行完,进程不会结束,所有线程执行完毕后,进程自动结束。

    4、在主线程中为什么使用start()方法去调用run()方法,而不是直接调用run()方法,因为run()方法是一个普通方法,没有真正启动线程,如果调用run()方法,就会将run()方法执行完毕后,再执行main方法剩余语句,主线程就会阻塞。

    所以上面程序都运算结果是:

    主线程main
    主线程i=1你好1Thread-0主线程i=2你好2Thread-0主线程i=3你好3Thread-0主线程i=4你好4Thread-0主线程i=5你好5Thread-0

    (二) 实现Runnable接口,重写run方法

    public class Thread02 {
        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 count = 0;
            while (true) {
                System.out.println("小狗汪汪叫" + (++count) + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (count == 10){
                    break;
                }
            }
        }}

    1、因为Runnable接口中没有start()方法,所以需要代理

    2、先在main方法中需要创建对象,然后创建一个Thread对象,将之前创建的对象传入Thread,借助Thread,调用start()方法,创建线程。

    3、建议使用Runnable,因为实现Runnable接口更加适合多个线程共享一个资源的情况,避免了单继承的限制。

    三、线程终止

    (一)基本说明

    1、当线程执行完任务时会自动退出。

    2、也可以使用变量控制run()方法退出终止线程。

    (二)操作

    1、如果一个线程中的run()方法内部是一个while(true){},即一个无限循环

    2、我们可以在线程内部,创建一个boolean属性loop,让loop = true,然后while(loop){}

    3、再提供一个setLoop方法,这样就可以在其他类中调用setLoop方法,改变boolean值,来控制线程终止。

    (三)代码演示

    public class ThreadExit {
        public static void main(String[] args) throws InterruptedException {
            T t = new T();
            t.start();
            Thread.sleep(10000);
            t.setLoop(false);
        }}class T extends Thread{
        private boolean loop = true;
        private int times = 0;
        
        @Override    public void run() {
            while (loop){
                System.out.println("hello" + (++times));
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public void setLoop(boolean loop) {
            this.loop = loop;
        }}

    四、线程常用方法

    (一)第一组

    1、setName:设置线程名称,使之与参数name相同。

    2、getName:返回该线程名称。

    3、start:使该线程开始执行,JAVA虚拟机底层调用该线程的start0方法。

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

    5、setPriority:更改线程的优先级。

    6、getPriority:获取线程的优先级。

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

    8、interrupt:中断线程。

    注意事项:

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

    2、中断线程一般用于中断正在休眠的线程,并没有真正的结束线程,所以如果线程中每输出内容后,会调用Thread.sleep()进行休眠的话,我们可以调用interrupt()方法将其提前唤醒

    代码演示:

    public class ThreadMethod01 {
        public static void main(String[] args) throws InterruptedException {
            T t = new T();
            t.setName("邱崇源");
            t.setPriority(1);
            t.start();
            for (int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                System.out.println("hi" + i);
            }
            System.out.println(t.getName() + "优先级是" + t.getPriority());
            t.interrupt();
        }}class T extends Thread{
        @Override    public void run() {
            while (true){
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "正在吃包子" + i);
                }
                try {
                    System.out.println(Thread.currentThread().getName() + "休眠中");
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + "被唤醒了");
                }
            }
        }}

    (二)第二组

    1、yield:是一个静态方法,表示线程的礼让。让出CPU,让其他线程执行,但是不一定成功,因为这取决于CPU,如果CPU认为两个线程可以一起执行,则不进行礼让,所以如果CPU的核数多,并且线程数少,礼让就会大概率失败

    2、join:表示线程的插队,假如主线程与子线程正在交替运行,我们想先让子线程执行完毕,然后再让主线程执行,就可以使用线程插队,在主线程中,创建子线程对象,并调用join方法,可以实现线程插队,线程插队一定会成功,先执行完插入线程任务后,再继续执行主线程

    代码演示:

    public class ThreadMethod02 {
        public static void main(String[] args) throws InterruptedException {
            A a = new A();
            a.start();
            for (int i = 1; i <= 20; i++) {
                System.out.println("主线程(小弟)吃了" + i + "个包子");
                Thread.sleep(1000);
                if (i == 5) {
                    System.out.println("主线程(小弟)让子线程(大哥)先吃");
                    a.join();
                    //Thread.yield();
                    System.out.println("子线程(大哥)吃完了,主线程(小弟)接着吃");
                }
            }
        }}class A extends Thread {
        @Override    public void run() {
            for (int i = 1; i <= 20; i++) {
                System.out.println("子线程(大哥)吃了" + i + "个包子");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }}

    五、用户线程和守护线程

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

    2、守护线程:一般是为工作线程服务的,当所有的用户线程结束守护线程自动结束

    应用场景:

    如果有两个线程,主线程运行结束,但子线程是无限循环。我们想让主线程结束的同时,子线程也结束,就需要让子线程变成守护线程。

    在主线程中,创建子线程对象,并调用setDaemon(true),让子线程变成守护线程。

    注意:一定要放在start方法之前

    代码演示:

    public class ThreadMethod03 {
        public static void main(String[] args) throws InterruptedException {
            MyDaemonThread myDaemonThread = new MyDaemonThread();
            myDaemonThread.setDaemon(true);
            myDaemonThread.start();
            for (int i = 1; i <= 10; i++) {
                System.out.println("宝强正在辛苦工作...");
                Thread.sleep(1000);
            }
            System.out.println("宝强回家了,宋喆仓惶跑路...");
        }}class MyDaemonThread extends Thread{
        @Override    public void run() {
            while (true){
                System.out.println("马蓉与宋喆正在爽歪歪...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }}

    六、线程的生命周期

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

    在这里插入图片描述

    2、线程状态转换图

    在这里插入图片描述

    七、线程的同步

    1、应用场景:

    假如有100张票,有三个线程同时进入该方法买票,票就有可能超卖。所以我们需要线程同步机制,保证数据在同一时刻,最多有一个线程访问。

    可以采取同步方法,在方法中加入synchronized关键字。

    也可以采用同步代码块,synchronized(对象){}

    注意:synchronized是非公平锁,如果这次第一个线程访问了数据,那么下一次第一个线程也有可能访问到数据。

    如果同步方法是非静态的,那么锁可以是this,也可以是其他对象,但要求是同一个对象。

    例:synchronized(this)

    如果同步方法是静态的,锁为当前类本身。

    例:synchronized(类名:class)

    2、代码演示:

    public class SellTicket {
        public static void main(String[] args) {
            SellTicket02 sellTicket04 = new SellTicket02();
            Thread thread1 = new Thread(sellTicket04);
            Thread thread2 = new Thread(sellTicket04);
            Thread thread3 = new Thread(sellTicket04);
            thread1.start();
            thread2.start();
            thread3.start();
        }}class SellTicket02 implements Runnable {
        public static int ticket = 100;
        private boolean loop = true;
    
        public synchronized void sell() {
            if (ticket <= 0) {
                loop = false;
                return;
            }
            System.out.println("卖出了一张票,还剩" + (--ticket) + "张");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        @Override    public void run() {
            while (loop) {
                sell();
            }
        }}

    八、线程的死锁

    1、基本介绍

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

    2、发生场景:

    例如:A和B的面前都各有两道门,A的第一道门是o1,第二道门是o2。B的第一道门是o2,第二道门是o1。他们的面前有两把锁,一个是o1锁,一个是o2锁,假如A抢到了o1锁,B抢到了o2锁,但是他们只可打开各自的第一道门,第二道门都没有打开,那么他们都无法释放自己的锁资源,又不可能相让,因此发生了死锁。

    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(); 
    	boolean flag; 
    	
    	public DeadLockDemo(boolean flag) {//构造器 
    		this.flag = flag; 
    	}
    	
    	@Override 
    	public void run() {  
    		if (flag) { 
    			synchronized (o1) { 
    				System.out.println(Thread.currentThread().getName() + " 进入 1"); 
    				synchronized (o2) { 
    					System.out.println(Thread.currentThread().getName() + " 进入 2"); 
    				} 
    			} 
    		} else {
    			synchronized (o2) { 
    				System.out.println(Thread.currentThread().getName() + " 进入 3"); 
    				synchronized (o1) {  
    					System.out.println(Thread.currentThread().getName() + " 进入 4"); 
    				} 
    			} 
    		} 
    	} }

    九、释放锁

    1、下面操作会释放锁

    当前线程的同步方法,同步代码块执行结束。
    当前线程在同步代码块,同步方法中遇到break,return
    当前线程在同步代码块,同步方法中出现了未处理的错误或异常,导致异常结束。
    当前线程在同步代码块,同步方法中执行的线程对象的wait方法,当前线程暂停,并释放锁。

    2、 下面操作不会释放锁

    线程执行同步代码块和同步方法时,程序调用Thread.sleep(),Thread.yield()方法暂停当前线程的执行,不会释放锁。
    线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。

    推荐学习:《java视频教程

    以上就是Java知识点总结之多线程基础的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:CSDN,如有侵犯,请联系admin@php.cn删除

    前端(VUE)零基础到就业课程:点击学习

    清晰的学习路线+老师随时辅导答疑

    自己动手写 PHP MVC 框架:点击学习

    快速了解MVC架构、了解框架底层运行原理

    专题推荐:java
    上一篇:深入分析Java的序列化与反序列化 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • ❤️‍🔥共22门课程,总价3725元,会员免费学• ❤️‍🔥接口自动化测试不想写代码?• 聊聊JavaScript中实现继承的6种方法• 一文搞懂Java线程池实现原理• 深入探讨JavaScript中的async函数• 详细介绍Java中的线程中断• 深入分析Java的序列化与反序列化
    1/1

    PHP中文网