目录
1.进程与线程的区别
2.线程创建
Thread class -> 继承Thread类
Runnable 接口-> 实现Runnable接口
Callable接口-> 实现Callable接口
3.
4.
5.
6.
7.
8.
9.多线程争夺同一个资源,并发问题,结果会出现多个人抢到了同一张票的情况。
10.
11.
12.
二.静态代理
总结:1.真实对象和代理对象都要实现同一个接口
2.代理对象要代理真实角色
好处:1.代理对象可以做很多真实对象做不了的事情
2.真实对象专注于做自己的事情
三、Lamda表达式
1,
2.为什么要使用lamda表达式
1.避免匿名内部类定义过多
2.可以让你的代码看起来简洁
3.去掉了一堆没有意义的代码,只留下核心的逻辑
3.
演变过程:
普通接口:定义一个函数式接口
静态内部类
局部内部类
匿名内部类
用lambda简化
总结:
1.Lamda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹,前提是接口为函数式接口
2.多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号
四、线程状态
1.
下列代码可能存在问题
五、线程休眠
模拟网络延时:放大问题的发生性
六.线程礼让
七、Join
八.线程状态
九.线程优先级
十.守护线程
用户线程:我们平常创建的普通线程。
守护线程(即 Daemon thread):是个服务线程,用来服务于用户线程;不需要上层逻辑介入,当然我们也可以手动创建一个守护线程。在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。
二、为什么需要守护线程
十一.线程同步
并发:同一个对象被多个线程同时操作
线程同步:多个线程操作同一个资源
同步形成条件:队列+锁
上述线程不安全,有负数
解决:在buy方法前加synchronize 同步方法,锁的是this,可以让买票安全
上面那个方法写错了,替换为下面这个
如果没有产生线程安全问题,在计算卡内余额上面部分添加延时,sleep可以放大问题的发生性
解决:如果把synchronized放在run方法前面,可以发现没有锁住,synchronized默认锁的是this
改成用代码块锁住(判断哪里进行了增删改,锁的对象就是变化的量)):
下列也是不安全,
解决:
同步方法弊端:只读代码不需要加锁,修改代码需要加锁(方法里面需要修改的内容才需要加锁,锁的太多,浪费资源)
下列集合线程安全,结果是1w
十二.死锁
上述情况会造成死锁,两个人都只能获得各自的第一把锁
修改后如下:可以各自获得两把锁
加入lock锁:
线程协作:生产者消费者模式
管程法
下列代码可能也有问题:
package cn.com.chnsys;
/**
* @Description:
* @Author: wangxb
* @Datetime: 2023/2/27 17:12
* @Version: 1.0
*/
//测试:生产者消费者模型->利用缓冲区解决:管程法
public class TestPC {
public static void main(String[] args) {
SynContainer container=new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread {
SynContainer container;
public Productor(SynContainer container) {
this.container = container;
}
//生产
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println("生产了"+i+"只鸡");
container.push(new Chicken((i)));
}
}
}
//消费者
class Consumer extends Thread {
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
//消费
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println("消费了---》"+container.pop().id+"只鸡");
container.push(new Chicken((i)));
}
}
}
//产品
class Chicken {
int id;//产品编号
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer {
//需要一个容器大小
Chicken[] chickens = new Chicken[10];
//容器计数器
int count = 0;
//生产者放入产品
public synchronized void push(Chicken chicken) {
//如果容器满了,就需要等待消费者消费
if (count == chickens.length) {
//通知消费者消费,生产等待
try {
this.wait();
}catch (InterruptedException e){
System.out.println(e.getMessage());
}
}
//如果没有满,需要丢入产品
chickens[count] = chicken;
count++;
//可以通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop() {
//判断能否消费
if (count == 0) {
//等待生产者生产,消费者等待
try{
this.wait();
}catch (InterruptedException e){
System.out.println(e.getMessage());
}
}
//如果可以消费
count--;
Chicken chicken = chickens[count];
//吃完了,通知生产者生产
this.notifyAll();
return chicken;
}
}
package cn.com.chnsys;
/**
* @Description:
* @Author: wangxb
* @Datetime: 2023/2/27 17:56
* @Version: 1.0
*/
//测试生产者消费者问题2:信号灯法,标志位解决
public class TestPc2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者-》演员
class Player extends Thread {
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
this.tv.play("快乐播放中");
} else {
this.tv.play("记录美好生活");
}
}
}
}
//消费者-》观众
class Watcher extends Thread {
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//产品-》节目
class TV {
//演员表演,观众等待
//观众观看,演员等待
String voice;//表演的节目
boolean flag = true;
//表演
public synchronized void play(String voice) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
System.out.println("演员表演了:" + voice);
//通知观众观看
this.notifyAll();//通知唤醒
this.voice = voice;
this.flag = !this.flag;
}
//观看
public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
System.out.println("观看了:" + voice);
//通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
线程池
线程池的创建⽅法总共有 7 种,但总体来说可分为 2 类:
-
通过
ThreadPoolExecutor
创建的线程池; -
通过
Executors
创建的线程池。
线程池的创建⽅式总共包含以下 7 种(其中 6 种是通过 Executors
创建的, 1 种是通过ThreadPoolExecutor
创建的):
-
Executors.newFixedThreadPool
:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待; -
Executors.newCachedThreadPool
:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程; -
Executors.newSingleThreadExecutor
:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序; -
Executors.newScheduledThreadPool
:创建⼀个可以执⾏延迟任务的线程池; -
Executors.newSingleThreadScheduledExecutor
:创建⼀个单线程的可以执⾏延迟任务的线程池; -
Executors.newWorkStealingPool
:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。 -
ThreadPoolExecutor
:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置,后⾯会详细讲。
【强制】
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式更加明确线程池的运行规则,规避资源耗尽的风险。说明:Executors 返回的线程池对象的弊端如下
1)FixedThreadPool和SingleThreadPool :允许的请求队列长度为 Integer.MAX VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool :
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
以上内容好像(具体是谁有点忘了)是b站狂神的视频笔记,感兴趣可以直接去B站观看~~