多线程基础知识总结

重点是线程实现和线程同步
1、线程简介

1)多任务
一个既可以吃饭又可以玩手机
2)多线程
多个账号同时登录游戏一起玩
普通方法调用和多线程调用区别是前者是一条线走到底,效率低,后者同时进行,效率高,后者即实现了前面提到的多任务。
3)线程和进程
进程就是在操作系统中执行的一个程序,比如一个视频播放器播放的时候会有声音、图像和字幕等,而这些都是对应线程实现的,还有浏览器网页中既可以听歌又可以浏览网页这就是多线程功劳!
注:程序是一个静态的概念,是指令和数据的有序集合,只有程序跑起来后才是进程,所以进程是一个动态的概念。
进程里包括线程,哪怕不创建线程,默认有main主线程和gc垃圾回收线程,从底层讲,进程是系统资源分配的单位,而线程是cpu调度和执行的单位。(自己写的线程都在main线程里,所以main线程是用户线程,而GC线程是守护线程)
cpu同一时间只执行一个线程,只是切换太快所以看起来同时,比如吃饭的时候玩手机就很快。
2、线程创建
1)继承Thread类(重点)(思瑞d)
A、实现过程
第一步写个类实现Thread类
第二步重写run方法
第三步去main方法中new该类并调用start方法(这样会和main方法中其他输出交替执行,要for1000次才有效果)(如果这里start方法改成该类里run方法则就是普通方法调用,即同步的,没有实现多线程效果)
B、注意点
start方法开启线程不一定立即执行,需要等待cpu调度执行,所以每次执行顺序不一定相同。
2)实现Runnable接口(重点)(run哪莫)
A、其实Thread类也是实现Runnable接口,所以Runnable接口是最重要的!
B、推荐用Runnable接口实现,为什么?因为Java是单继承,所以建议用接口实现。
C、实现过程
第一步写个类实现Runnable接口
第二步重写run方法
第三步new一个Thread类作为代理,new的时候是有参构造,参数是第一步写的类对象(还可以指定线程名),然后再调用Thread类start方法。
3)实现Callable接口(了解)(卡啦莫)
A、实现过程
第一步写一个类实现Callable接口,这个接口需要指定call方法的返回值,比如下载图片成功返回true
第二步重写call方法,这里需要抛出异常
第三步创建该类的对象,可以创建多个
第四步创建执行服务,其实就是创建多线程指定线程数:
ExecutorService ser = Executors.newFixedThreadPool(3)
第五步提交执行,返回Future<Boolean
>
Future<Boolean> result1 = ser.submit(1)
Future<Boolean> result2 = ser.submit(2)
Future<Boolean> result3 = ser.submit(3)
第六步获取结果,即Future中返回值
boolean r1 = result1.get()
等等
第七步关闭服务
ser.shutdownNow()
4)扩展
A、静态代理
静态代理需要有一个真实对象的有参构造(前提是代理对象和真实对象都要p实现同一个接口,都有重写方法,只是代理对象里有before和after方法。)(好处是真实对象专注做自己的事,代理对象帮真实对象做真实对象做不了的事)
(所以Thread类就是静态代理)
B、lambda表达式
前提是有一个接口,里面只有一个抽象方法(接口中方法默认就是抽象方法,所以不需要自己写abstrat),这个和前面啥很像?Runnable接口。
那为什么用lambda表达式?
首先有一个接口,里面只有一个抽象方法(这个接口就是函数式接口)
第一次是main方法所在类外面写个该接口实现类
第二次是main方法所在类里面但main方法外面写个静态内部类实现该接口
第三次是main方法里面写个局部内部类
第四次是main方法里写个匿名内部类,即写个没有名字的类,直接new 要实现的接口或者继承的父类名称然后大括号里面重写方法即可,但既然接口名字唯一,且只有一个抽象方法,那为什么不能把这些省略?于是出现第五次。
第五次就是lambda接口,比如new Runnable(()->{打印一段话})。
(如果方法有方法参数,则上面的()改成(方法参数列表)比如(int a),还可以去掉int、小括号和花括号,但前提是花括号里只有一行代码)(如果两个参数,比如int a,int b如果去掉a的int那b也要去掉,但这个时候它们外面的小括号必须保留!)
3、线程的状态
创建状态:new 一个继承Thread类或者实现Runnable接口或Callable接口的对象(下游只有一种状态:就绪)
就绪状态:调用该对象start方法,等待cpu调度执行,类似跑步运动员预备动作(下游只有一种状态:运行)
运行状态:cpu调度执行,类似跑步运动员在跑步过程,然后可能会被cpu抛弃失去cpu资源从而继续就绪状态,也可能被中断进入阻塞状态,当然也可能直接结束进入死亡状态(下游有三种状态:死亡、阻塞和就绪)
阻塞状态:调用sleep等方法使线程休眠一段时间或者等待用户输入从而进入阻塞状态,然后阻塞消除就会进入就绪状态等待cpu调度(下游只有一种状态:就绪状态)
死亡状态:线程执行结束(没有下游,且不能再启动)
注:让线程进入死亡状态最好自定义stop方法,不要用Thread类里的stop方法,因为最好设置循环次数或者一个标志位使其正常结束,而且默认的stop方法,JDK已经不建议使用。
4、线程休眠:sleep方法
1)sleep方法两个作用:一是为了模拟网络延时从而放大多线程抢票等线程不安全问题发生性;二是倒计时
2)牢记:每个对象有一把锁,sleep不会释放锁。
5、线程礼让:yield方法
礼让不一定成功,看cpu心情
6、线程强制执行:join方法
main方法里for循环1000次,里面if判断200次时线程作为vip插入执行500次,然后再执行main里剩余300次。
再回顾Thread作为实现Runnable接口的对象的静态代理!
7、线程状态:getState方法
可以在new线程的时候getStart方法
线程调用start方法的时候getStart方法
可以判断线程getStart方法的状态是否是指定状态后执行不同代码
8、线程优先级:setPriority方法和getPriority方法
10为最大值,1为最小值,5是默认值,main方法默认是5,但肯定第一个执行,然后其他线程设置的值越大理论上最早执行,但最主要还是看cpu,所以设置优先级不一定很准,一般是准的,但大部分都是默认5让大家公平竞争。
9、守护线程:setDaemon方法
如果线程调用setDaemon方法,并且方法参数为true说明这个线程是守护线程(默认是false即用户线程),那么哪怕这个线程执行的是死循环方法也会因为用户线程结束而结束,比如人生不过三万天、上帝守护你的例子。
10、线程同步
为什么要线程同步?1万人抢一张票,如果不进行线程同步那票就剩余-9999明显不行;还有你手机网上取钱和你老婆柜台取钱,如果不线程同步那银行要亏死。
如何线程同步?队列加锁,类似排队上厕所,一个蹲位有一扇门,这个门就是锁。
线程同步肯定牺牲性能
11、sychonized
线程不安全三个例子:
不安全的买票
不安全的取钱
 不安全的arraylist集合(可以写个demo)(可以采用juc包下的CopyOnWriteArrayList,juc即java.util.concurrent包下)
上面三个例子怎么处理为线程安全?
锁的是run里面的方法,或者需要增删改的变量或者对象即代码块。
自己手撕!
12、死锁
例子:两个女士都要用到镜子和口红来化妆,然后她们都想拿到一个物品锁的代码块还想拿另一个物品锁(一个先镜子,另一个先口红)这样就出现死锁。
怎么在代码中避免死锁?
首先看下死锁发生的四个必要条件
A、互斥条件:一个资源每次只能被一个进程使用;(化妆例子里两个进程即两个女士都想要镜子)
B、请求与保持条件:一个进程因为请求资源而阻塞时,对已获取的资源保持不放(比如一个女士拿了镜子锁,然后镜子不给另一个女士还想要口红锁)
C、不剥夺条件:进程已获取的资源,在未使用完之前,不能强制剥夺(一个女士拿着镜子,另一个女士不能抢)
D、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系(你想要我的,我想要你的)
只需要破坏上面至少一个条件就可以避免死锁发生!
比如化妆例子可以破坏请求与保持条件:获取镜子锁的代码块里把获取口红锁代码放到外面。
13、Lock
jdk5之后出现,使用起来也非常简单,首先明确Lock的实现类ReentrantLock可重入锁,具体实现如下
private final ReentrantLock lock = new ReenTrantLock();
然后对不安全买票代码进行try-catch-finally,try里不安全买票代码前lock.lock()显式加锁,然后finally里lock.unlock(),显式释放锁。(Lock和synchronized区别在:前者是显示锁必须开启和关闭锁,后者是隐式锁,出了作用域自动释放锁;前者只能用于代码块,后者可以用代码块和方法;Lock可扩展性更高,性能也更好,优先顺序是Lock、synchronized同步代码块、synchronized同步方法)
14、线程协作
生产者消费者模式是问题
使用wait和notifyAll方法
15、线程池
为了提高性能,类似生活中的共享单车重复使用。
使用过程
前提是写个类实现Runnable接口,所以这个例子说明不仅仅要用new Thread代理实现。
第一步创建服务,创建线程池
ExecutorService service = Excutors.newFixedThreadPool(10);
(注:idea快捷键es就会出现ExecutorService)
(Excutors里都是静态方法,直接调用)
第二步执行
service.execute(new 实现了Runnable接口的类)
(Callable那是submit方法,这两种方法区别在submit方法有返回值,而execute没有)
第三步关闭连接
service.shutdown()
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值