当前位置: 首页 > news >正文

【Java】Java多线程编程基础

文章目录

  • 1. 进程与线程
    • 1.1 进程与线程的基本认识
      • 1.1.1 进程(Process)
      • 1.1.2 线程(Thread)
    • 1.2 为什么会有线程
      • 1.2.1 以看视频为例
  • 2. 多线程实现
    • 2.1 Thread类实现多线程
    • 2.2 Runnable接口实现多线程
    • 2.3 Callable接口实现多线程
    • 2.3 多线程运行状态
      • 2.3.1 创建状态
      • 2.3.2 就绪状态
      • 2.3.3 运行状态
      • 2.3.4 阻塞状态
      • 2.3.5 终止状态
  • 3. 多线程常用操作方法
    • 3.1 线程的命名和获取
    • 3.2 线程休眠
    • 3.3 线程中断
    • 3.4 线程强制执行
    • 3.5 线程让步
    • 3.6 线程优先级
  • 4. 线程的同步和锁死
    • 4.1 线程同步
      • 4.1.1 同步代码块实现
      • 4.1.2 同步方法实现
      • 4.1.3 Lock锁实现
    • 4.2 线程死锁
  • 5. 后台守护线程
    • 5.1 观察守护线程操作
    • 5.2 守护线程的应用场景
  • 6. 线程池
    • 6.1 线程池的概念
    • 6.2 为何引入线程池?
    • 6.3 线程池的好处?
    • 6.4 核心思想:线程复用

1. 进程与线程

1.1 进程与线程的基本认识

1.1.1 进程(Process)

进程是程序的一次动态执行过程,它经历了从代码加载、执行、到执行完毕的一个完整过程;同时也是并发执行的程序在执行过程中分配和管理资源的基本单位,竞争计算机系统资源的基本单位。

1.1.2 线程(Thread)

线程可以理解为进程中的执行的一段程序片段,是进程的一个执行单元,是进程内可调度实体,是比进程更小的独立运行的基本单位,线程也被称为轻量级进程。

1.2 为什么会有线程

每个进程都有自己的地址空间,即进程空间,在网络或多用户换机下,一个服务器通常需要接收大量不确定数量用户的并发请求,为每一个请求都创建一个进程显然行不通,即使多进程可以提高硬件资源的利用率,但是进程的启动和销毁需要消耗大量的系统性能,从而导致程序的执行性能下降。所以为了进一步提升并发操作的处理能力,在进程的基础上又划分了多线程概念。

1.2.1 以看视频为例

打开看视频可以理解为实现了一个“进程”,而在看的时候,会同时听到声音,看到图片,还有可以发弹幕等……这些都是多线程实现。

简而言之:一个程序至少一个进程,一个进程至少一个线程。

2. 多线程实现

在java中,如果要实现多线程,就必须依靠线程主体类,而java.lang.Thread是java中负责多线程操作类,只需继承Thread类,就能成为线程主体类,为满足一些特殊要求,也可以通过实现Runnable接口或者Callable接口来完成定义。

具体方式如下:

  1. 继承Thread类,重写run方法(无返回值)

  2. 实现Runnable接口,重写run方法(无返回值)

  3. 实现Callable接口,重写call方法(有返回值且可以抛出异常)

2.1 Thread类实现多线程

通过继承Thread类,并重写父类的run()方法实现

public void run()

定义线程类:

class MyThread extends Thread{private String name;public MyThread(String name) {this.name = name;}@Overridepublic void run() {for(int i = 0 ; i < 50 ; i++) {System.out.println(this.name + "正在工作中……" + i);}}
}

多线程启动:

public class testThread {public static void main(String[] args) {// 实例化线程对象MyThread mt1 = new MyThread("线程一");MyThread mt2 = new MyThread("线程二");MyThread mt3 = new MyThread("线程三");// 启动实例线程对象mt1.start();mt2.start();mt3.start();}
}

运行情况:
在这里插入图片描述

以上结果可以发现,它并不是按照顺序执行,而是以一种随机交替方式执行的,每次的结果都可能不一样。

通过以上代码可以知道,要想实现多线程,就要依靠Thread类的start()方法执行,线程启动后会默认调用run()方法。

2.2 Runnable接口实现多线程

使用Thread类的确可以实现多线程,但是也容易发现它的缺陷:面向对象的单继承局限,因此才采用Runnable接口来实现多线程。

该接口的定义如下(以下并不需要在代码中实现):

@FunctionalInterface
public interface Runnable{public void run();
}

定义线程类:

public class MyThread implements Runnable{private String name;public MyThread(String name) {this.name = name;}@Overridepublic void run() {for(int i = 0 ; i<50 ;i++) {System.out.println(this.name + " 正在执行中……" + i);}}
}

多线程启动:

public class testThread {public static void main(String[] args) {// 实例化继承Runnable接口的MyThread类Runnable mt1 = new MyThread("线程一");Runnable mt2 = new MyThread("线程二");Runnable mt3 = new MyThread("线程三");// 多线程启动new Thread(mt1).start();new Thread(mt2).start(); 		new Thread(mt3).start(); 		}
}

运行情况:

在这里插入图片描述

以上程序实例化三个继承Runnable接口的MyThread类,然后通过Thread类的一个构造函数public Thread(Runnable target)分别实例化Thread类,再利用start()方法实现多线程。

Thread方案实现和Runnable方案实现的区别:

// Thread类定义:
public class Thread extends Object implements Runnable {}

也就是说,Thread类是Runable接口的子类,通过直接覆写Thread类的run方法实际上依然是覆写Runnable接口内的run方法,其实本质上是没有区别的,但是利用Runnable方案实现更加能体现面向对象思维,有点类似于代理设计模式。

2.3 Callable接口实现多线程

使用Runnable接口实现的多线程可以避免单继承的局限,但是还有一个问题就是run方法没有返回值,为了解决这个问题,所以提供了一个Callable接口java.util.concurrent.Callable

其定义如下:

@FunctionalIterface
public interface Callable<T>{public T call() throws Exception;
}

FutureTask类常用方法:

import java.util.concurrent.ExecutionException; // 导入ExecutionException异常包
public FutureTask(Callable<T> callable) // 构造函数:接收Callable接口实例
public FutureTask(Runable runnable,T result) // 构造函数:接收Runnable接口实例,同时指定返回结果类型 
public T get() throws InterruptedException,ExecutionException // 取得线程操作返回结果

Thread类的一个构造方法:

public Thread(FutureTask<T> futuretask) //构造方法:接收FutureTask实例化对象

定义线程主体类:

import java.util.concurrent.Callable;
public class MyThread implements Callable<Integer>{private String name;public MyThread(String name) {this.name = name;}@Overridepublic Integer call(){Integer sum = 0;for(int i = 0 ; i < 500;i++) {System.out.println(this.name + i);sum += i;}return sum;}
}

多线程启动:

import java.util.concurrent.FutureTask;
public class testThread {public static void main(String[] args)  throws Exception{// 实例化继承Callable接口的MyThread类MyThread mt1 = new MyThread("线程一");MyThread mt2 = new MyThread("线程二");MyThread mt3 = new MyThread("线程三");// FutureTask类接收继承Callable接口的MyThread的实例FutureTask<Integer> ft1 = new FutureTask<Integer>(mt1);FutureTask<Integer> ft2 = new FutureTask<Integer>(mt2);FutureTask<Integer> ft3 = new FutureTask<Integer>(mt3);// 启动多线程new Thread(ft1).start();new Thread(ft2).start();new Thread(ft3).start();System.out.println(ft1.get());System.out.println(ft2.get());System.out.println(ft3.get());}
}

运行情况:

在这里插入图片描述

通过以上代码容易了解到,Callable接口实现采用泛型技术实现,继承需要重写call方法,再通过FutureTask包装器包装,传入后实例化Thread类实现多线程。

其中FutureTask类是Runnable接口的子类,所以才可以利用Thread类的start方法启动多线程,读者可以将call方法假设为有返回值的run方法。

2.3 多线程运行状态

任意线程具有5种基本的状态:

2.3.1 创建状态

实现Runnable接口和继承Thread类创建一个线程对象后,该线程对象处于创建状态,此时它已经有了内存空间和其它资源,但他还是处于不可运行的。

2.3.2 就绪状态

新建线程对象后,调用该线程的start方法启动该线程。启动后,进入线程队列排队,由CPU调度服务。

2.3.3 运行状态

就绪状态的线程获得处理器的资源时,线程就进入了运行状态,此时将自动调用run方法。

2.3.4 阻塞状态

正在运行的线程在某些特殊情况下,如:当前线程调用sleep、suspend、wait等方法时,运行在当前线程里的其它线程调用join方法时,以及等待用户输入的时候。只有当引起阻塞原因消失后,线程才能进入就绪状态。

2.3.5 终止状态

当线程run方法运行结束后,或者主线程的main()方法结束后,线程才能处于终止状态,线程一旦死亡就不能复生。

3. 多线程常用操作方法

  1. 线程的命名与取得
  2. 线程休眠方法
  3. 线程中断方法
  4. 线程强制执行
  5. 线程让步
  6. 线程优先级

3.1 线程的命名和获取

线程是不确定的运行状态,名称就是线程的主要标记。因此,需要注意的是,对于线程的名字一定要在启动之前设置进程名称,不建议对已经启动的线程,进行更改名称,或者为不同线程设置重名。

其主要方法:

public Thread(Runnable runnable,String name) //构造函数:实例化线程对象,为线程对象设置名称
public final void setName(String name) // 普通函数:设置线程名字
public final String getName() // 普通函数:获取线程名字

观察线程命名操作:

class MyThread implements Runnable{@Overridepublic void run() {System.out.println( Thread.currentThread().getName());System.out.println( Thread.currentThread().getName());}
}public class ThreadDemo {public static void main(String[] args) {MyThread mt1 = new MyThread();MyThread mt2 = new MyThread();MyThread mt3 = new MyThread();new Thread(mt1,"线程一").start();new Thread(mt2,"线程二").start();new Thread(mt3).start(); // 使用默认的线程名称mt1.run(); // 这里直接查看main方法的线程名称}
}

运行情况:

在这里插入图片描述

需要注意的是主方法也是进程的一个线程。

3.2 线程休眠

sleep方法定义在java.lang.Thread中,由Thread.sleep()调用实现。其作用是需要暂缓线程的执行速度,则可以让当前线程休眠,即当前线程从“运行状态”进入到“阻塞状态”。sleep方法会指定休眠时间,线程休眠的时间会大于或等于该休眠时间,该线程会被唤醒,此时它会由“阻塞状态”变成“就绪状态”,然后等待CPU的调度执行。

其主要方法:

public static void sleep(long millis) throws InterruptedException // 普通函数:设置休眠时间的毫秒数
public static void sleep(long millis,int nanos) throws InterruptedException // 普通函数:设置休眠毫秒数和纳秒数

定义线程主体类:

public class MyThread implements Runnable{@Overridepublic void run() {for(int i = 0 ; i<90 ; i++) {System.out.println(Thread.currentThread().getName() + " 正在工作中……" + i );if(i == 20) {try {System.out.println(Thread.currentThread().getName() + " 等一会儿就要休息五秒钟了……");Thread.sleep(5000); // 当前线程休眠五秒钟System.out.println(Thread.currentThread().getName() + " 已经休息五秒钟了……");}catch (InterruptedException e) {System.out.println(Thread.currentThread().getName() + " 休眠被打扰了……");}}}}
}

观察线程休眠操作:

public class ThreadDemo {public static void main(String[] args){MyThread mt1 = new MyThread();MyThread mt2 = new MyThread();MyThread mt3 = new MyThread();new Thread(mt1,"线程一").start();new Thread(mt2,"线程二").start();new Thread(mt3,"线程三").start();}
}

运行情况:

在这里插入图片描述

由以上结果容易了解到,休眠期间该线程并没有工作,而其他未休眠的线程则在继续工作。

3.3 线程中断

interrupt方法定义在java.lang.Thread中,由Thread.interrupt()调用实现。该方法将会设置该线程的中断状态位,即设置为true,中断的结果线程是终止状态、还是阻塞状态或是继续运行至下一步,就取决于该程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(即中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程。

其主要方法:

// 以下均为Thread类的方法
public boolean isInterrupted() //普通函数:判断线程是否被中断
public void interrupt() //普通函数:中断线程执行

定义线程主体类:

public class MyThread implements Runnable {@Overridepublic void run() {int i = 0;while(true) {System.out.println(Thread.currentThread().getName() + " 正在努力工作中……" + i++);try {System.out.println(Thread.currentThread().getName() + " 准备休息5秒钟了……");Thread.sleep(5000);  // 休眠5秒钟System.out.println(Thread.currentThread().getName() + " 已经休息5秒钟了……");}catch(InterruptedException e) {System.out.println(Thread.currentThread().getName() + " 被打扰了,不想工作了……");break;}}}
}

观察线程中断操作:

public class ThreadDemo {public static void main(String[] args) throws Exception{Runnable mt1 = newyThread();Runnable mt2 = newyThread();Runnable mt3 = newyThread();Thread thread1 = new Thread(mt1,"线程一"); //线程一就绪Thread thread2 = new Thread(mt2,"线程二"); //线程二就绪Thread thread3 = new Thread(mt3,"线程三"); //线程三就绪thread1.start(); //线程一启动thread2.start(); //线程二启动thread3.start(); //线程三启动// 以下通过利用main线程控制 线程一 中断Thread.sleep(6000); //使main方法先休眠6秒钟,即让子线程先运行6秒钟if(!thread1.isInterrupted()) {System.out.println("吵闹~~~");thread1.interrupt(); //中断线程一的执行}}
}

运行情况:
在这里插入图片描述

以上代码线程一在休眠期间被中断,然后直接break,也就是说以后就只有线程二和三工作了。这里每当中断执行都会产生InterruptedException异常。

3.4 线程强制执行

join方法定义在java.lang.Thread中,由Thread.join()调用实现。多线程启动后会交替进行资源抢占和线程体执行,如果此时某些线程异常重要,也就是说这个对象需要优先执行完成,则可以设置为线程强制执行,待其完成后其它线程继续执行。

其主要方法:

// Thread类方法
public final void join() throws InterruptedException //普通函数:强制执行

定义线程主体类:

public class MyThread implements Runnable{private Thread thread = null;public MyThread() {}public MyThread(Thread thread) {this.thread = thread;}@Overridepublic void run() {for(int i = 0; i<50 ; i++) {if(i >= 20 && i <= 25) {try {System.out.println(thread.getName()  + "被迫参与 " + Thread.currentThread().getName() + " 的工作了……" +i);thread.join();}catch(InterruptedException e) {e.printStackTrace();}}// 以下语句不管上面判断语句是否执行都会执行的System.out.println(Thread.currentThread().getName() + " 正在工作中……" +i);}}
}

观察线程强制执行操作:

public class ThreadDemo {public static void main(String[] args) {Thread mainThread = Thread.currentThread();MyThread mt1 = new MyThread(mainThread);Thread thread1 = new Thread(mt1,"子线程");thread1.start();for(int i = 0 ;i<20;i++) {try {Thread.sleep(1000); // 每次main线程休眠1秒System.out.println(Thread.currentThread().getName() +  "正在工作中……" + i);}catch(InterruptedException e) {e.printStackTrace();}}System.out.println("我main线程已经完成了所有任务,从此无法再复生了……");}
}

运行情况:
在这里插入图片描述

由以上结果容易了解到,当子线程经行到第20次时,开始强制执行main线程任务,直至全部完成为止,而且只能够完整地执行地一次。当main线程执行完毕后,子线程才能开始继续执行。

3.5 线程让步

yield方法定义在java.lang.Thread中,由Thread.yield()调用实现。多线程在彼此交替执行的时候往往需要进行资源的轮流抢占,如果某些不是很重要的线程抢占到资源但是又不急于执行时,就可以将当前的资源暂时让步出去,交给其它资源先执行。但是,因为yeild是将线程由“运行状态”转别为“就绪状态”,这样并不能保证在当前线程调用yield方法之后,其它具有相同优先级的线程就一定能获得执行权,也有可能是当前线程又进入到“运行状态”继续运行,因为还是要依靠CPU调度才可以。

其主要方法:

public static void yield() // 静态函数:线程让步

定义线程主体类:

public class MyThread implements Runnable{private Thread thread = null;public MyThread () {}public MyThread(Thread thread) {this.thread = thread;}@Overridepublic void run() {for(int i = 0 ; i<50 ; i++) {System.out.println(Thread.currentThread().getName()  + " 正在工作中……" + i);if( i  == 30) {System.out.println(Thread.currentThread().getName() + " 打算将工作交给 "+thread.getName() + "了……");Thread.yield(); // 当前线程让步出去System.out.println(Thread.currentThread().getName() + " 又想自己工作了……");}}}
}

观察线程让步操作:

public class ThreadDemo {public static void main(String[] args) {Thread mainThread = Thread.currentThread();Runnable mt1 = new MyThread(mainThread);Thread thread1 = new Thread(mt1,"子线程");thread1.start();for(int i = 0 ; i < 40 ; i++) {*运行情况:*		System.out.println(Thread.currentThread().getName() + " 正在工作中……" + i);}}
}

运行情况:

在这里插入图片描述

由以上结果容易了解到,会短暂地将资源调度让给其它的线程,当然这并不是严格的,因为还是要CPU调度的。

3.6 线程优先级

所有创造的线程都是子线程,所有的子线程在启动时都会保持同样的优先级权限,但是如果现在某些重要的线程希望可以优先抢占到资源并且先执行,就可以修改优先级权限来实现。

记住当线程的优先级没有指定时,所有线程都携带普通优先级。

需要理解的是:
优先级用从1到10的范围的整数指定。10表示最高优先级,1表示最低优先级,5是普通优先级,也就是默认优先级。
优先级相对最高的线程在执行时被给予优先权限。但是不能保证线程在启动时就进入运行状态。
优先级越高越有可能先执行。

其主要方法以及常量:

public static final int MAX_PRIORITY // 静态常量:最高优先级,数值为10
public static final int NORM_PRIORITY //静态常量:普通优先级,数值为5
public static final int MIN_PRIORITY // 静态常量:最低优先级,数值为1
public final void setPriority(int newPriority) // 普通函数:设置优先级
public final int getPriority() //普通函数:获取优先级

定义线程主体类:

public class MyThread implements Runnable{@Overridepublic void run() {for(int i  = 0 ; i<50 ; i++) {System.out.println(Thread.currentThread().getName() + " 正在工作中……" + i);}}
}

观察线程优先级操作:

public class ThreadDemo {public static void main(String[] args) {Runnable mt1 = new MyThread();Runnable mt2 = new MyThread();Thread thread1  = new Thread(mt1,"线程一");Thread thread2  = new Thread(mt2,"线程二");// 设置优先级thread2.setPriority(Thread.MAX_PRIORITY);thread1.setPriority(Thread.MIN_PRIORITY);// 启动thread1.start();thread2.start();}
}

运行情况:
在这里插入图片描述

由以上可以看出,线程二执行的概率高于线程一的执行概率,前面的执行情况大致为交替执行,是因为它们的优先级均相等,默认都等于5。

4. 线程的同步和锁死

当这样也导致了一个问题:在某一时刻,这一份资源在某一个线程发生改变时,其它线程正在执行的操作也会受到其影响。

如下程序为售票员售票代码以及结果:

定义线程主体类:

public class MyThread implements Runnable{private int ticket = 10; @Overridepublic void run() {while(true) {if(ticket<0) {System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket);break;}try {Thread.sleep(1000); // 延迟1秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了)}catch(InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()  + " 正在售票,还剩余票数为:" + ticket--);}}
}

观察售票状态:

public class ThreadDemo {public static void main(String[] args) {// 一份资源Runnable mt1 = new MyThread();// 共享同一份资源new Thread(mt1,"售票员A").start();new Thread(mt1,"售票员B").start();new Thread(mt1,"售票员C").start();}
}

运行情况:
在这里插入图片描述

以上结果就很好地说明了多线程的不安全问题,票数本来应该大于等于0的,但是因为某一个A线程延迟,由于共享资源,此时可能其它某个线程已经将票数售完(ticket-- == 0),然后线程A继续执行就会使得票数小于0。要解决类似于上面的问题,就需要引入线程同步和死锁概念。

4.1 线程同步

解决数据共享问题必须使用同步,所谓的同步就是指多个线程在同一个时间段内只能有一个线程执行指定的代码,其他线程要等待此线程完成之后才可以继续进行执行,在Java中提供有synchronized关键字以实现同步处理,同步的关键是要为代码加上“锁”。

而锁的操作有三种:

  1. 同步代码块
  2. 同步方法
  3. Lock实现

4.1.1 同步代码块实现

使用方式:

synchronized(需要同步的对象){需要同步的操作
}

定义线程主体类:

public class MyThread implements Runnable{private int ticket = 10; @Overridepublic void run() {while(true) {// 同步代码块synchronized(this) {if(ticket<0) {System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket);break;}try {Thread.sleep(10); // 延迟0.01秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了)}catch(InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()  + " 正在售票,还剩余票数为:" + ticket--);}}}
}

观察售票状态:

public class ThreadDemo {public static void main(String[] args) {// 一份资源Runnable mt1 = new MyThread();// 共享同一份资源new Thread(mt1,"售票员A").start();new Thread(mt1,"售票员B").start();new Thread(mt1,"售票员C").start();}
}

运行情况:

在这里插入图片描述

读者从上面代码也容易了解到,虽然它的确起到了安全的作用,但是执行的效率却下降了,因为每次都只有一个线程才能访问同步代码块。

4.1.2 同步方法实现

使用方式:

利用函数包装的形式实现,如下:

修饰符 synchronized 返回类型 函数名()

定义线程主体类:

public class MyThread implements Runnable{private int ticket = 10; @Overridepublic void run() {while(this.sale()) {}}public synchronized boolean sale() {if(ticket<0) {System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket);return false;}try {Thread.sleep(10); // 延迟0.01秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了)}catch(InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()  + " 正在售票,还剩余票数为:" + ticket--);return true;}
}

观察售票状态:

public class ThreadDemo {public static void main(String[] args) {// 一份资源Runnable mt1 = new MyThread();// 共享同一份资源new Thread(mt1,"售票员A").start();new Thread(mt1,"售票员B").start();new Thread(mt1,"售票员C").start();}
}

运行情况:

在这里插入图片描述

4.1.3 Lock锁实现

定义线程主体类:

package cn.wu;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class MyThread implements Runnable{private int ticket = 10;private final Lock lock = new ReentrantLock();@Overridepublic void run() {while(this.sale()) {}}public boolean sale() {lock.lock();try{if(ticket<0) {System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket);return false;}Thread.sleep(200);System.out.println(Thread.currentThread().getName()  + " 正在售票,还剩余票数为:" + ticket--);}catch (Exception e) {e.printStackTrace();}finally {lock.unlock();}return true;}
}

观察售票状态:

public class ThreadDemo {public static void main(String[] args) {// 一份资源Runnable mt1 = new MyThread();// 共享同一份资源new Thread(mt1,"售票员A").start();new Thread(mt1,"售票员B").start();new Thread(mt1,"售票员C").start();}
}

运行情况:

在这里插入图片描述

4.2 线程死锁

所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去,死锁的操作一般是在程序运行时候才有可能出现,死锁是在多线程开发中较为常见的一种问题,过多的同步就有可能出现死锁。

观察死锁状态:

class firstCorssBridge{public synchronized void tell(secondCorssBridge scb) {System.out.println("张三告诉王五:我先过,你后过,否则你别想过这桥!");scb.cross();}// 以下函数不会执行public synchronized void cross() {System.out.println("张三快快乐乐地过桥了……");}
}
class secondCorssBridge{public synchronized void tell(firstCorssBridge fcb) {System.out.println("王五告诉张三:我先过,你后过,否则你别想过这桥!");fcb.cross();}// 以下函数不会执行public synchronized void cross() {System.out.println("王五快快乐乐地过桥了……");}
}public class DeadLock implements Runnable{private firstCorssBridge fcb = new firstCorssBridge();private secondCorssBridge scb = new secondCorssBridge();public DeadLock() {// 启动线程 并执行以下语句new Thread(this).start(); // 会运行run函数fcb.tell(scb); // 运行到里面时 fcb会等待scb}@Overridepublic void run() {scb.tell(fcb); // 运行到里面时 scb会等待fcb}public static void main(String[] args) {new DeadLock();}
}

运行情况:
在这里插入图片描述

上面红色的说明:两者已经处于相互等待状态,后续代码并不会执行。

如果读者不太理解上面的代码,可以在new Thread(this).start(); // 会运行run函数语句的下面加上:

try {Thread.sleep(100); // 休眠0.1秒钟}catch(InterruptedException e) {e.printStackTrace();}

结果就为:

在这里插入图片描述

这实际上是“锁中有锁”的情况。

5. 后台守护线程

Java中的线程分为两类,用户线程和守护线程。守护线程(Daemon)是一种运行在后台的线程服务线程,当用户线程存在时,守护线程可以同时存在,但是,当用户线程不存在时,守护线程会全部消失。

主要操作方法:

public final setDaemon(boolean on) throws Exception // 普通函数:是否设置为守护线程
public  final boolean isDaemon() //普通函数:  判断是否为

5.1 观察守护线程操作

class MyThread implements Runnable{private int times;public MyThread(int times) {this.times = times;}@Overridepublic void run() {for(int i = 0 ; i<times;i++) {if(Thread.currentThread().isDaemon()) {try {Thread.sleep(10); // 如果是守护线程,则休眠0.01秒钟}catch(InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + " 正在工作中……"+i);}}
}public class testDemo {public static void main(String[] args) {MyThread mt1 = new MyThread(4);MyThread mt2 = new MyThread(100); //守护线程的循环次数远多于用户线程Thread thread1 = new Thread(mt1,"用户线程");Thread thread2 = new Thread(mt2,"守护线程");thread2.setDaemon(true); //thread2设置为守护线程thread1.start();thread2.start();}
}

运行情况:
在这里插入图片描述

由以上可以了解到,守护线程已经提前结束了,原因是main线程等用户线程全部消失了。

5.2 守护线程的应用场景

在主线程关闭后无需手动关闭守护线程,因为会自动关闭,避免了麻烦,Java垃圾回收线程就是一个典型的守护线程,简单粗暴地可以理解为所有为线程服务而不涉及资源的线程都能设置为守护线程。

6. 线程池

6.1 线程池的概念

容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程的操作,无需反复创建线程而消耗过多的资源。

6.2 为何引入线程池?

如果并发的线程数量过多,并且每个线程都是执行一个时间很短的任务就结束,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要消耗时间,线程也属于宝贵的系统资源,因此,线程池就是为了能使线程可以复用而创建的。

6.3 线程池的好处?

  • 降低资源的消耗,减少创建和销毁线程的次数,每个工作线程都可以被重复使用,可执行多个任务
  • 提高响应速度,不需要频繁地创建线程,如果有线程可以直接使用,避免了系统僵死
  • 提高线程的可管理性

6.4 核心思想:线程复用

Runnable简单实例:

package cn.wu;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Test {public static void main(String[] args) {// 1.创建一个线程池,指定线程的数量为4ExecutorService pools = Executors.newFixedThreadPool(4);// 2.添加线程任务Runnable target = new MyRunnable();pools.submit(target); // 第一次提交任务,此时创建新线程pools.submit(target); // 第二次提交任务,此时创建新线程pools.submit(target); // 第三次提交任务,此时创建新线程pools.submit(target); // 第四次提交任务,此时创建新线程pools.submit(target); // 第五次提交任务,复用之前的线程pools.shutdown(); // 当所有任务全部完成后才关闭线程池
//        pools.shutdownNow(); // 立即关闭线程池}
}
class MyRunnable implements Runnable {@Overridepublic void run() {for(int i = 0 ; i<10 ; i++) {System.out.println(Thread.currentThread().getName()+"正在执行任务…  "+i);}}
}

结果:

Callable简单实例:

package cn.wu;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class Test {public static void main(String[] args) {// 1.创建一个线程池,指定线程的数量为4ExecutorService pools = Executors.newFixedThreadPool(4);try{long start = System.currentTimeMillis();// 2.添加线程任务Future<String> t1 = pools.submit(new MyCallable(100000)); // 提交求出1-100000和的线程任务Future<String> t2 = pools.submit(new MyCallable(200000)); // 提交求出1-200000和的线程任务Future<String> t3 = pools.submit(new MyCallable(300000)); // 提交求出1-300000和的线程任务Future<String> t4 = pools.submit(new MyCallable(400000)); // 提交求出1-400000和的线程任务Future<String> t5 = pools.submit(new MyCallable(500000)); // 提交求出1-500000和的线程任务System.out.println(t1.get());System.out.println(t2.get());System.out.println(t3.get());System.out.println(t4.get());System.out.println(t5.get());long end = System.currentTimeMillis();System.out.println("采用多线程所耗时间为:"+(end-start)*1.0/1000+"s");start = System.currentTimeMillis();long sum = 0;for(int i = 1 ; i<=100000 ; i++) {sum += i;}System.out.println("最终结果为:"+sum);sum = 0;for(int i = 1 ; i<=200000 ; i++) {sum += i;}System.out.println("最终结果为:"+sum);sum = 0;for(int i = 1 ; i<=300000 ; i++) {sum += i;}System.out.println("最终结果为:"+sum);sum = 0;for(int i = 1 ; i<=400000 ; i++) {sum += i;}System.out.println("最终结果为:"+sum);sum = 0;for(int i = 1 ; i<=500000 ; i++) {sum += i;}System.out.println("最终结果为:"+sum);end = System.currentTimeMillis();System.out.println("采用单线程所耗时间为:"+(end-start)*1.0/1000+"s");}catch(Exception e) {e.printStackTrace();}}
}
class MyCallable implements Callable<String> {private int num;public MyCallable(int num) {this.num = num;}@Overridepublic String call() throws Exception {long sum = 0;for(int i = 1 ; i <= num ; i++) {sum += i;}return Thread.currentThread().getName()+"任务执行的最终结果为:"+sum;}
}

结果:

在这里插入图片描述

相关文章:

【Java】Java多线程编程基础

文章目录 1. 进程与线程1.1 进程与线程的基本认识1.1.1 进程&#xff08;Process&#xff09;1.1.2 线程&#xff08;Thread&#xff09; 1.2 为什么会有线程1.2.1 以看视频为例 2. 多线程实现2.1 Thread类实现多线程2.2 Runnable接口实现多线程2.3 Callable接口实现多线程2.3 …...

FFmpeg-4.2.4的去logo源码分析

1.源码 libavfilter/vf_delogo.c 2.源码分析 /** 去logo算法, 函数的参数解释如下: w: 输入图像的宽度 h: 输入图像的高度 logo_x: 标志区域左上角的x坐标 logo_y: 标志区域左上角的y坐标 logo_w: 标志的宽度 logo_h: 标志的高度 band: 处理区域周围的带宽大小 show: 是否在…...

深度学习(一)

目录 一、特征工程的作用 二、深度学习的应用 三、得分函数 四、损失函数 五、前向传播 六、反向传播 一、特征工程的作用 数据特征决定了模型的上限预处理和特征提取是最核心的算法与参数选择决定了如何逼近这个上限 二、深度学习的应用 无人驾驶人脸识别分辨率重构 深…...

Stream API将对象中的某一字段取出转换为list或数组

List<DevicePartMaintain> devicePartMaintainList devicePartMaintainMapper.selectDevicePartMaintainByMitId(mitId);所有id转换为List 要使用Stream流获取devicePartMaintainList中所有的id&#xff0c;您可以使用stream()方法将列表转换为流&#xff0c;然后使用…...

什么是Java中的JVM(Java虚拟机)?

JVM&#xff08;Java虚拟机&#xff09;是Java平台的核心组件之一&#xff0c;是一个用于执行Java字节码的虚拟计算机。Java源代码经过编译器编译&#xff0c;生成字节码文件&#xff08;.class文件&#xff09;&#xff0c;然后由JVM来解释和执行这些字节码。JVM负责将字节码翻…...

springboot + redis + 注解 + 拦截器 实现接口幂等性校验

一、概念 幂等是一个数学与计算机学概念&#xff0c;在数学中某一元运算为幂等时&#xff0c;其作用在任一元素两次后会和其作用一次的结果相同。在计算机中编程中&#xff0c;一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。 幂等函数或幂等方法是…...

PLC编程:关键在于模拟操作流程和实现控制

PLC编程的核心是通过程序描述流程&#xff0c;完成控制过程。因此&#xff0c;掌握PLC编程语言和基本功能实现是必要的。 PLC语言主要分为梯形图、语句和功能图。梯形图适合基本逻辑描述&#xff0c;语句表用于数据处理&#xff0c;相对较难理解。步进式功能图的状态函数描述很…...

List的各种排序

目录 Collections.sort对list进行排序 对象中某个属性进行排序 通过比较器进行比较 JAVA8特性Stream流进行排序 Stream升降序组合使用 Collections.sort对list进行排序 public static void main(String[] args) {List<Integer> list new ArrayList<>();list…...

在自定义数据集上微调Alpaca和LLaMA

本文将介绍使用LoRa在本地机器上微调Alpaca和LLaMA&#xff0c;我们将介绍在特定数据集上对Alpaca LoRa进行微调的整个过程&#xff0c;本文将涵盖数据处理、模型训练和使用流行的自然语言处理库(如Transformers和hugs Face)进行评估。此外还将介绍如何使用grado应用程序部署和…...

Python 实现接口类的两种方式+邮件提醒+动态导入模块+反射(参考Django中间件源码)

实现抽象类的两种方式 方式一 from abc import ABCMeta from abc import abstractmethodclass BaseMessage(metaclassABCMeta):abstractmethoddef send(self,subject,body,to,name):pass 方式二 class BaseMessage(object):def send(self, subject, body, to, name):raise …...

Solr原理剖析

一、简介 Solr是一个高性能、基于Lucene的全文检索服务器。Solr对Lucene进行了扩展&#xff0c;提供了比Lucene更为丰富的查询语言&#xff0c;并实现了强大的全文检索功能、高亮显示、动态集群&#xff0c;具有高度的可扩展性。同时从Solr 4.0版本开始&#xff0c;支持SolrCl…...

解决 “无法将 ‘npm‘ 项识别为 cmdlet、函数、脚本文件或可运行程序的名称“ 错误的方法

系列文章目录 文章目录 系列文章目录前言一、错误原因&#xff1a;二、解决方法&#xff1a;三、注意事项&#xff1a;总结 前言 在使用 npm 进行前端项目开发时&#xff0c;有时会遇到错误信息 “无法将 ‘npm’ 项识别为 cmdlet、函数、脚本文件或可运行程序的名称”&#x…...

Python 电商API 开发最佳实践

一、简介 当你打卡了一家北京最具有地中海特色的餐厅&#xff0c;当我们在餐厅点餐时&#xff0c;服务员会给我们一份菜单&#xff0c;菜单上列出了所有可供选择的菜品和饮料。我们可以在菜单上选择我们想要的食物和饮料&#xff0c;然后告诉服务员我们的选择。服务员会根据我…...

JAVA基础-集合(List与Map)

目录 引言 一&#xff0c;Collection集合 1.1,List接口 1.1.1&#xff0c;ArrayList 1.1.1.1&#xff0c;ArrayList的add&#xff08;&#xff09;添加方法 1.1.1.2&#xff0c;ArrayList的remove&#xff08;&#xff09;删除方法 1.1.1.3&#xff0c;ArrayList的contai…...

19 QListWidget控件

Tips: 对于列表式数据可以使用QStringList进行左移一块输入。 代码&#xff1a; //listWidget使用 // QListWidgetItem * item new QListWidgetItem("锄禾日当午"); // QListWidgetItem * item2 new QListWidgetItem("汗滴禾下土"); // ui->…...

手动安装docsify

安装docsify详见&#xff1a;docsify 1、下载 wget https://codeload.github.com/docsifyjs/docsify/zip/refs/heads/master -o docsify-master.zip 2、解压 unzip docsify-master.zip 3、移动文件到nginx的html所在目录【略】 4、配置nginx&#xff0c;示例如下 locati…...

yaml语法详解

#kv #对空格的严格要求十分高 #注入到我们的配置类中 #普通的keyvalue name: qinjiang#对象 student:name: qingjiangage: 3#行内写法 student1: {name: qinjiang,age: 3}#数组 pets:- cat- dog- pigpet: [cat,dog,pig]yaml可以给实体类赋值 person:name: kuangshenage: 19happ…...

ubuntu下tmux安装

目录 0. 前言1. Tmux介绍2. 安装3. 验证安装 0. 前言 本节安装tmux终端复用工具&#xff0c;在Ubuntu中运行一些服务或脚本的时候往往不能退出终端&#xff0c;需要一直挂着。在有图形界面的linux中你还可以新开一个终端去做别的事&#xff0c;但是在无界面linux中&#xff0c…...

ssh打开远程vscode

如果想要远程打开其他终端的vscode&#xff0c;首先要知道远程终端的ip地址和用户名称以及用户密码 1、打开本地vscode 2、点击左下角蓝色区域 3、页面上部出现如下图&#xff0c;点击ssh&#xff0c;我这里已经连接&#xff0c;所以是connect to host 4、选择Add New SSH Host…...

Socket发送数据---winsock库和boost库

一个是通过winsock库提供的api实现,一个是boost库实现,两个方法都可以,因为项目是vc++6.0实现的,不支持boost库,只能使用winsock库,vc++6.0太老,局限性大。 通过Winsock库提供的API 通过UDP #include<winsock2.h> #include<vector> #include<WS2tcpip.h…...

Qt Core学习日记——第七天QMetaObject(上)

每一个声明Q_OBJECT的类都具有QMetaObject对象 Q_OBJECT宏源代码&#xff1a; #define Q_OBJECT \ public: \ QT_WARNING_PUSH \ Q_OBJECT_NO_OVERRIDE_WARNING \ static const QMetaObject staticMetaObject; \ virtual const QMetaObject *metaObject() const; \ vir…...

100、用简洁的语言描述一下:TCP的三次握手和四次挥手(不需要长篇大论)

TCP的三次握手和四次挥手 TCP协议是7层网络协议中的传输层协议&#xff0c;负责数据的可靠传输。 1、三次握手 在建立TCP连接时&#xff0c;需要通过三次握手来建立&#xff0c;过程是: 客户端向服务端发送一个SYN服务端接收到SYN后&#xff0c;给客户端发送一个SYN_ACK客户…...

中南大学硕士论文latex版本全指导

要毕业了&#xff0c;闲下点时间写的东西。之前一直收益与师兄师姐流传下来的latex版本&#xff0c;用起来很舒服&#xff0c;希望后面的学弟学妹也能完美用上。latex功能很强大&#xff0c;不需要自己排版&#xff0c;只管内容即可&#xff0c;但是安装流程会多一丢丢。 目录 …...

RFC8470在HTTP中使用早期数据

摘要 使用TLS早期数据会暴露出重放攻击的可能性。本文定义了允许客户端与服务器就早期数据中发送的HTTP请求进行通信的机制。描述了使用这些机制来减轻重放风险的技术。 1. 介绍 TLS 1.3[TLS13]引入了早期数据&#xff08;也称为零往返时间&#xff08;0-RTT&#xff09;数…...

macOS Big Sur 11.7.9 (20G1426) 正式版 ISO、PKG、DMG、IPSW 下载

macOS Big Sur 11.7.9 (20G1426) 正式版 ISO、PKG、DMG、IPSW 下载 本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。另外也支持在 Window…...

【LeetCode】62.不同路径

题目 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff1f; …...

使用序列化和反序列化函数archivedDataWithRootObject和unarchivedObjectOfClasses的使用和遇到问题及解决方案

为何archiveRootObject和unarchiveObjectWithFile正常&#xff0c;而archivedDataWithRootObject和unarchivedObjectOfClasses一直报错。 [NSKeyedArchiver archiveRootObject:account toFile:path];和c PPAccountModel *account [NSKeyedUnarchiver unarchiveObjectWithFile:…...

python获取鼠标出颜色

import pyautogui as pg import keyboarddef rgb2hex(r, g, b):return #{:02x}{:02x}{:02x}.format(r, g, b)try:width, height pg.size()print(f"Display resolution: {width} * {height}\n") # 打印屏幕分辨率print(按下shift键打印出鼠标所指位置的颜色......)w…...

Github Flow工作流简单介绍(以部署为中心的开发模式)

前言 这篇文章主要介绍Github Flow的理念&#xff0c;以下内容来源于《Github入门与实践》。 Github Flow是以部署为中心的开发模式&#xff0c;通过简单的规则&#xff0c;持续高速且安全地进行部署。而Gitflow则是以发布为中心的分支管理模型&#xff0c;它提供了一种更灵活…...

selenium浏览器驱动下载

Chrome谷歌浏览器 下载地址&#xff1a;http://chromedriver.storage.googleapis.com/index.html 不同的Chrome的版本对应的chromedriver.exe 版本也不一样&#xff0c;下载时不要搞错了。 如果是最新的Chrome, 下载最新的chromedriver.exe 就可以了。 Firefox火狐浏览器 驱…...