一、线程
1.并发和并行
- 并发:指两个或者多个时间在同一时刻发生
- 并行:指两个或者多个时间在同一时间发生(同时发生)
2.进程和进程
-
进程 指一个内存中运行的应用程序
每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程
进程也是程序的依次执行过程,是系统运行程序的基本单位
系统运行的一个程序就是一个进程从创建到运行消亡的过程 -
线程: 线程就是进程过程的一个执行单元,负责当前进程的执行,一个进程至少有一个线程
一个进程中是可以有多个线程的,这种程序就是多线程程序 -
总结:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
-
多线程的好处
- 效率高
- 多个线程之间互不影响
3.线程的调度
- 线程调度方式:
-
分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间 -
抢占式调用
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会CPU随机选择一个线程
java使用的是抢占式调度
-
4.主线程
主线程: 执行主(main)方法的线程
-
单线程程序:java程序中只有一个线程
执行main方法开始,从上到下依次执行 -
主线程执行过程:
1. JVM执行main方法,main方法会进入到栈内存
2. JVM会找操作系统开辟一条main方法通向CPU的执行路径
3. CPU通过这个路径来执行main方法,这个路径就叫主线程
二、多线程
1.Thread类
java.lang.Thread
是描述线程的类,我们想要实现多线程程序,就可以继承Thread类
-
构造方法:
扫描二维码关注公众号,回复: 12916478 查看本文章1.public Thread() 分配一个新的线程对象 2.public Thread(String name) 分配一个指定名字的新线程对象 3.public Thread(Runable target) 分配一个带有指定目标的新线程对象 4.public Thread(Runable target,String name) 分配一个带有指定目标的新线程对象并指定名字
-
常用方法:
1.获取线程的名称 1.使用Tread类中的方法getName() String getName() 返回该线程的名称 2.可以先获取到当前执行的线程,使用线程中的方法getName()获取线程的名称 static Thread currentThread() 返回对当前正在执行的线程的引用 2.设置线程的名称 1. 使用Tread类中的方法setName() void setName(String name) 改变线程的名称为参数的name 2.创建一个带参数的构造方法,参数传递线程的名字; 调用父类的构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字 Thread(String name)分配新的Thread对象 3.static void sleep(long millis) : 使当前正在执行的线程以毫秒值数暂停执行(休眠),毫秒数结束之后线程继续运行 4.void run() 此线程要执行的任务方法代码 5.void start() 导致此线程开始执行,JVM调用此线程的run方法
2.创建多线程
-
第一种方式:继承Thread类
1.实现步骤 1.创建一个 Thread类的子类 2.重写Thread类中的run方法,设置线程任务(开启线程要做什么) 3.创建Thread类的子类对象(使用多态) 4.调用Thread类中的方法start方法,开启新的线程,执行run方法 void start() 使该线程开始执行;JVM调用该线程的run方法 结果是两个线程并发的运行:当前线程(main线程)和另一个线程(创建的新线程) 2.注意:多次启动一个此案成是非法的。 特别是当前(主)线程已经结束执行之后,不可再重新启动 java程序属于抢占式调度, 个线程的优先级高妈个线程优先执行。相同优先级随机选择一个执行
-
第二种方式:实现Runable接口
java.lang.Runable接口
需要实现类实现,(可以使用隐藏内部类),该类必须定义并重写一个run的无参方法2.实现步骤 1.创建一个Runable接口的实现类 2.重写Runable接口中的run方法,设置线程任务 3.创建Runable接口的实现类对象 4.创建Thread类对象,构造方法中传递Runable接口的实现类对象 5.调用Thread类中红的start()方法,开启新的线程执行run方法 3-4可以使用隐藏内部类 Thread 对象名 =new Thread(new Runable( run(){ 线程任务} ) );
-
两种创建多线程方式的区别:
如果一个类继承Thread,不适合资源共享。实现Runable接口容易实现资源共享- 总结: 实现Runable接口比继承Thread类所具有的优势
- 适合多个相同程序的代码的线程去共享同一个资源
- 可以避免java中单继承的局限性
一个类只能继承一个类,继承了Thread类不能继承其他类 - 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立
实现Runable接口,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写run()来设置多线程任务
多态创建Thread类对象,调用start()方法开启多线程 - 线程池只能放入实现Runable接口或者Callable类线程不能直接放入继承Thread类的线程
- 总结: 实现Runable接口比继承Thread类所具有的优势
三、线程安全
使用多线程访问统一资源时,多个线程对资源都有写的操作久容易产生线程安全问题
解决线程安全问题:
同步( synchronized )
同步有三种使用方式
- 同步代码块
- 同步方法
- 同步锁
1.同步代码块
- 格式
synchronized(同步锁){
可能出现线程安全问题的代码(访问了共享数据的代码)
}
- 同步锁 : 对象的同步锁只是一个概念,可以理解成在对象上标记了一个锁
- 注意事项
- 通过代码块中的锁对象,可以是任意的对象
- 但是必须保证多个线程使用的锁对象是同一个
- 锁对象的作用 :
把同步代码块所著,只让一个线程在同步代码块中执行
2.同步方法
-
同步方法:
使用synchronized修饰的方法就叫做同步方法
保证A线程执行该方法的时候,其他线咸亨只能在方法外等着 -
格式
public synchronized void Mathod(){ 可能产生线程安全问题的代码块 }
-
同步锁是谁
- 对于
非static
方法,同步锁就是this
- 对于
static
方法,我们使用当前方法所在的类的字节码对象(类名.class
)
- 对于
3.Lock锁 (同步锁)
java.util.concurrent.locks.Lock接口
Lock接口实现了提供比synchronized方法和语句可获得更广泛的锁定操作
-
Lock接口中的方法
void lock() 获取锁 void ublock() 释放锁
-
使用步骤:
1. 在成员位置创建一个ReentrantLock对象
2. 在可能出现线程安全问题的代码块前使用Lock接口中的方法lock()获得锁
3. 在可能出现线程安全问题的代码块后调用Lock接口中的方法unlock()释放锁
4.线程的状态
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建 | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡 |
5.线程等待和唤醒
-
1.进入TimeWaiting(计时等待)有两种方式
- 使用sleep(long ms)方法
在毫秒值结束后,线程睡醒到Runnable/Blocked状态 - 使用wait()方法,wait()方法
如果在毫秒值结束之后还没有被notify就会自动醒来,线程睡醒到Runnable/Blocked状态
- 使用sleep(long ms)方法
-
2.唤醒方法
void notify() 唤醒在此对象监视器上等待的单个线程 void notifyAll() 唤醒在此对象监视器上等待的所有线程
-
3.线程间通信
多个线程在处理同一个资源,但是处理的的动作(线程的任务)不同-
目的:
多线程并发执行时,在默认情况下CPU时随即切换线程的,
当我们需要多个线程完成同一任务时,我们希望他们有规律的运行,那么线程之间需要一些协调通信,依次来达到多线程操纵同一份数据 -
保证线程间通信有效利用资源:
多线程处理同一资源,并且任务不同时,需要线程间通信来帮助解决线程之间对同一变量的操作,
避免对同一共享变量的争夺需要通过指定的手段使各个线程能有限的利用资源使用等待唤醒机制
-
-
4.等待唤醒机制
- 线程之间的竞争(race),去争夺锁
线程之间的合作,等待唤醒机制就是其中一种协作机制 - 一个线程进行了规定操作之后就进入等待状态(wait()),等待其他线程执行完他们的指定操作之后再将其唤醒(notify())
多个线程进入等待状态时,需要时候可以使用notifyAll()来唤醒所有等待的线程
wait/notify就是线程之间的一种协作机制
1.wait:线程不再活动,不再参与调度, 进入wait set中不会浪费CPU资源,也不会去竞争锁了,这时候线程的状态就是WAITING。 需要等待别的线程通知唤醒他(notify()) 在这个对象上的线程从wait set中释放出来,重新进入到调度队列(ready queue)中 2.notify:选取所统治对象的wait set中的一个线程释放 3.notifyAll:则释放所通知对象的wait set上的全部线程
- 注意事项:
-
wait方法与notify方法必须要同一个锁对象调用
对应的锁对象可以通过notify()欢迎使用同一个锁对象调用的wait()后的线程 -
wait()方法和notify()方法属于Object类。
锁对象可以是任意对象,所有对象所属的类都是继承Object类 -
wait()方法和notify()方法要在同步代码块或者同步方法中使用。因为必须要通过锁对象调用这两种方法
-
- 线程之间的竞争(race),去争夺锁
6.线程池
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁的创建线程对象的操作,无需反复创建线程而小号过多资源
-
1.Executor
Java线程池的顶级接口时java.util.concurrent.Executor
,严格上讲Executor
并不是一个线程池而是一个线程的执行工具
真正的线程接口时java.util.concurrent.ExecutorService
java.util.concurrent.Executors
线程工厂里面提供了一些静态工厂,生成一些常用的线程池。
官方建议使用Executors工程类来创建线程池对象
-
2.Executors类中创建线程池的方法
1.static ExecutorService newFixedThreadPool(int nThreads) : int nThreads :创建线程池中包含的线程数量 返回线程对象,创建的是有界线程池,也就是池中的线程个数可以指定最大数量线程池对象的方法 2. submit(Runnable task) 获取线程池中的某个线程对象并执行 3.void shutdown() 关闭销毁线程池
-
3.使用步骤
1.实用Executors类的ewFixedThreadPool(int nThreads)方法创建线程池对象 2.创建Runnable接口子类对象(task)并重写run方法 3.调用ExecutorService中的方法submit() 提交Runnable接口子类对象(task task),开启线程执行run方法 4.调用ExecutorService中的方法shutdown()关闭(销毁)线程池(一般不做)
三、函数式编程思想
-
面向对象编程思想:
做一件事,找一个能解决这个事情的对象,调用对象的方法,完成该事情 -
函数式编程思想 :
只要能获取到结果,谁做的怎么做的都不重要,重视的是结果,不重视过程 -
编程思想转换:做什么而不是怎么做 Lambda表达式简化匿名内部类
1.Lambda表达式
Lambda表达式语法非常简洁,完全没有面向对象复杂的约束
-
1.使用注意 :
- 使用Lambda表达式必须要有接口,且要求接口中有且仅有一个抽象方法
(有且仅有一个抽象方法的接口叫做函数式接口)
比如Runnable接口和Comparator接口 - 使用Lambda表达式必须具有上下文推断
- 使用Lambda表达式必须要有接口,且要求接口中有且仅有一个抽象方法
-
2.Lambda表达式的省略:
Lambda表达式:可推导,可省略 凡是可以根据上下文推导出来的内容都可以省略 可以省略的内容 1.(参数列表):括号中参数列表的数据类型,可以省略不写 2.(参数列表):如果括号中参数只有一个那么类型和()都可以不写 3.{ 一些代码}:如果括号中代码只有一行,无论是否有返回值,都可以省略{ }, return,分号 (三者必须一起省略)
-
3.Lambda表达式实现多线程:
1.使用匿名内部类实现多线程 new Thread(new Runnable(){ @Override public void run(){ System.out.println(Thread.currentThread().getName()+"新线程创建"); }}).start(); 2.使用Lambda表达式,实现多线程 new Thread(()->{ System.out.println(Thread.currentThread().getName()+"新线程创建"); }).start(); 3.省略Lambda表达式 new Thread(()->System.out.println(Thread.currentThread().getName)+"新线程创建")).start();
-
4.Lambda表达式对自定义类型对象数组的排序:
1.对数组中的Person对象使用Arrays的sort方法通过年龄进行升序排序. Arrays.sort(arr,new Comparator<Person>(){ @Override public int compare(Person 01,Person 02){ return 01.getAge()-02.getAge(); } }); 2. 使用Lambda表达式简化匿名内部类 Arrays.sort(arr,(Person 01,Person 02)->{ return 01.getAge()-02.getAge(); });