
本文共 4513 字,大约阅读时间需要 15 分钟。
Java 多线程
线程简介
-
多任务
-
进程
-
多线程
一个进程可以有多个线程
核心
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程、gc线程
- main()称之为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行有调度器安排调度,调度器时与操作系统密切相关的,先后顺序是不能人为干预的
- 操作同一份资源时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,如CPU调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
一个java程序,相当于一个jvm进程,jvm进程用一个主线程来执行
main()
,在main()
内部,又可以启动多个线程。此外,jvm还有负责垃圾回收线程、其他线程
实现多任务的方法
1、多进程模式 每个进程一个线程
2、多线程模式 每个进程多个线程
3、多进程+多线程
进程 线程 对比
和多线程相比,多进程的缺点
1、创建进程比创建线程开销大,尤其在win
2、进程间通信比线程间通信慢
多进程优点
1、稳定性高,一个进程崩溃不会影响其他进程
2、而多线程,任何一个线程崩溃会直接导致进程崩溃
线程创建
三种方式
继承Thread类
实现Runnable接口
因为java使用单继承,所以推荐使用Runnable接口使用了静态代理
实现Callable接口
lambda表达式
实质:函数式编程
1、避免内部匿名类定义过多
2、让代码看起来更加简洁
3、去掉没有意义的代码,只留下核心
函数式接口:
任何接口,只包含唯一一个抽象方法
public interface Runnable{ public abstract void run();}
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
线程的五大状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oAn0oQ0D-1614828638542)(C:\Users\ThundeRobot\AppData\Roaming\Typora\typora-user-images\image-20210303194859052.png)]
1、创建状态
2、就绪状态
3、阻塞状态
4、运行状态
5、死亡状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7gnMQm8T-1614828638543)(C:\Users\ThundeRobot\AppData\Roaming\Typora\typora-user-images\image-20210303195000653.png)]
线程方法
setPriority(int newPriority);static void sleep(long millis);void join();static void yield();void interrupt();boolean isAlive();
停止线程
1、不推荐使用JDK提供的stop()、destory()【已废弃】
2、推荐让线程自己停下来
3、建议使用一个标志位进行终止线程,当flag = false,则线程停止
public class TestStop implements Runnable{ // 线程中定义线程使用的标识 private boolean flag = true; @Override public void run(){ // 线程使用该标识 whlie(flag){ System.out.println("run --- thread"); } } // 对外提供方法,改变标识 public void stop(){ this.flag = false; }}
线程休眠
1、 sleep 执行当前线程阻塞的毫秒数
2、 sleep 存在异常 InterruptedException
3、sleep时间达到后线程进入就绪状态
4、sleep可以模拟网络延时、倒计时等
5、每个对象都有一个锁,sleep不会释放锁
线程礼让
1、礼让程序,让当前正在执行的线程暂停,但不阻塞
2、将线程从运行状态转为就绪状态
3、让CPU重新调度,礼让不一定成功!看CPU心情
join
1、join合并线程,其他线程先阻塞,待此线程执行完成后,再执行其他线程
2、可以想象成插队
线程状态观测
1、创建
2、就绪
3、阻塞
4、运行
5、死亡
死亡之后的线程不能再启动
线程的优先级
1、java提供一个线程调度器来监控程序中启动后进入就绪状态的所有进程,线程调度器按照优先级来决定应该调度哪个程序来执行
2、线程优先级高,不一定先执行,但概率更大
3、Thread.MIN_PRIORITY = 1 返回1~10
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5;
4、getPriority() setPriority()
5、优先级的设定再start调度前设置
守护(daemon)线程
1、线程分为用户线程 、守护线程
2、虚拟机必须确保用户线程执行完毕
3、虚拟机不用等待守护线程执行完毕
4、如:后台记录操作日志、监控内存、垃圾回收等
线程同步
多个线程操作同一个资源
并发:同一对象被多个线程同时操作
线程同步:一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池 形成队列,等待前面的线程使用完毕,下一个线程再使用
队列和锁
由于同一进程的多个线程共享同一块内存,在带来方便的同时,也带来的冲突问题。
为了保证数据在方法中的正确性,在访问时加入**锁机制 synchronized,**当一个线程获得排他锁,独占资源,其他线程必须等待,使用后释放锁即可。
存在问题
1、一个线程持有锁会导致其他所有需要此锁的线程挂起
2、在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
3、如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
Synchronized
两种用法:synchronized 方法 synchronized块
synchronized 方法:控制对 “对象” 的访问,每个对象对应一把锁,每个synchronized 方法都必须获得该对象的锁才能执行,否则会阻塞;方法一旦执行,就独占该锁,直到返回才能接所,后面被阻塞的线程才能获得这个锁,继续执行
缺点:若将一个大的方法申明为synchronized,将影响效率
同步块
synchronized (Obj){ }
1、Obj称为同步监视器
2、Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
3、同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class【反射】
4、同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中的代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
JUC
CopyOnWriteArrayList
死锁
某一同步块同时拥有 两个以上对象的锁时,就可能发生死锁
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。 (2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 (3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。 (4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不满足,就不会发生死锁。Lock(锁)
1、JDK5.0开始,java提供的更强大的线程同步机制——通过显式定义同步锁对想来实现同步。同步锁是Lock对象充当
2、java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始放回共享资源前应先获得Lock对象
3、**ReentrantLock(可重入锁)**实现了Lock,它拥有和synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁
synchronized 与 Lock 的对比
1、Lock是显式的锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放
2、Lock只有代码块锁,synchronized有代码块和方法锁
3、使用Lock锁,JVM将花费较少的时间来调度程序,性能更好。并且具有更好的扩展性(提供更多的子类)
4、优先使用顺序
Lock > 同步代码块 > 同步方法
线程协作-- 生产者消费者问题
在生产者消费者问题中,仅有synchronized是不够的
synchronized 可以阻止并发更新同一共享资源,实现了同步
synchronized不能用来实现不同线程之间的消息传递(通信)
java提供了几个方法来解决线程之间的通信问题
wait(); 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁wait(long timeout); 指定等待的毫秒数notify(); 唤醒一个处于等待状态的线程notifyAll(); 唤醒同一对象上所有调用wait()方法的线程,优先级别高的线程优先调度
上面的方法均是Object类的方法,都只能在同步方法或同步代码块中使用,否则会抛出异常IllegalMonitorStateException
管程法
信号灯法
线程池
场景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完返回池中。可以避免频繁创建、销毁、实现重复利用。
好处:
1、提高响应速度(减少创建新线程的时间)
2、降低资源消耗(重复使用线程池中的线程,不需要每次创建)
3、便于管理
corePoolSize: 线程池的大小
maximumPoolSize : 最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会中止
静态代理
* 1、真实对象和代理对象都要实现同一个接口* 2、代理对象要代理真实角色** 好处:* 1、代理对象可以做很多真实对象做不了的事情* 2、真实对象专注自己的事情
Thread相当于婚庆公司
Runnable相当于要结婚的自己
发表评论
最新留言
关于作者
