这段笔记是参照b站教程BV1Rv411y7MU整理而来的。用于个人备忘以便复习,有需要的朋友可以自取。
线程概述
1. 概念
-
线程
- 线程是进程的一个执行单元。
- 一个线程就是进程中一个单一顺序的控制流,是进程的一个执行分支。
- 进程是线程的容器,一个进程中至少有一个线程。在操作系统中是以进程为单位分配资源的。
- 每个线程都有各自的线程栈、寄存器环境和本地存储。
-
进程
- 进程是计算机程序关于某数据集合上的一次运行活动,是操作系统进行资源调度和分配的基本单位。
-
主线程和子线程
- JVM启动的时候会创建一个主线程,该线程负责执行main方法,主线程就是运行main方法的线程。
- Java中线程不是孤立的,线程之中存在一些联系,如果在A线程中创建了B线程,则称B线程为A线程的子线程,相对A线程就为B线程的父线程。
-
串行、并发和并行
- 并发 可以提高对事务的处理效率,在一段时间内可以处理或者完成更多的事情。
- 并行 是一种更为严格的并发。
- 从硬件角度来说,如果是单核CPU,一个处理器一次只能执行一个线程的情况下,处理器可以使用时间片轮转技术,可以让CPU快速的在各个线程之间切换;对于用户来说,感觉则是多个进程同时进行。如果是多核CPU,则可以为每个线程分配不同的CPU内核。
2. Thread操作
线程创建
public class MyThread extends Thread{
@Test
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("Sub Thread : "+i);
int time = (int) (Math.random()*1000);
try {
Thread.sleep(time); /*线程睡眠,单位为毫秒*/
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
常用方法
- currentThread()方法
Thread.currentThread()
方法可以获得当前线程。
Java中任何一段代码都是执行在某个线程中的,执行当前代码的线程就是当前线程。
同一段代码可以被不同的线程执行,因此当前线程是相对的。
Thread.currentThread()
返回值是代码实际运行时候的线程对象。
/*
* MyThread方法
*/
public class MyThread extends Thread{
public MyThread(){
System.out.println("打印执行构造函数的线程 :"+Thread.currentThread().getName());
}
@Override
public void run() {
System.out.println("打印执行run方法的线程 :" + Thread.currentThread().getName());
}
}
/*
* Main方法
*/
public class ThreadPoolExecutorDemo {
public static void main(String[]args){
System.out.println("打印执行main方法的线程 :"+Thread.currentThread().getName());
MyThread myThread = new MyThread();
myThread.start();//直接使用myThread.run()则为main线程执行run()方法
}
}
/*打印结果为:
* 打印执行main方法的线程 :main
* 打印执行构造函数的线程 :main
* 打印执行run方法的线程 :Thread-0
* 因为MyThread的构造函数在main方法中调用,所以执行这段代码的是main线程
* 而start()函数是调用子线程执行方法,所以执行run()方法的是子线程
*/
public class MyThread extends Thread{
public MyThread(){
System.out.println("打印执行构造函数的线程-1 :"+Thread.currentThread().getName());
System.out.println("打印执行构造函数的线程-2 :"+this.getName());
}
@Override
public void run() {
System.out.println("打印执行run方法的线程-1 :" + Thread.currentThread().getName());
System.out.println("打印执行run方法的线程-2 :" + this.getName());
}
}
public class ThreadPoolExecutorDemo {
public static void main(String[]args) throws InterruptedException {
//创建子线程对象
MyThread myThread = new MyThread();
myThread.setName("xiye");//设置线程名字
myThread.start();
Thread.sleep(500);
//Thread(Runnable)构造方法是Runnable接口,调用时传递的实参是接口的实现类方法
Thread thread = new Thread(myThread);
thread.start();
}
}
/*
* 打印执行构造函数的线程-1 :main
* 打印执行构造函数的线程-2 :Thread-0
* 打印执行run方法的线程-1 :xiye
* 打印执行run方法的线程-2 :xiye
* 打印执行run方法的线程-1 :Thread-1
* 打印执行run方法的线程-2 :xiye
*myThread线程子主线程中创建,但是构造方法中的this指向自己,所以此时返回应该是子线程的名字。
*随后我们对子线程进行了更名,调用子线程的run()方法的时候,Thread.currentThread()和this都指向子线程,所以输出结果一样。
*Runnable接口不会调用构造函数,而创建thread的另一个子线程,所以打印Thread-1.Runnable接口中传入的是myThread,所以调用start()的时候是myThread线程执行。
*/
-
setName()/getName()
thread.setName()
设置线程名称thread.getName()
得到线程名称- 通过设置线程名称可以提高程序可读性,所以建议为每个线程都设置一个可以体现线程功能的名称。
-
isAlive()
thread.isAlive()
可以判断当前线程是否处于活动状态。- 活动状态 :线程已启动,并且尚未终止。
-
sleep()
Thread.sleep()
让当前线程休眠指定毫秒数。且当前线程指Thread.currentThread()
返回的线程。
@Test
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"启动时间 : "+System.currentTimeMillis());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束时间 : "+System.currentTimeMillis());
}
简易计时器
@Test
@Override
public void run() {
int remaining=60;//计时器
while (true){
System.out.println("Remaining :"+remaining);
remaining--;
if(remaining < 0)//结束条件
break;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("End!");
}
-
getId()
Thread.getId()
返回线程的唯一编号- 某个编号线程运行结束后,该编号可能被后续创建的进程使用。
- JVM重启后,同一个线程编号可能不一样。
-
yield()
Thread.yield()
方法的作用的放弃当前CPU资源,
public class MyThread extends Thread{
@Override
public void run() {
long begin = System.currentTimeMillis();
long sum=0;
for(int i=1;i<=1000000;i++){
sum += i;
Thread.yield();
}
long end = System.currentTimeMillis();
System.out.println("子线程用时:"+(end-begin));
}
}
public class ThreadPoolExecutorDemo {
public static void main(String[]args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//在main线程中计算累加和
long begin = System.currentTimeMillis();
long sum=0;
for(int i=1;i<=100000000;i++){
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("main用时:"+(end-begin));
}
}
- setPriority()
thread.setPriority()
可以设置线程的优先级。- 线程优先级取值范围为1-10,如果超出范围则会抛出IllegalArgumentException异常。
- os中,优先级越高的线程获得的CPU资源就越多。
- 线程的优先级本质上是给线程调度器的提示信息,便于线程调度器决定先调度哪些线程。
- 不能保证优先级高的线程先运行。
- Java优先级设置不当或滥用,可能导致某些线程永远无法运行,即产生线程饥饿。
- 线程优先级并不是设置越高越好,一般开发情况下不必设置线程优先级。
public class Thread_A extends Thread{
@Override
public void run() {
int sum=0;
long begin = System.currentTimeMillis();
for(int i=0;i<100000000;i++){
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println("Thread_A Time : "+(end-begin));
}
}
public class Thread_B extends Thread{
@Override
public void run() {
int sum=0;
long begin = System.currentTimeMillis();
for(int i=0;i<100000000;i++){
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println("Thread_B Time : "+(end-begin));
}
}
public class Main {
public static void main(String[]args){
Thread_A thread_a = new Thread_A();
thread_a.setPriority(1);
thread_a.start();
Thread_B thread_b = new Thread_B();
thread_b.setPriority(10);
thread_b.start();
}
}
- interrupt()
thread.interrupt()
用于中断线程。- 调用此方法仅仅是在当前线程打上一个停止标志,并不是真的停止进程。
public class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<1000000;i++){
//判断线程额中断标志,线程isInterred()方法,该方法返回线程中断标志
if(this.isInterrupted())
return;
System.out.println("子线程 -> "+i);
}
}
}
public class ThreadPoolExecutorDemo {
public static void main(String[]args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();//开启子线程
//主线程
for (int i=0;i<100;i++){
System.out.println("main -> "+i);
}
//for循环结束后中止子线程
myThread.interrupt();
}
}
- setDaemon()
- Java中线程分为***用户线程*** 和***守护线程***。守护线程是为其他线程提供服务的线程,例如垃圾回收器(GC)就是一个典型的守护线程。
- 守护线程不能单独运行,当JVM中没有其他用户线程,只有守护线程的时候,守护线程会自动销毁,且JVM会退出。
thread.setDaemon(true)
将线程设置为守护线程。
下方例子中,在运行时main线程结束后但是子线程仍然在控制台有输出。原因为main线程销毁的时候守护线程还在运行,当main销毁步骤完成后守护线程停止,此时没有其余用户线程,JVM关闭。
public class MyThread extends Thread{
@Override
public void run() {
super.run();
while (true){
System.out.println("Sub Thread...");
}
}
}
public class ThreadPoolExecutorDemo {
public static void main(String[]args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.setDaemon(true);//在线程启动之前设置为守护线程
myThread.start();
for(int i=0;i<100;i++){
System.out.println("main -> "+i);
}
}
}
3. 生命周期
- 线程的生命周期即线程对象的生老病死,即线程状态。
- 线程生命周期可以通过
getState()
方法获得,线程状态是Thread.State
枚举类型,有以下几种定义方式:Thread.State Description NEW 新建状态,创建了线程对象,在调用start()启动之前。 RUNNABLE 可运行状态,是一个复合状态,包含:READY和RUNNING两个状态。READY表示该线程可以被线程调度器调度使其处于RUNNING状态。RUNNING状态表示该线程正在执行【Thread.yield()方法可以将RUNNING转换为READY状态】 BLOCKED 阻塞状态,线程发起一个阻塞的I/O操作,或者申请一个由其他线程占用的独占资源。线程会转换为BLOCKED状态,处于阻塞状态的线程不会占用CPU资源,当阻塞I/O操作执行完,或者线程获得了其申请的资源,线程可以转换为RUNNABLE。 WAITING 等待状态,线程执行了object.wait(),thread.join()方法会把线程转换为WAITING状态,执行object.notify()或者加入的线程执行完毕后,当前线程会转换乘RUNNABLE状态 TIMED_WITING 与WAITING状态类似,都为等待状态。但处于TIMED_WAITING的线程不会无限的等待,如果线程没有在指定的时间范围内完成期望的任务,该线程会自动转换为RUNNABLE。 TERMINATED 终止状态,线程结束处于终止状态。
4.多线程编程的优势与存在的风险
优势:
- 可以提高系统的吞吐率(Throughot)。多线程变成可以使得一个进程有多个并发(concurrent,即同时进行的)的操作。
- 可以提高响应性(Responsiveness)。Web服务器会采用一些专门的线程负责用户的请求处理,缩短了用户等待时间提高了响应性。
- 充分利用多核(Multicore)处理器资源,可以充分利用CPU资源。
风险:
- 线程安全问题(Thread safe)。多线程共享数据池的时,如果没有采用正确的并发访问控制措施,就可能会产生一致性问题,如读脏数据(过期数据),如丢失数据。
- 线程活性问题(Thread liveness)问题。由于程序自身的缺陷或者有资源的稀缺性,可能会导致线程一直处于非RUNNABLE状态,这就是线程活性问题。常见活性故障有:
- 死锁(Deadlock)。
- 锁死(Lockout)。
- 活锁(Livelock)。
- 饥饿(Starvation)。
- 上下文切换(Content Switch)。处理器从执行一个线程切换到执行另一个线程。
- 可靠性问题。可能会有一个线程导致JVM意外终止,其他得线程也无法执行。