知识屋:更实用的电脑技术知识网站
所在位置:首页 > 编程技术  > CSS教程

Java多线程入门超详解(小白也可以看懂的)

发表时间:2022-03-21来源:网络

Java多线程入门超详解(小白也可以看懂的)

一、名词详解1.1 进程和线程1.2 并行和并发1.3 同步和异同步1.4 临界资源和临界区二、为什么要用多线程三、线程的五种状态四、Java创建线程的两种方法4.1、继承Thread类4.2、实现Runnable接口Runnable接口拓展:使用lambda表达式五、Java线程的常用方法5.1、wait()和notify()5.2、sleep(),yield(),join()5.3、setPriority()六、synchronized的使用

一、名词详解

名词解释有个概念即可,修过操作系统这门课的同学可以快速浏览一遍在脑海中回忆起来。

1.1 进程和线程

进程: 进程就是一个运行的程序实体,进程拥有独立的代码和数据空间,比如我们打开了QQ,就是启动了一个QQ的进程。进程间的切换开销较大,进程是资源分配的最小单位,一个进程可以创建多个线程。

线程: 线程类似于进程,是轻量级的进程,同一个进程创建的线程共享代码段和数据空间,因此线程间的切换开销较小。线程是cpu调度的最小单位。

1.2 并行和并发

并行: 指的是同一时刻的同时执行,比如你在吃饭,你旁边的同学在喝水,便可说你们两个是在并行。

并发: 在同一时间间隔执行,比如你吃一口饭,喝一口水,便可以说你是在并发地执行吃饭和喝水两个任务。

因此,并行指的是不同实体的多个任务,用多个cpu完成多个任务,如hadoop集群;而并发指的是同一个实体进行的多个任务,旨在提高处理器每个核的运行效率。

1.3 同步和异同步

同步: 即实时处理,可以理解为方法执行完时等待系统的返回值或消息,再执行下一步的操作。如一个顽固的老头,充了100块钱话费,一定要看到话费到账的信息再去做其他事。

异步: 方法执行之后不必一直等到返回值或消息,交由系统进行异步处理。如上面提到了老头,在充了100块钱话费后去喝茶了,晚上才看到话费到账的提示。

1.4 临界资源和临界区

临界资源: 。一次只允许一个线程访问的资源,如打印机,一个线程抢占后其他线程只能等待其用完后再去抢占。因为多个线程同时访问一个临界源可能会造成数据的不一致甚至毁坏,因此对临界资源进行访问的方法一般要加上同步方法
临界区: 指的是对临界资源进行访问的代码段,如使用打印机的代码。

二、为什么要用多线程

通过多个线程提高系统的工作效率以及资源的使用效率

三、线程的五种状态

创建、就绪、运行、阻塞、终止

重要:记熟状态转换图!看懂了这张图,也就基本可以理解线程的运行状态。
创建: 新建了一个线程对象 t = new Thread()

就绪: t.start(),此时线程并没有开始运行,而是进入了就绪状态,告诉操作系统“ 我准备好了可以运行 ”,具体什么时候运行由操作系统的调度决定。

运行: 线程分配到了cpu的时间片,此时占用cpu资源运行

阻塞: 由于线程自身的原因,此时线程释放cpu资源,暂停运行进入阻塞状态。满足某些条件后可重新进入就绪状态等待系统调度。阻塞分为三种:
1: 等待阻塞: 执行object.wati()方法,线程释放持有的对象锁,同时进入阻塞状态,等待其他线程执行object.notify或者object.notifyAll()唤醒,进入就绪状态等待系统调度。
2:同步阻塞: 线程在尝试获取比如objectX的锁资源时,若X已经被其他线程所持有了,那么本线程就进入阻塞状态,直至X被释放时再去争抢X,成功获取锁资源objectX再进入就绪状态。
3: 其它阻塞: 如执行Thread.sleep()或t.join()。sleep()不会释放持有的锁资源,时间到了直接进入就绪状态。

终止: 线程结束执行并消亡了。一般线程里抛出了异常,执行return或者代码执行完毕都会使线程终止。

四、Java创建线程的两种方法

Java创建线程有三种方式,继承Thread类,实现Runnable接口,以及通过Callable和FutureTask创建。最后一种暂时不做介绍。

4.1、继承Thread类

/** * @function:学习使用继承Thread的方法实现多线程 * @author: 程志军 * @date: 2020/3/6 下午7:53 **/ class Thread1 extends Thread{ private String name; public Thread1(String name) { this.name = name; } public void run(){ for (int i=0;i public static void main(String[] args) { Thread1 t1=new Thread1("A"); Thread1 t2=new Thread1("B"); t1.start(); t2.start(); } }


在这里我们编写了一个Thread1继承Thread类,重写了run方法。新创建的线程执行的代码写在run方法里。

我们发现多线程各个线程的执行顺序是不固定的,每次运行都可以是不同的运行顺序,因此多线程适用于运行顺序乱序的场景。若需要顺序访问或互斥访问时需要用上synchronized。

注意:在运行t.start()时线程不会马上执行,而是进入就绪态,等到系统调度抢到了cpu资源再运行,如结果所示,先执行t1.start(),却先打印了了t2线程执行的信息。

4.2、实现Runnable接口

接着我们编写Thread2类实现Runnable接口

/** * @function: 学习使用实现runnable接口实现多线程 * @author: 程志军 * @date: 2020/3/6 下午11:35 **/ public class Thread2 implements Runnable{ private String name; public Thread2(String name){ this.name = name; } @Override public void run() { for(int i=0;i public static void main(String args[]) throws InterruptedException { Thread t1 = new Thread(new Thread2("A")); Thread t2 = new Thread(new Thread2("B")); t1.start(); t2.start(); } }


在这里我们编写了一个Thread2类实现了Runnable接口,要运行的代码逻辑写在run方法里。
我们来看看Thread的构造方法

/ ** @param target * the object whose {@code run} method is invoked when this thread * is started. If {@code null}, this classes {@code run} method does * nothing. */ public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }

按照注释的说明,我们在new Thread(Runnable target)构造一个线程时,实际上传进的参数便是实现了run方法的类对象,在线程执行start方法时进入就绪状态等待执行run方法。

因此继承Thread类和实现Runnable接口本质都是实现run方法,而我们也更推荐使用Runnable接口的方法,一是更利于线程间资源的共享,二是可以避免Java单继承的限制,即Java类只能继承一个类,但却可以实现多个接口。

注:不能直接t.run()运行线程,这样相当于执行了一个普通类的普通方法,只有t.start()才会通过新建一个线程来执行run方法里的代码。

Runnable接口拓展:使用lambda表达式

既然只是继承接口实现方法,那我们便可使用lambda匿名内部类的方式实现

/** * @function: * @author: 程志军 * @date: 2020/3/26 下午9:56 **/ public class ThreadCreat { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()->{ for (int i = 0; i for (int i = 0; i private int num=0; public synchronized void increase() throws InterruptedException { if (num!=0){ wait(); } num++; System.out.println(num); notify(); } public synchronized void decrease() throws InterruptedException { if (num!=1){ wait(); } num--; System.out.println(num); notify(); } public static void main(String[] args) { Pool pool = new Pool(); new Thread(()->{ for (int i = 0; i pool.decrease(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(()->{ for (int i = 0; i pool.increase(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }


我们编写了increase和decrease两个同步方法(synchronized加在方法上时的对象锁即为this,后面的wait()和notify实际也都是this.wait()和this.notify()),然后新建了两个线程执行increase和decrease方法。

开始执行时,第一个线程因num=0而进入等待状态,第二个线程执行了num++,接着唤醒了第一个线程使其执行num–,第一个线程再唤醒第二个线程,循环五次,顺序打印了10五次.

5.2、sleep(),yield(),join()

Thread.sleep(long millis): 当前线程释放cpu资源睡眠指定的毫秒数,但并不释放持有的锁资源。

Thread.yield(): 让步函数,当前线程由运行态变成就绪态。当前线程重新进入就绪态与其它线程一起再抢夺cup资源,可能是自己再次运行,也可能不是。

5.3、setPriority()

设置线程的优先级,Java线程设置1-10优先级依次从低到高。需要注意的是,优先级高的不是一定会比优先级低的先执行,而是先执行的概率更大。

MIN_PRIORITY = 1 NORM_PRIORITY = 5 MAX_PRIORITY = 10

线程默认的优先级为5,通过t.setPriority()可以设置线程的优先级,但必须要在t.start()之前设置,否则无效。

六、synchronized的使用

synchronized的内容也是一个很大的板块,在这里就不展开介绍了,下篇博客再来详细介绍。

收藏
  • 人气文章
  • 最新文章
  • 下载排行榜
  • 热门排行榜