进程和线程
-
进程
- 一个程序代表一个进程
-
线程
- 一个进程中需要多个线程共同配合, 使程序正常运行.
- 程序执行流的最小单元
-
多线程的作用
-
需求: 打印0-100的数字, 不需要考虑顺序
-
/** * 若不要考虑打印顺序, * 一个线程打印0-100,这一个线程需要打印100个数才算执行结束 * * 若是两个线程,则可以分工,两个线程分别打印奇数与偶数. * 如此,若两个线程同时执行与结束,单个线程只需要打印50个数字,效率要高的多 */ -
使用一个线程打印0-100
-
//使用一个线程打印0-100 @Test public void test01() { //main作为程序的启动, 使其可以调度thread线程对象执行 //创建线程对象 Thread thread = new Thread(() -> { for (int i = 0; i <= 100; i++) { System.out.println(i); } }); //启动线程 thread.start(); } -
使用两个个线程打印0-100
-
//使用两个个线程打印0-100 @Test public void test02() { //main作为程序的启动, 使其可以调度两个线程对象执行 //创建线程对象thread1, 使其打印奇数 Thread thread1 = new Thread(() -> { for (int i = 1; i <= 100; i+=2) { System.out.println(i); } }); //启动线程 thread1.start(); //创建线程对象thread2, 使其打印偶数 Thread thread2 = new Thread(() -> { for (int i = 0; i <= 100; i+=2) { System.out.println(i); } }); //启动线程 thread2.start(); }
-
Java开启线程的方式
-
继承Thread类
-
实现Runnable接口 (主推)
-
//创建线程对象 Thread thread = new Thread(() -> { for (int i = 0; i <= 100; i++) { System.out.println(i); } }); //启动线程 thread.start(); }
-
-
实现Callable接口
- 线程任务有返回值
线程相关方法
-
方法名称 说明 String getName() 返回此线程的名称 void setName(String name) 设置线程的名字(构造方法也可以设置名字) static Thread currentThread() 获取当前线程的对象 static void sleep(long time) 让线程休眠指定的时间,单位为毫秒
线程安全和同步
-
线程安全问题出现的条件
- 是多线程环境
- 有共享数据
- 有多条语句操作共享数据
-
解决方案: 加锁(同步) --> 将共享数据进行封锁, 当多条线程同时执行时, 只放行一条线程处理, 将其他线程进行阻塞, 当前一条线程处理完后, 让所有线程重新抢夺执行权, 每次只放行一条线程, 即可以避免线程安全问题
-
同步代码块
-
锁对象可以是任意对象,但是需要保证多条线程的锁对象,是同一把锁, 一般使用当前线程对象的字节码对象(类只加载一次)
-
//买票场景 synchronized (TicketsTask.class) { if (ticket == 0) { break; } System.out.println(Thread.currentThread().getName() + "卖出第:" + ticket + "张票"); ticket--; }
-
-
同步方法
-
在方法的返回值类型前面加入 synchronized 关键字
-
public synchronized void method() {} -
面试题:
- 静态方法的锁对象是字节码对象,非静态方法的锁对象是 this
-
-
Lock锁
-
Lock 是接口,无法直接创建对象
-
Lock lock= new ReentrantLock(); -
成员方法 说明 void lock() 加锁 void unlock(); 释放锁
-
-
线程通讯 (等待唤醒机制)
-
作用:
- 确保线程能够按照预定的顺序执行并且能够安全地访问共享资源
- 使多条线程更好的进行协同工作
-
Object类中的方法
-
void wait() 使当前线程等待 void notify() 随机唤醒单个等待的线程 void notifyAll() 唤醒所有等待的线程
-
-
ReentrantLock接口 : 监视器
-
创建方式
-
Condition condition = lock.newCondition();
-
-
成员方法 说明 void await() 指定线程等待 void signal(); 指定唤醒单个等待的线程 -
代码示例 :
-
class Printer{ int flag = 1; Lock lock = new ReentrantLock(); //获取监视器 Condition c1 = lock.newCondition(); Condition c2 = lock.newCondition(); Condition c3 = lock.newCondition(); public void print1() throws InterruptedException { lock.lock(); if (flag != 1) { c1.await(); } System.out.println("黑马程序员"); flag = 2; c2.signal(); lock.unlock(); } public void print2() throws InterruptedException { lock.lock(); if (flag != 2) { c2.await(); } System.out.println("传智教育"); flag = 3; c3.signal(); lock.unlock(); } public void print3() throws InterruptedException { lock.lock(); if (flag != 3) { c3.await(); } System.out.println("传智大学"); flag = 1; c1.signal(); lock.unlock(); } }
-
线程生命周期
线程池
-
概述
- 系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互
- 当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程,就会严重浪费系统资源
-
自定义线程池
-
代码示例:
-
public class MyPool { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor( 2, //核心线程数量 5, //总线程数量(当线程对象与大于核心线程数 + 任务队列数, 开始创建临时线程) 60, //临时变量空闲时间后销毁 TimeUnit.SECONDS, //时间单位 new LinkedBlockingDeque<>(10), //任务队列 Executors.defaultThreadFactory(), //线程对象工厂(默认) new ThreadPoolExecutor.AbortPolicy() //拒绝策略(当线程对象大于总线程数 + 任务队列数量时执行) ); for (int i = 0; i < 10; i++) { pool.submit(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"提交了线程任务.."); } }); } } } -
参数概述
-
拒绝策略
-
策略选项 说明 ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常 (默认) ThreadPoolExecutor.DiscardPolicy 丢弃任务,但是不抛出异常 这是不推荐的做法 ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务 然后把当前任务加入队列中 ThreadPoolExecutor.CallerRunsPolicy 调用任务的run()方法, 绕过线程池直接执行
-
-