关键词:
线程概述
运行一个音乐播放器播放一首歌,音乐播放器就是一个进程,在程序执行时,既有声音的输出,同时还有该歌曲的字幕展示,这就是进程中的两个线程
线程与进程
程序进入内存就变成了进程,进程就是处于运行中的程序
进程特征:
- 独立性:每个进程都有自己的私有地址,一个进程不能直接访问其他进程
- 动态性:进程有自己的生命周期和不同状态,而程序不具备
- 并发性:多个进程可以在单个处理器上并发执行,进程之间互不影响
并发: 进程在cpu中切换执行 并行:进程在cpu上一起执行
对于一个CPU而言,它在某个时间点只能执行一个程序,也就是说,只能运行一个进程,
CPU不断地在这些进程之间轮换执行,虽然CPU在多个进程间轮换执行,但是我们感觉到好像有多个进程在同时进行
线程是进程的执行单元,对于绝大多数的应用程序来说,通常仅要求有一个主线程,
但也可以在该进程内创建多条顺序执行流,这些顺序执行流就是线程(子线程),
每个线程也是相互独立的
线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,
但不拥有系统资源,它与父进程的其他线程共享该进程所有拥有的全部资源
一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
多线程的优势
- 进程中的线程之间的隔离程度要小。它们共享内存、文件句柄和其他的每个线程的状态
- 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,提高运行效率
- 线程共享的环境包括:进程代码段、进程的公有数据
- 进程之间不能共享内存,但线程之间共享内存非常容易
- 系统创建进程是需要为该进程重新分配系统资源,但创建线程则代价小得多
线程的创建与启动
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。
每个线程的作用是完成一定的任务,实际上就是执行一段程序代码。
Java使用线程执行体来代表这段程序代码。
Ø 继承Thread创建线程
-
定义Thread类的子类,并重写该类的run()方法,run()方法的方法体代表线程需要完成的任务。因此把run方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程。
public class Test2 extends Thread // 重写run方法,run方法的方法体就是子线程的执行体 @Override public void run() for (int i = 0; i < 100; i++) // 继承Thread类后,从父类继承的getName方法可以获取当前线程的名称 System.out.println("线程名称:"+this.getName()+" "+i); public static void main(String[] args) for (int i = 0; i < 100; i++) // 调用Thread的currentThread()方法获取当前线程对象 // 这里就不能用this来获取name了 System.out.println("线程名称;"+Thread.currentThread().getName()+"="+i); //创建两个子线程,并运行 if (i == 20) new Test2().start(); new Test2().start();
线程是以抢占式的方式运行的,虽然只创建了两个线程实例,实际上有三个线程在运行
(两个子线程,一个主线程main)
通过setName(String name)的方式来为线程设置名称,也可以通过getName的方式来得到线程的名称。
在默认情况下,主线程的名称为main,用户启动的多线程的名称依次为Thread-0,Thread-1,Thread-3..Thread-n
实现Runnable接口创建线程
- 定义Runnable接口的实现类,并重写该接口的run方法
-
创建Runnable实现类的实例对象,并以此实例对象作为Thread的target来创建Thread类,该Thread对象才是真正的线程对象。
- 调用线程对象的start()方法来启动该线程
public class Test3 implements Runnable @Override public void run() for(int i = 0;i < 100;i++) // 实现了Runnable接口的类其本质并不是线程类,因此没有getName方法, // 因此需要通过Thread类来获取当前线程,仅仅是一个任务体,仍需交给Thread去执行 System.out.println("线程名称:"+Thread.currentThread().getName()+" "+i); public static void main(String[] args) /*new一个实现了Runnable接口的实例,这个实例不是线程对象 * 不能test3.start()来运行子线程,执行run方法体 * 实际的线程对象要通过new Thread()来获取,只不过对于实现了Runnable接口的实现类的实例 * 作为参数传入到new Thread(test3).start()来执行子线程 * 意义是让线程对象来执行test3实例的run方法体 * */ Test3 test3 = new Test3(); new Thread(test3).start(); new Thread(test3).start();
又因为Runnable是一个函数式接口,所以可以使用lamda表达式来进行代码编写
public class Test4 public static void main(String[] args) /* * 用lamda表达式的写法,里面写的就是run方法体 * 将runnable传入到 new Thread(runnable,"子线程1")里面,就表示了创建了子线程 * 并执行run方法体,第二个参数是为子线程起名字 * */ Runnable runnable = ()-> for (int i = 0; i < 100; i++) System.out.println("线程名字:"+Thread.currentThread().getName()+"="+i); ; Test4 test4 = new Test4(); new Thread(runnable,"子线程1").start(); new Thread(runnable,"子线程2").start();
通过对比上面两种创建线程的方式,继承Thread 和 实现Runnable接口,第一种主线程和子线程
分别执行一遍任务。第二种主线程和子线程共同完成一个任务。
Ø 使用Callable&Future创建线程
在Java 1.5开始,Java提供了Callable接口,该接口实际上可看成是Runnable接口的增强版,Callable接口提供了一个call()的方法可以作为线程的执行体,但call()方法比run()方法功能更加强大。
这是因为:
1. call()方法可以有返回值
2. call()方法可以声明抛出异常
因此我们可以提供一个Callable对象作为Thread的target,而该线程的线程执行体就是该Callable对象的call()方法。但是存在以下两个问题:
1. Callable接口是Java 5提供的一个新的接口而不是Runnable接口的子接口,所以Callable对象不能直接作为Thread类的target目标执行类。
2. call()方法还有一个返回值——call()方法并不是直接调用,它是作为线程执行体被调用的。如何获取call()方法的返回值?
为了解决以上两个问题,Java 1.5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runable接口——可以作为Thread类的target。
创建并启动有返回值的线程的步骤如下:
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且该call()方法有返回值
-
创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程
- 调用FutureTask对象的get()方法来获得子线程结束后的返回值
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * 实现Callable接口时指定的泛型为返回值的类型 */ public class Test5 implements Callable<Integer> /* * 对于实现了Callable接口的类,重写的call方法就是子线程要执行的方法体 * 这个call方法与run方法的区别就是,call有返回值,可以声明抛出异常 * * 实现的Callable接口可以看做是Runnable接口的增强版,所以可以提供一个Callable对象作为target传给线程对象 * 但是问题就是,Callable接口不是Runnable接口的子接口,不能直接作为target * call方法有返回值 * * */ @Override public Integer call() throws Exception for (int i = 0; i < 100; i++) /*与实现了Runnable接口的run方法相似,也是不能使用this来获取name*/ System.out.println("当前线程名称:"+Thread.currentThread().getName()+" "+i); Thread.sleep(200); return 100; public static void main(String[] args) throws ExecutionException, InterruptedException /*创建Callable对象,因为当前类实现了Callable接口 多态*/ Callable<Integer> callable = new Test5(); // 创建FutureTask对象,并将call对象封装在FutureTask内部,FutureTask的泛型为Callable FutureTask<Integer> futureTask = new FutureTask<>(callable); /*创建线程对象*/ new Thread(futureTask).start(); // 获取线程结束后的返回值 System.out.println("线程执行结束后的返回值:"+futureTask.get());
Callable接口是一个函数式接口,所以可以用lamda表达式写法
import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Test6 public static void main(String[] args) throws Exception /* * Callable接口也是一个函数式接口,所以可以用lamda表达式写法 * 里面写的就是call的方法体 * */ Callable<Integer> callable = ()-> for (int i = 0; i < 100; i++) System.out.println("线程名称:"+Thread.currentThread().getName()+"="+i); return 100; ; // 创建FutureTask对象,并将call对象封装在FutureTask内部,FutureTask的泛型为Callable FutureTask<Integer> futureTask = new FutureTask<>(callable); /*创建线程对象 并将FutureTask封装好的Callable对象传入线程对象中*/ new Thread(futureTask).start(); // 获取线程结束后的返回值 System.out.println("线程执行结束后的返回值:"+futureTask.get());
Ø 创建线程的三种方式比较
通过继承Thread类或实现Runnable、Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常,并且Callable需要FutureTask来进行封装成Thread可识别的target目标。因此可以将实现Runnable接口和实现Callable接口归纳为一种方式。这种方式与继承Thread方式之间的主要差别如下
线程的声明周期
当线程被创建并启动后,并不是一启动就进入了执行状态,也不是一直处于执行状态,
在线程的生命周期中,它要经历新建、就绪、运行、阻塞和死亡5种状态。
尤其是当线程启动以后,它不可能一直占用CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会在运行、阻塞之间切换。
新建状态:当new了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样仅仅由Java虚拟机为其分配内存,并初始化其他成员变量的值
就绪状态:当线程对象调用了start方法之后,该线程就处于就绪状态,Java虚拟机会为这个线程对象创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于什么时候开始运行,则取决于JVM里的线程调度器的调度。
运行状态:处于就绪状态的线程获得了CPU,开始执行run方法的线程执行体,则该线程就处于运行状态
阻塞状态:
- 线程调用sleep()方法主动放弃所占用的处理器资源
- 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
- 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有
-
线程在等待某个通知(notify)
- 程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法
死亡状态:
- run()或call()方法执行完成,线程正常结束
- 线程抛出一个未捕获的Exception或者直接Error错误
- 直接调用该线程的stop()方法来结束该线程——该方法容易导致死锁,通常不推荐使用
当主线程结束时,其他线程不受任何影响,并不会随之结束。一旦子线程启动之后,它就拥有和主线程相同的地位
不要试图对一个已经死亡的线程调用start()方法使它重新启动,死亡就是死亡,该线程不可以再次作为线程执行。
控制线程
多线程详解(代码片段)
一.多线程的创建与启动1、继承Thread类:步骤:①、定义类继承Thread;②、复写Thread类中的run方法;目的:将自定义代码存储在run方法,让线程运行③、调用线程的start方法: 该方法有两步:启动线程,调用run方法。不建议使... 查看详情
cyclicbarrier详解(代码片段)
CyclicBarrier详解简介阻塞一组线程,直到某个事件发生.所有线程必须都到达栅栏位置时,才能继续执行.使得一定数量的线程反复在栅栏位置汇集.需要等待集合的线程调用await()方法在栅栏处阻塞.栅栏释放阻塞的线程后会重置以便下... 查看详情
java线程池详解(代码片段)
构造一个线程池为什么需要几个参数?如果避免线程池出现OOM?Runnable和Callable的区别是什么?本文将对这些问题一一解答,同时还将给出使用线程池的常见场景和代码片段。基础知识Executors创建线程池Java中创建线程池很简单,... 查看详情
java线程池详解(代码片段)
构造一个线程池为什么需要几个参数?如果避免线程池出现OOM?Runnable和Callable的区别是什么?本文将对这些问题一一解答,同时还将给出使用线程池的常见场景和代码片段。基础知识Executors创建线程池Java中创建线程池很简单,... 查看详情
javadaemonthread守护线程详解(代码片段)
原文链接:https://www.cnblogs.com/ziq711/p/8228255.html用户线程和守护线程在Java中有两类线程:UserThread(用户线程)、DaemonThread(守护线程) 用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:只要当前JVM实例... 查看详情
线程池拒绝策略详解(代码片段)
文章目录1.前言2.ThreadPoolExecutor创建线程方式3.ThreadPoolExecutor拒绝策略测试3.1.AbortPolicy3.2.CallerRunsPolicy3.3.DiscardPolicy3.4.DiscardOldestPolicy3.5.自定义拒绝策略4.第三方实现的拒绝策略4.1.dubbo中的线程拒绝策略4.2.Netty中的线程池拒绝策略4.3... 查看详情
线程池拒绝策略详解(代码片段)
文章目录1.前言2.ThreadPoolExecutor创建线程方式3.ThreadPoolExecutor拒绝策略测试3.1.AbortPolicy3.2.CallerRunsPolicy3.3.DiscardPolicy3.4.DiscardOldestPolicy3.5.自定义拒绝策略4.第三方实现的拒绝策略4.1.dubbo中的线程拒绝策略4.2.Netty中的线程池拒绝策略4.3... 查看详情
exchanger详解(代码片段)
Exchanger详解简介当一个线程到达栅栏时,会检查是否有其他线程已经到达栅栏.若没有,则该线程进入等待.若有,则与等待的其他线程交换各自的数据,然后继续执行.原理内部类Participant继承自ThreadLocal,用来保存线程本地变量Node.Node存... 查看详情
reentrantreadwritelock详解(代码片段)
...ntrantReadWriteLock详解简介特点:ReentrantReadWriteLock允许多个读线程同时访问,不允许写线程和读线程,写线程和写线程同时访问.一般情况下,共享数据的读操作远多于写操作,比ReentrantLock提供更好的并发性和吞吐量.读写锁内部维护两个... 查看详情
java多线程详解(代码片段)
多线程创建方式Thread类定义一个子类MyThread继承线程类Java.lang.Thread,重写run()方法创建MyThread对象调用线程对象的start()方法启动线程(启动后还是执行run方法)优缺点优点:编码简单缺点:线程类已经继承Thread,无法继承其他类... 查看详情
java多线程:线程池详解(代码片段)
文章目录1.线程池1.1线程池概述1.1.1线程池的概念1.1.2线程池的工作机制1.1.3使用线程池的原因1.1.3线程池的设计思路:1.2线程池的创建1.2.1Executors默认线程池newCachedThreadPool:创建默认线程池,最多容纳int类型的最大值newFixedT... 查看详情
java多线程详解(代码片段)
线程对象是可以产生线程的对象。比如在Java平台中Thread对象,Runnable对象。线程,是指正在执行的一个指点令序列。在java平台上是指从一个线程对象的start()开始,运行run方法体中的那一段相对独立的过程。相比于多进程,多线... 查看详情
线程详解(代码片段)
线程概述运行一个音乐播放器播放一首歌,音乐播放器就是一个进程,在程序执行时,既有声音的输出,同时还有该歌曲的字幕展示,这就是进程中的两个线程线程与进程程序进入内存就变成了进程,进程就是处于运行... 查看详情
ios多线程详解(代码片段)
iOS多线程详解Slogan:可能是最通俗易懂的iOS多线程详细解析文章1.基础概念1.1进程进程是计算机中已运行程序的实体,是线程的容器维基百科-进程。每个进程之间是相互独立的,每个进程均运行在器专用且收保护的内存空... 查看详情
线程安全性详解(代码片段)
线程安全性定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的线程... 查看详情
02java进阶--线程池详解(代码片段)
线程池线程池做的工作主要是控制运行的线程的数量,处理过程中将任务加入队列,然后在线程创建后,启动这些任务,如果线程超过了最大数量,超出的数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行... 查看详情
狂神说java笔记--多线程详解部分笔记(代码片段)
传送门==>B站遇见狂神说Java多线程详解做笔记时有的知识点并没有整理;ml1.线程创建之继承Thread类图片下载练习2.线程创建之实现Runnable接口买票案例模拟龟兔赛跑3.线程创建之实现Callable接口4.静态代理模式5.Lambda表达式6.... 查看详情
c#多线程thread实例详解(代码片段)
1.Thread线程启动由于ThreadStart是一个委托,所以可以简化写法staticvoidMain(string[]args)Console.WriteLine("----------主程序开始,线程ID是0-----------------",Thread.CurrentThread.ManagedThreadId);for(inti=0;i<5;i++)ThreadSta 查看详情