看完后需要理解的知识
- 串行,并发,与并行的区别
- 进程与线程的关系
- 线程的生命周期,以及关系
- 创建线程的前两种方式
1.线程概念
1.1 线程相关概念
进程
-
- 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是操作系统进行资源分配与调度的基本单位
-
- 可以把进程简单的理解为正在操作系统中运行的一个程序
线程
-
- 线程(thread)是进程的一个执行单元.
-
- 一个线程就是进程中一个单一顺序的控制流,进程的一个执行分支
-
- 进程是线程的容器,一个进程至少有一个线程,一个进程中也可以有多个线程.
-
- 在操作系统中是以进程为单位分配资源,如虚拟存储空间,文件描述等,每个线程都有各自的线程栈,自己的寄存器环境,自己的线程本地存储,
1.2 主线程与子线程
-1. JVM 启动时会创建一个主线程,该主线程负责执行main方法,主线程就是运行main方法的线程
-2. Java中的线程不是孤立的,线程之间存在一些联系,如果在A线程中创建B线程,称B线程为A线程的子线程,相应的A线程就是B线程的父线程。
1.3 串行并发与并发
-1. 串行,逐个完成任务,
-2. 并发,等待一个任务完的时间内,就开始下一个任务
-3. 并行, 任务同时开始,时间取决于最长的那个任务
并发可以提高事物处理的效率,即一段时间内可以处理或完成更多的事情.
并行是一种更为严格,理想的并发
2线程的创建与启动
在Java中,创建一个线程就是创建一个Thread类(子类)的对象(实列)
-
调用线程的start方法来启动线程,启动线程的实质就是请求JVM运行相应的线程,这个线程具体在什么时候运行由线程**调度器(Scheduler)**决定
-
注意:start()方法调用结束并不意味着子线程开始运行。
-
新开启的线程会执行run()方法
-
如果开启了多个线程,start()调用的顺序并不一定就是线程启动的顺序
-
多线程运行结果与代码执行调用顺序无关
Thread有两个常用的构造方法:Thread()与Tread(Runnable).对应的创建的两种方式:
定义Thread类的子类
-
- 定义类继承Thread
-
- 重写Thread父类中的run()
-
- 创建子线程对象
-
- 启动线程
定义一个Runbale接口的实现类
-
- 定义实现类Runnable接口
-
- 重写Runnable接口中的抽象方法,run()方法就是子线程要执行的代码
-
- 创建Runnable 的实现类对象
-
- 使用Runnable对象做为参数创建线程对象
-
- 开启线程
有时调用Thread(Runnable)构造方法时,实参也会传递署名内部类
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
}
});
这两种创建线程的方式没有本质的区别
2.1线程的常用方法
2.1.1 currentThread()方法
Thread.currentThread() 方法可以获得当前线程
Java中的任何一段代码都是执行在某以线程当中的,执行当前线程就是当前线程,
同一段代码可能被不同的线程执行,因此当前线程是相对的,Thread.currentThread()方法的返回值是在代码实际运行时的
2.1.2 setName()/getName()
Thread.setName(线程名称), 设置线程名称
Thread.getName() 返回线程名称
通过设置线程名称,有助于线程调试,提高线程的可读性,建议为每个线程都设置一个能够体现线程功能的名称
2.1.3 isAlive
thread.siAlive() 判断线程是否处于活动状态
活动状态就是线程以及启动并且尚未终止
2.1.4 sellp()
Thread.sleep(); 让当前线程休眠指定的毫秒数
当前线程是指Thread.currentThread() 返回的线程
2.1.5 getId()
thread.getId() 可以获得线程的唯一标识,
注意 :
某个编号的线程运行结束后,该编号可能被后续创建的线程使用,
重启的JVM后,同一个线程的编号可能不一样
2.1.6 yield()
Thread.yieId()方法的作用是放弃当前CPU的资源,
2.1.7 setPriority
thread.setPriority(num ) 设置线程的优先级
- java下次你的优先级为1-10, 如果超出这个范围会抛出异常IllegaLargumentException
- 在操作系统红,优先级较高的线程获得CPU的资源越多,
- 线程优先级本质上只是给线程调度器一个提示信息,以便调度器先调用哪些线程,注意不能保证优先级高的线程先运行
- Java优先级设置不当或者滥用可能会导致某些线程永远无法得到运行,即产生了线程饥饿
-
线程的优先级并不是设置的越高越好,一般情况下使用普通的优先级即可,即在开发时不必设置线程的优先级
- 线程的优先级具有继承性
2.1.8 intrrupt()
中断线程.
- 调用interrupt()方法仅仅在当前线程打一个停止标注,并不是真正的停止线程
- 需要代码去手动判断是否有中断标注 (this.isInterrupted())
2.1.9 setDaemon()
Java中的线程分为用户线程和守护线程
守护线程
- 守护线程是为其他线程提供服务的线程,如垃圾回收器(GC)就是一个典型的守护线程
- 守护线程不能单独运行,当JVM中没有其他用户线程,只有守护线程时,守护线程会自动销毁,JVM会推出
- 设置守护线程的代码需要在线程启动前
2.2线程的生命周期
线程的生命周期可以通过getState()方法获得,线程的状态是Thread.state是一个枚举类型有一下几种:
- new,新建状态,创建了多线程对象,在start() 启动器的状态
- RUNNABLE, 可运行状态,它所一个复合状态,包含:READY和RUNNING 两个状态,READY状态该线程可以被线程调度器进行调度使他处于RUNNING状态,RUNNINGA状态表示该线程正在执行. Thread.yieId()方法可以把线程由RUNNNG状态转换为READY状态
- BLOCKED 堵塞状态. 线程发起堵塞的I/O操作,或者申请由其他线程独占资源,线程会转化为BLOCKED堵塞状态, 处于堵塞状态的线程不会占用CPU资源,当堵塞I/o操作完,或者线程获得了其申请的资源,线程就可以转换为RUNNABLE
- WAITING 等待状态,线程执行了object.wait(), thread.join()方法会把线程转换为WATING等待状态, 执行object.notify()方法,或加入的线程执行完毕,当前线程会转换为RUNNABLE状态
- TIMED_WAITING 状态,与WAITING状态类似,都是等待状态,区别在于处于该状态线程不会无限的等待,如果线程没有在指定的时间范围里完成期待的操作,该线程自动转换为RUNNABLE
- TERMINATED 中止状态,线程结束处于中止装
2.3多线程编程的优势与存储的风险
2.3.1优势:
- 提高系统的吞吐率(Throughout). 多线程编程可以使一个进程有多个并发(concurrent,即同时进行的)的操作
- 提高响应性(Responsiveness). Web服务器会采用一些专门的线程负责用户的请求处理,缩短了用户的等待时间
- 充分利用多核处理器资源(Multicore)处理器资源,通过多线程可以充分的利用CPU资源
2.3.2 风险:
- 线程安全(Thread safe)问题, 多线程共享数据时,如果没有采用正确的并发访问措施,就可能会产生数据一致性问题,如读取脏数据(过期的数据),如丢失数据更新.
- 线程活性问题(thread liveness)问题.由于程序自身的缺陷或者由资源稀缺性导致线程一直处于非RUNNABLE状态,这就是线程活性问题,常见的活性故障有以下
1 .死锁(deadlock). 类似于鹬蚌相争.
2.锁死(Lockout)类似睡美人故事中王子挂掉了,
3.活锁(Livelock)类似于小猫咬自己尾巴
4 饥饿(Starvation)类似于健壮的雏鸟总是从母鸟嘴中签抢到食物
- 上下文切换(Context Switch)处理器从执行一个线程切换到执行另外一个线程,消耗系统资源
- 可靠性,可能导致一个线程导致JVM意外终止,其他线程也无法执行
3.3 内部锁:synchronized 关键字
Java中的每个对象都有一个与之关联的内部锁(Intrinsic lock)。这种锁也称为监视器(Monitor),这种内部锁是一种排他锁,可以保障原子性,可见性与有序性。
内部锁是通过synchronized关键字实现的。 syfanfanchronized关键字修饰代码块,修饰该代码块,修饰方法
synchronized(对象锁) {
同步代码块,可以在同步代码块中访问共享数据
}
- 修饰实例方法就称为同步实例方法
- 修饰静态方法就称为同步静态方法
多线程锁
公平锁与非公平锁
公平锁: 阳光普照,相对非公平锁而已效率会低,
非公平锁: 可能会出现饿死的情况。 效率相对较高
可重入锁
也叫递归锁, 在锁的内存还可以上锁, synchronized (隐式) 与Lock(显式) 都是可以重入锁。
死锁,
两个或两个以上的线程,因为抢占资源的问题造成互相等待的现象,如果没有外力干涉,它们无法再执行下去。
Callable 接口
目前我们学习了两种创建线程的方式-一种通过创建Thread类,一种是通过使用Runnable创建线程。但是,Runnable缺少的一项功能是,当线程终止时(即run()完成时),我们无法使线程返回结果,为了支持此功能,Java中提供了Callable接口
Runnable 接口与Callable接口的区别
- Callable接口 有返回值, Runnable 没有
- Callable会抛出异常,Runnable 没有
- 实现方法不同,一个run方法,一个call方法
Callable不能像Runnable一样直接start()。 需要使用 FutureTask futureTask1 = new FutureTask<>(new MyThread2()); 来作为一个中间类。
FutureTask常用方法
- isDone() 是否执行完毕。
- get()。 获取返回值。
JUC强大的辅助类
1.减少计数(CountDownLatch)
- CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await方法等待计数器不大于0,然后继续执行awai方法之后的语句。
- CountDownLatch主要有两个发放,当一个或多个线程调用await方法时,这些线程会阻塞
- 其他线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)
- 当计数器的值变成0时,因await方法阻塞的线程会被唤醒,继续执行
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+ ":号同学,离开教室");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("班长锁门走人");
}
}
2.循环栅栏 (CyclicBarrier)
从字面上的意思可以知道,这个类的中文意思是“循环栅栏”。大概的意思就是一个可循环利用的屏障。
它的作用就是会让所有线程都等待完成后才会继续下一步行动。
举个例子,就像生活中我们会约朋友们到某个餐厅一起吃饭,有些朋友可能会早到,有些朋友可能会晚到,但是这个餐厅规定必须等到所有人到齐之后才会让我们进去。这里的朋友们就是各个线程,餐厅就是 CyclicBarrier。
public class CyclicBarrierDemo {
private static final int NUMBER = 7;
public static void main(String[] args) {
CyclicBarrier cyclicBarrier =
new CyclicBarrier(NUMBER, ()->{
System.out.println("收齐7颗龙珠");
});
for (int i = 0; i < 7; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + " 龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
3. 信号灯(Semaphore)
public class SemaphoreDemo {
public static void main(String[] args) {
//创建Semaphore, 设置许可证
Semaphore semaphore = new Semaphore(3);
//模拟六辆汽车
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(()->{
//抢占
try {
semaphore.acquire();
System.out.println("i"+ finalI + "-抢到了车位-" + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println("i"+ finalI + "--------离开了车位-" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}