目录
一、程序、进程、线程、协程
● 程序
一段静态的代码,一段指令的有序集合,他是一个静态的实体,是应用程序执行的蓝本
● 进程
进程就是程序的一次动态执行,是一个动态的实体。它拥有自己的生命周期
● 线程
线程是进程的组成部分。一个进程可以有多个线程,而这多个线程必须依赖于同一个父进程。线程可以拥有自己的堆栈、程序计数器和局部变量,但不能拥有自己独立的系统资源。一个进程下的所有线程,都共享该进程的所有资源。
二、线程的实现方式
2.1 三种方式对比
● 继承 Thread 类
描述:继承 Thread 类,重写 run 方法,在实例化对象后调用 start 启动线程
优点:直接继承就可以使用,代码简单清晰
缺点:由于 JAVA 是单继承的,继承了 Thread 类就无法继承其他类
● Runnable
描述:通过使用 Runnable 接口,、重写 run 方法,在实例化对象后,将对象装载到其他 Thread 对象中使用
优点:解决了 Thread 不能继承其他类的问题
一个实例可以被多个线程加载
● Callable
描述:通过使用 callable 接口,重写 call 方法,在实例化对象后,将对象装载到 FutureTask 中,并将 FutureTask 传入线程实例中,通过 FutureTask 对象的 get 方法获取到调用结果,并处理
优点:可以实现线程的回调以及抛出异常
2.2 继承 Thread 类
/**
* @author tom
* @date 2019-11-01 09:37
* @Description 通过继承 Thread 类来创建线程
*/
public class MainThread extends Thread{
public static void main(String[] args) {
// 新建了一个 MainThread 的类实例,这个类继承了 Thread
MainThread thread1 = new MainThread();
// 设置这个实例的名称(可重复)
thread1.setName("tom thread");
// 启动线程,此时线程开始跑 run 方法
thread1.start();
System.out.println("main over");
// 运行 main 函数的线程叫主线程,循环打印主线程的内容
// 由于主线程和其他线程存在竞争关系,故可以在打印台看到两种线程存在交互情况
for (int i=0; i<50; i++) {
System.out.println(Thread.currentThread().getName() + " i = " + i);
}
}
/**
* @author tom
* @date 2019/11/1 9:39
* @params
* @return
* @Description 继承了 Thread 类,必须重写 run 方法,来让这一类的线程做自己想要让他做的事情
*/
@Override
public void run() {
// 循环打印出这个线程的名字
for (int i=0; i<50; i++) {
System.out.println(Thread.currentThread().getName() + " i = " + i);
}
}
}
2.3 Runnable
/**
* @author tom
* @date 2019-11-01 09:59
* @Description 使用 Runnable 接口创建子线程
*/
public class MainRunnable implements Runnable {
public static void main(String[] args) {
// 创建 Runnable 实例
MainRunnable mRu = new MainRunnable();
// 创建 Thread 并传入 Runnable 实例
Thread th1 = new Thread(mRu);
// 设置子线程名称
th1.setName("children thread");
// 开启子线程
th1.start();
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + " i = " + i);
}
}
/**
* @author tom
* @date 2019/11/1 10:00
* @params
* @return
* @Description this is a function
*/
@Override
public void run() {
// 打印子线程信息
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + " i = " + i);
}
}
}
2.4 Callable
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author tom
* @date 2019-11-01 10:08
* @Description
*/
public class MainCallable implements Callable<Integer> {
public static void main(String[] args) {
// 构建 Callable 实例类
MainCallable mCall = new MainCallable();
// 通过 FutureTask 类来实现回调
// 需要指定回调类型,此时回调类型是 Integer,并将call实例类添加到 FutureTask 实例类中
FutureTask<Integer> mFutureTask = new FutureTask<>(mCall);
// 构建线程,并在构建时将 FutureTask 实例传入
Thread mThread = new Thread(mFutureTask);
// 设置子线程名
mThread.setName("children thread");
// 开启线程
mThread.start();
// 重要的一步,通过 FutureTask 实例的 get() 方法获取到线程返回的结果
try {
int count = mFutureTask.get();
System.out.println("callable count = " + count);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
/**
* @author tom
* @date 2019/11/1 10:08
* @params
* @return
* @Description this is a function
*/
@Override
public Integer call() throws Exception {
int count = 0;
for (int i=0; i<50; i++) {
count++;
System.out.println(Thread.currentThread().getName() + " i = " + i);
}
return count;
}
}
三、线程的常用方法和状态
3.1 常用方法
方法 | 描述 |
setPriority、getPriority | 设置/获取 优先级,0为最低级,10为最高级 |
sleep | 在指定毫秒内休眠,但不会让位置 |
yield | 礼让,挂起当前的线程,与其他线程再次竞争 |
join | 插队,挂起当前线程,先运行插队线程,插队线程运行完后,再运行原理挂起的线程 |
isAlive | 查看线程是否处于活跃状态 |
getStatus | 获取线程状态 |
interrupt | 中断线程 |
https://www.jianshu.com/p/e0ff2e420ab6
3.2 状态
状态 | 描述 |
new | 尚未启动的线程的状态 |
runnable | 在执行中的线程的状态 |
blocked | 被阻塞等待的线程的状态 |
waiting | 等待另一个线程执行特定动作的线程的状态 |
timed_waiting | 正在等待另一个线程执行特定动作的线程的状态,如 sleep 等 |
terminated | 已退出的线程的状态 |
四、守护线程
4.1 概念
线程分为用户线程和守护线程,一般直接创建出来的线程是用户线程
虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕
4.2 作用
守护线程常用来后台记录操作日志、监控内存、垃圾回收等
4.3 使用 demo
public class TestDaemon {
public static void main(String[] args) {
DaemonThread daemonThread = new DaemonThread();
NormalThread normalThread = new NormalThread();
// 启动守护线程
Thread thread = new Thread(daemonThread);
thread.setDaemon(true);
thread.start();
// 启动正常线程
Thread thread1 = new Thread(normalThread);
thread1.start();
}
}
class DaemonThread implements Runnable {
@Override
public void run() {
while (true){
System.out.println("this is DaemonThread Run");
}
}
}
class NormalThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36000; i++) {
System.out.println("正常线程");
}
}
}
五、线程锁
5.1 线程锁
线程锁又称为同步方法,通过 synchronized 关键字声明,可以用于声明方法或代码块
5.2 synchronized 声明方法
5.2.1 示例
public synchronized void methodName(int args){}
5.2.2 缺点
若将一个大的方法申明为 synchronized,则会影响效率。且默认锁的是当前这个对象this,可能不适合一些需求
5.2.3 demo代码
package com.tom.demo01.sync;
/**
* @File: UnsafeBuyTIicket
* @Description:
* @Author: tom
* @Create: 2020-07-30 17:04
**/
public class UnsafeBuyTIicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket, "A").start();
new Thread(buyTicket, "B").start();
new Thread(buyTicket, "C").start();
}
}
class BuyTicket implements Runnable {
private int ticketNums = 10;
boolean flag = true;
@Override
public void run() {
while (flag) {
buy();
}
}
private synchronized void buy() {
if(ticketNums <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
}
}
5.3 synchronized 代码块
示例代码
public class UnsafeBuyTIicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket, "A").start();
new Thread(buyTicket, "B").start();
new Thread(buyTicket, "C").start();
}
}
class BuyTicket implements Runnable {
private int ticketNums = 10;
boolean flag = true;
@Override
public void run() {
while (flag) {
buy();
}
}
private void buy() {
synchronized (this) {
if(ticketNums <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
}
}
}
5.4 死锁
死锁即两个或两个以上的线程都在等待对方释放资源,结果都被锁住停止执行的情况。当一个同步块用时用于两个及两个以上的对象的锁时,就有可能发生
5.5 Lock
https://blog.csdn.net/e54332/article/details/86577071
六、线程通信
6.1 常用的线程通信方法
这些方法只能在同步方法或同步代码块中使用
方法名 | 作用 |
wait() | 让线程一直等待,直到其他线程通知。此时会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待的线程 |
notifyAll() | 唤醒同一个对象上,所有调用了 wait() 方法的线程,优先级别高的线程优先调度 |
6.2 管程法
使用缓存区,类似消费者/生产者模型,一个填入缓冲区,一个从缓冲区取出
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
class Productor extends Thread{
SynContainer synContainer;
public Productor(SynContainer container) {
this.synContainer = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了 " + i + "只鸡");
synContainer.push(new Chicken(i));
}
}
}
class Consumer extends Thread{
SynContainer synContainer;
public Consumer(SynContainer container) {
this.synContainer = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了 " + synContainer.pop().id + "只鸡");
}
}
}
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
class SynContainer{
Chicken[] chickens = new Chicken[10];
int count = 0;
public synchronized void push(Chicken chicken) {
if (count == chickens.length) {
try {
System.out.println("push wait");
this.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
chickens[count]=chicken;
count++;
this.notifyAll();
}
public synchronized Chicken pop() {
if (count == 0) {
try {
System.out.println("pop wait");
this.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
count--;
Chicken chicken = chickens[count];
this.notifyAll();
return chicken;
}
}
6.3 信号灯法
通过设置 flag 值,判断哪个需要的等待
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
class Player extends Thread{
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
this.tv.play("快乐大本营播放中");
} else {
this.tv.play("抖音记录美好生活");
}
}
}
}
class Watcher extends Thread {
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
class TV{
String voice;
boolean flag = true;
public synchronized void play(String voice) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了: " + voice);
this.notifyAll();
this.voice = voice;
this.flag = !this.flag;
}
public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("观众观看了:" + voice);
this.notifyAll();
this.flag = !this.flag;
}
}
七、线程池
7.1 概念
线程池就是提前创建好多个线程放入池中,使用时直接获取,使用完放回池中。这样可以避免频繁创建和销毁,实现重复利用。
7.2 为什么要使用线程池
由于创建线程的代价和维护十分昂贵
- JVM 中默认一个线程需要使用256k~1M的内存
- 加重 GC 的回收压力
使用线程池的好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗
- 便于线程管理
7.3 默认提供API(不推荐使用)
7.3.1 API描述
java 提供了相关的API: ExecutorService 和 Executors
● ExecutorService
常见子类: ThreadPoolExecutor
方法:
void execute(Runnable command):执行任务,无返回值
Future submit(Callable task):执行任务,有返回值
void shutdown():关闭线程池
● Executors
线程池的工厂类,用于创建并返回不同类型的线程池
7.3.2 创建的线程池类型
● newCachedThreadPool (ThreadPoolExecutor)
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
● newFixedThreadPool (ThreadPoolExecutor)
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
● newSingleThreadExecutor (ThreadPoolExecutor)
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
● newScheduledThreadPool (ScheduledThreadPoolExecutor)
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
● newSingleThreadScheduledExecutor (ScheduledThreadPoolExecutor)
创建一个单线程用于定时以及周期性执行任务的需求。
● newWorkStealingPool (1.8 ForkJoinPool)
创建一个工作窃取
对应的实现类
newCachedThreadPool | ThreadPoolExecutor |
newFixedThreadPool | ThreadPoolExecutor |
newSingleThreadExecutor | ThreadPoolExecutor |
newScheduledThreadPool | ScheduledThreadPoolExecutor |
newSingleThreadScheduledExecutor | ScheduledThreadPoolExecutor |
newWorkStealingPool | ForkJoinPool |
7.3.3 使用
public class TestPool {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}