进程是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,
线程实际上时进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程。
线程实际上是在进程基础上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分为若干个线程。
线程调度:
1.分时调度:
- 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间,早期电脑的DOS系统只能同时执行一件事情。
2.抢占式调度:
- 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度,优先级越高,抢占到时间片(CPU分出来的一个个时间)的概率越大。CPU空闲时会主动抛出时间片。
CPU使用抢占式调度模式在多个线程内进行着高速的切换。对于CPU的一个核心而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对我们而言感觉要快,看上去就是在同一个时刻运行,其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
同步与异步:
- 同步:排队执行,效率低但是安全。
- 异步:同时执行,效率高但是数据不安全。
并发与并行:
- 并发:指两个或多个事件在同一个时间段内发生。
- 并行:指两个或多个事件在同一时刻发生(同时发生)。
- Java实现多线程技术:
1.继承Thread:
- run方法里的代码就是一条新的执行路径。这个路径的执行方式,不是调用run方法,而是通过调用对象的start方法来启动线程。两个线程谁先执行谁后执行是不确定的。
- 这些事情都在JVM内部发生。
- 程序启动-->main线程开启-->main方法执行-->创建了一个线程-->线程启动-->main线程与新创建的线程抢占CPU的时间执行-->两个线程都结束了程序才结束
- 每个线程都拥有自己的栈空间(即线程创建时会创建属于自己的栈),共用一个堆空间。
- 可以通过匿名内部类的方式实现线程的实现与启动。
2.类实现Runnable接口:
实现Runnable接口与继承Thread相比有以下优势:
- 通过创建任务,然后给线程分配任务的方式来实现的多线程,更适用于多个线程执行相同任务的情况。
- 可以避免单继承所带来的局限性。
- 任务与线程本身是分离的,提高了程序的健壮性。
- 后续学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程。
stop方法并不能停止线程,它是不安全的。可以使用变量做标记的方式(flag),interrupt方法来实现线程的停止。
sleep方法用于休眠线程(暂时停止线程)。
当所有用户线程死亡时,守护线程也会自动死亡。
Thread.CurrentThread().getName获取线程名称。
Thread.CurrentThread().setName设置线程名称。
线程阻塞指的是线程中比较消耗时间的操作,比如获取用户输入,线程休眠,在线程中读取文件等操作。
在继承了Thread或者实现了Runnable的类的run方法里return可以直接结束一个线程。
线程的中断:
- 一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定,而不是由外部将其结束。外部结束线程会导致线程中一些操作如文件读取没有完成就中途结束了,会导致内存垃圾或者其他线程无法读取该文件的问题的产生。要结束一个线程要让其自身决定,可以使用变量做标记的方式(flag),一旦标记成立return,或者interrupt方法中断线程,catch到interrupt方法产生的异常后在catch块中return来实现让线程自身决定是否结束。
守护线程:
- 起到守护用户线程的作用。
- setDaemon(true)设置线程为守护线程。
线程不安全:
- 几个线程同时操作一个数据。
线程同步:
- 同步代码块:格式:synchronized(锁对象){},锁对象:Java中任何对象都可以作为锁对象存在。关于如何识别对象是否已经被上锁,是内存底层的一些机制(可以了解一下)。
- 同步方法:给方法加上锁格式:在方法的访问权限修饰符后面加上synchronized修饰符,排队的对象是方法。
同步代码块与同步方法都属于隐式锁。
显示锁:
//创建显示锁。
Lock l = new ReentrantLock();
//给代码块上锁。
l.lock();
//要锁住的代码块。
................
//给代码块解锁。
l.unlock();
显示锁与隐式锁的区别:
公平锁:
先来先到,排队执行。
不公平锁:
谁先抢到谁执行。Java中默认为不公平锁。在显示锁中将参数设置为true就是公平锁。
Lock l = new ReentrantLock(true);
线程死锁:
线程之间在互相等待资源,在任何有可能产生锁的方法里不要再调用另一个产生锁的方法。
生产者与消费者问题
线程的六种不同的状态:
- new:线程刚被创建但是还没有启动。
- runnable:执行的线程。
- Blocked:被阻塞等待监视器锁定的状态。
- waiting:无限期等待另一个线程执行特定操作的线程处于此状态。
- TimedWaiting:正在等待另一个线程执行最多执行等待时间的操作的线程处于此状态。
- Terminated:已退出的线程。
带返回值的线程Callable:
Callable的get方法获取返回值会导致前面的线程停止,等待返回值后再运行。其中还有一些方法可以了解一下。
线程池:
一个容纳线程的容器,其中的线程可以反复利用,线程实现了反复使用,Java中有四种不同的线程池:
- 缓存线程池:长度无限制
- 定长线程池
- 单线程线程池
- 周期定长线程池
Lambda表达式:接口必须只有一个方法才能使用Lambda表达式
Thread类中一些方法的使用:
package com.javaEELesson.day08;
import java.util.Map;
public class Demo2{
public static void main(String[] args) {
/**
* Thread的构造方法:
* Thread() 分配新的 Thread对象。
* Thread(Runnable target) 分配新的 Thread对象。
* Thread(Runnable target, String name) 分配新的 Thread对象。
* Thread(String name) 分配新的 Thread对象。
* Thread(ThreadGroup group, Runnable target) 分配新的 Thread对象,并且属于 group引用的线程组。
* Thread(ThreadGroup group, Runnable target, String name) 分配新的 Thread对象,使其具有 target作为其运行对象,具有指定的 name作为其名称,并且属于 group引用的线程组。
* Thread(ThreadGroup group, Runnable target, String name, long stackSize) 分配新的 Thread对象,使其具有 target作为其运行对象,具有指定的 name作为其名称,并且属于 group引用的线程组,并具有指定的 堆栈大小 。
* Thread(ThreadGroup group, Runnable target, String name, long stackSize, boolean inheritThreadLocals) 分配新的Thread对象,使其具有target作为其运行对象,具有指定的name作为其名称,属于group引用的线程组,具有指定的stackSize ,并且如果inheritThreadLocals是true ,则继承inheritable thread-local变量的初始值。
* Thread(ThreadGroup group, String name) 分配新的 Thread对象,并且属于 group引用的线程组。
*/
/**
* static int activeCount():返回当前线程thread group及其子组中活动线程数的估计值。
* 返回的值只是一个估计值,因为当此方法遍历内部数据结构时,线程数可能会动态更改,并且可能会受到某些系统线程的影响。此方法主要用于调试和监视目的。
* 结果:
* 估计当前线程的线程组和当前线程的线程组作为祖先的任何其他线程组中的活动线程数。
*
* 这里为什么打印的是2不是1的原因:
* IntelliJ IDEA执行用户代码的时候,实际是通过反射方式去调用,而与此同时会创建一个Monitor Ctrl-Break线程用于监控目的。
* 所以打印的是2(main线程和Monitor Ctrl-Break线程)。
*/
System.out.println(Thread.activeCount());
/**
* static Thread currentThread():返回对当前正在执行的线程对象的引用。
* 结果:
* 当前正在执行的线程。
*
* 因为现在只有main线程在运行所以这里调用方法返回的是main线程。
*/
System.out.println(Thread.currentThread());
/**
* void checkAccess():确定当前运行的线程是否具有修改此线程的权限。
* 如果不允许当前线程访问此线程抛出SecurityException异常,这里是允许访问,所以不抛出异常。
*/
Thread.currentThread().checkAccess();
/**
* static void dumpStack():将当前线程的堆栈跟踪打印到标准错误流。此方法仅用于调试。
* 这里不调用这个方法,因为调用了程序就直接结束运行。
*/
//Thread.dumpStack();
/**
* static int enumerate(Thread[] tarray):将当前线程的线程组及其子组中的每个活动线程复制到指定的数组中。
* 要注意的是,复制到数组中的线程并不是创建了新线程,还是原来的线程,可以理解为两个指针指向了同一线程。对数组中的线程进行
* 操作相当于是对原该线程进行操作。我们这里试着改变一下数组中main线程的名称,发现原来main线程的名称确实被更改了。
*/
Thread[] tarray = new Thread[2];
Thread.enumerate(tarray);
for (Thread t : tarray) {
System.out.println(t.getName());
}
tarray[0].setName("主线程名现在是哈哈哈");
System.out.println(Thread.currentThread().getName());
//将线程名称改回来。
tarray[0].setName("main");
/**
* static Map<Thread,StackTraceElement[]> getAllStackTraces():返回所有活动线程的堆栈跟踪映射。
* 返回所有活动线程的堆栈跟踪映射。映射键是线程,每个映射值是一个StackTraceElement的数组,表示相应的Thread的堆栈转储。返回的堆栈跟踪采用为getStackTrace方法指定的格式。
* 调用此方法时,线程可能正在执行。每个线程的堆栈跟踪仅表示快照,并且可以在不同时间获得每个堆栈跟踪。如果虚拟机没有关于线程的堆栈跟踪信息,则将在映射值中返回零长度数组。
* 如果有安全管理器,则调用安全管理器的checkPermission方法,并获得RuntimePermission("getStackTrace")权限以及RuntimePermission("modifyThreadGroup")权限,以查看是否可以获取所有线程的堆栈跟踪。
*
* 结果 :
* 一个 Map表示Thread与数组StackTraceElement的映射,它表示相应线程的堆栈跟踪。
*/
Map<Thread,StackTraceElement[]> map = Thread.getAllStackTraces();
//我们查看一下到目前为止main线程的堆栈跟踪。
StackTraceElement[] stes = map.get(Thread.currentThread());
for (StackTraceElement ste : stes) {
System.out.println(ste);
}
/**
* long getId():返回此线程的标识符。
* 要注意的是线程ID是唯一的,并且在其生命周期内保持不变。 当线程终止时,可以重用该线程ID。所以我们无法设置线程的ID。
*/
long ID = Thread.currentThread().getId();
System.out.println(ID);
/**
* String getName():返回此线程的名称。
be749cba8475 6 月前
77d3da8d0787 6 月前