Java多线程基础
线程简介
程序进程线程
程序:指令和数据的有序集合,静态概念
进程:程序的一次执行过程,动态概念。系统资源分配的单位
线程:独立执行的路径(运行程序时,即使没有创建线程后台会有多个线程,主线程,gc线程)
注意:真正的多线程是有多个CPU(多核),如服务器。模拟多线程优于切换速度很快,就会有多线程的错觉
线程实现(重点)
三种创建方式:
-
Thread class(继承Thread类)
-
Runnable接口(实现Runnable接口)
-
Callable接口(实现Callable接口)
注意:线程开启不一定执行,由CPU调度
Thread class
-
创建类继承自Thread类
-
重写run()方法
-
调用start()方法开启线程
public class TestThread1 extends Thread {
@Override
public void run() {
// run方法体
}
public static void main(String[] args) {
TestThread1 t1 = new TestThread1();
// 调用start()方法
t1.start();
}
}
Runnable(推荐使用)
-
声明类实现Runnable接口
-
重写run()方法
-
创建类对象
-
创建Thread时作为参数传递,调用start()方法
public class TestThread3 implements Runnable{
@Override
public void run() {
// run方法体
}
public static void main(String[] args) {
// 创建一个线程对象
TestThread3 t3 = new TestThread3();
// 线程丢入Thread类,调用start方法
new Thread(t3).start();
}
}
龟兔赛跑 - Race
静态代理
StaticProxy:静态代理,Runnable实现的原理
public class StaticProxy {
public static void main(String[] args) {
// 创建对象,相当于创建实现了Runnable接口的对象
You y1 = new You();
// 相当于将对象传入Thread类并调用start方法
new WeddingCompany(y1).HappyMarry();
//可简写为: new WeddingCompany(new You()).HappyMarry();
}
}
// marry接口,相当于Runnable接口
interface Marry{
void HappyMarry();
}
// You类,相当于实现Runnable接口的类
class You implements Marry {
@Override
public void HappyMarry(){
System.out.println("你要结婚了");
}
}
// 代理角色,帮助你结婚,相当于Thread类
class WeddingCompany implements Marry{
private Marry target;
// 传入you对象,相当于有参构造Thread类
public WeddingCompany(Marry target){
this.target = target;
}
// 具体的实现细节,相当于重写Run方法
@Override
public void HappyMarry(){
before();
this.target.HappyMarry();
after();
}
private void after() {
System.out.println("结婚后,补尾款");
}
private void before() {
System.out.println("结婚前,布置现场");
}
}
lambda表达式在接口中的使用
函数式接口:只包含一个抽象方法的接口
lambda表达式在函数式接口中的使用:类名 对象名 = (参数类型 形参名) -> { 需要重写的语句块; };
简化重写方法的部分代码
可简化的部分:
-
参数类型
-
括号
-
花括号
public class Test {
public static void main(String[] args) {
Person person = (int a)-> {System.out.println("person eat" + a);};
// 1. (a)-> {System.out.println("person eat" + a);};
// 2. a-> {System.out.println("person eat" + a);};
// 3. a-> System.out.println("person eat" + a);
person.eat(1);
}
}
interface Person{
void eat(int a);
}
Callable
-
实现Callable接口,需要返回值
-
重写call()方法,需要抛出异常
-
创建目标对象
-
创建执行服务:ExecutorService ser = Executors.newFixedThreadPool()
-
提交执行:Future
result1 = ser.submit(t1) -
获取结果:boolean r1 = result1.get()
-
关闭服务:ser.shutdownNow()
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
public class TestCallable implements Callable<Boolean> {
private String url;
private String name;
public TestCallable(String url, String name){
this.name = name;
this.url = url;
}
@Override
public Boolean call() {
// 创建 WebDownloader 对象
WebDownloader webDownloader = new WebDownloader();
// 调用downloader方法,传入
webDownloader.downloader(url,name);
System.out.println("下载了文件名为:" + name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 新建对象并传参 url,name
TestCallable t1 = new TestCallable("https://img2020.cnblogs.com/blog/2459624/202107/2459624-20210728230730532-2056940147.png","1.png");
TestCallable t2 = new TestCallable("https://img2020.cnblogs.com/blog/2459624/202107/2459624-20210728203411378-1829797592.png", "2.png");
TestCallable t3 = new TestCallable("https://img2020.cnblogs.com/blog/2459624/202107/2459624-20210728203430153-280296762.png","3.png");
// 创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
// 提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);
// 获取结果
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
// 关闭服务
ser.shutdownNow();
}
}
// 下载器
class WebDownloader{
// 下载方法
public void downloader(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}
线程状态
线程五大状态:
-
创建状态
-
就绪状态
-
阻塞状态
-
运行状态
-
死亡状态
线程停止
设置一个标志位,通过公开的方法转换标志位
//1.建议线程正常停止-->利用次数,不建议死循环
//2.建议使用标志位--->设置一个标志位
//3.不要使用stop或者destroy等过时或JDK不建议使用的方法
public class TestStop implements Runnable{
//1.设置一个标志位
private boolean flag = true;
@Override
public void run(){
int i = 0;
while (flag){
System.out.println("run...Thread" + i++);
}
}
//2.设置一公开方法停止线程,转换标志位
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
TestStop ts1 = new TestStop();
new Thread(ts1).start();
for (int i = 0; i < 200; i++) {
System.out.println("main" + i);
if (i==50){
ts1.stop();
System.out.println("Thread线程停止了");
}
}
}
}
线程休眠:sleep方法
- sleep方法不会释放对象的锁
// 模拟倒计时
// 打印系统当前时间
Thread.sleep(1000);//参数为毫秒
线程礼让:yield方法
Thread.currentThread().getName():获取当前线程的name
//测试礼让线程
// 礼让不一定成功,看CPU心情
public class TestYield {
public static void main(String[] args) {
MyYield my1 = new MyYield();
new Thread(my1,"a").start();
new Thread(my1,"b").start();
}
}
class MyYield implements Runnable {
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"线程开始");
Thread.yield(); //礼让
System.out.println(Thread.currentThread().getName()+"线程停止");
}
}
注意:线程礼让会让线程重新进入就绪状态,重新和其他线程进行资源的抢夺
强制执行:join方法
注意:在调用join方法前,线程是同步执行的,调用join方法后,其他方法进入阻塞状态
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("vip线程来到了"+i);
}
}
public static void main(String[] args) throws InterruptedException{
TestJoin tj1 = new TestJoin();
Thread thread = new Thread(tj1);
thread.start();
// 主线程
for (int i = 0; i < 250; i++) {
if (i == 200) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主线程"+i);
}
}
}
线程优先级
注意:优先级高的线程,执行还是看CPU调度
// 线程优先级用数字表示
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5; // 线程优先级默认为5
Thread t1 = new Thread();
// 获取优先级方法
t1.getPriority();
// 改变优先级方法
t1.setPriority(int 8);
守护线程
daemon:守护线程(音同demon)
- 线程分为用户线程和守护线程
- 虚拟机不用等待守护线程执行完成
- 虚拟机必须确保用户线程执行完毕
Thread t1 = new Thread();
t1.setDaemon(true); //默认为false表示用户线程
线程同步(重点)
并发,锁机制与死锁
并发:同一个对象被多个线程 同时操作
锁机制:当一个线程获得排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
锁机制可能出现的问题:
-
优先级倒置
-
性能问题
-
其他需要某锁的线程会挂起
死锁:两个(或多个)线程占有对方所需的锁,形成僵局
- 某一同步块同时拥有两个以上对象的锁时,可能会发生死锁问题
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
- 上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生
synchronized同步
同步机制:synchronized方法必须获得对象的锁才可以执行
每个对象有一把锁!!!
// 该方法为同步方法
public synchronized void method(int args) {}
synchronized同步块
// 同步块
synchronized(Obj) {}
-
Obj称之为同步监视器(就是对象),推荐使用共享资源作为同步监视器
-
同步方法中无需指定同步监视器,其Obj为this,即对象本身
-
Obj为属性需要进行修改(同步)的对象
同步监视器执行过程:
-
第一个线程访问,锁定同步监视器,执行其中代码.
-
第二个线程访问 ,发现同步监视器被锁定,无法访问.
-
第一个线程访问完毕,解锁同步监视器.
-
第二个线程访问, 发现同步监视器没有锁,然后锁定并访问
Lock同步
Lock锁:ReentrantLock类的lock方法
private final ReentrantLock lock = new ReentrantLock();
public void m() {
while (true){
lock.lock(); // 加锁
try{
// 保证线程安全的代码
}
finally {
// 解锁
lock.unlock();
}
synchronized与Lock对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁, 出了
作用域自动释放 - Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展
性(提供更多的子类)- 优先使用顺序:Lock >同步代码块(已经进入了方法体,分配了相应资源) >同步方法(在方法体之外)
线程通信问题
生产者消费者问题
相关方法:
wait():线程等待,直到其他线程通知,与sleep不同,会释放锁
notify():唤醒一个处于等待状态的线程
notifyAll():唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度
注意:均是Object类的方法,都只能在同步方法或者同步代码块中
使用,否则会抛出异常llegalMonitorStateException
管程法
并发协作模型:“生产者/消费者模式”--->管程法
Producer:生产数据模块
Consumer:处理数据模块
缓冲区:消费者间接从生产者拿数据
Producer --> 数据缓存区 --> Consumer
信号灯法
并发协作模型:“生产者/消费者模式”--->信号灯法
线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize: 核心池的大小
- maximumPoolSize: 最大线程数
- keepAliveTime: 线程没有任务时最多保持多长时间后会终止
用到Executors和ExecutorService类
//1.创建服务,创建线程池
//newFixedThreadPool 参数:线程池的大小
ExecutorService service = Executors.newFixedThreadPool(10);
//执行(4个线程)
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//2.关闭连接
service.shutdownNow();