多线程基础篇

我们平常说的一个程序,一个程序中有声音,图片,字幕
实际上是一个进程中有多个线程

main线程是主线程。
多核,多个cpu,多个线程,切换的很快
单核的话是一个cpu,某一时间只能是一个线程,但是因为切换的很快, 因此有同时执行的错觉。

在一个进程中,如果开辟了多个线程,线程的运行是由调度器安排调度,调度器与操作系统紧密相关,先后顺序是不能认为干预的。
对同一份资源操作,会存在资源抢夺的问题,需要加入并发控制。
线程会带来额外的开销,cpu调度,线程的切换等

每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。ThreadLocal

创建线程的三种方式

1、继承thread类
2、实现Runnable接口
3、实现Callable接口

继承Thread类,重写run方法,调用start方法执行

多线程基础篇_第1张图片

实现Runnable接口,重写run方法,调用start方法执行

需要创建runnable接口的实现类对象,然后创建线程对象,通过线程对象开启线程,代理
多线程基础篇_第2张图片java是单继承,我们一般使用runnable接口创建线程。

多线程基础篇_第3张图片
这里我们可以看到一个对象可以被多个线程使用,换句话说就是多个线程会使用同一个资源,那么这里就会涉及到并发问题->线程安全问题->数据紊乱

实现Callable接口

多线程基础篇_第4张图片这里的创建服务的是一个池子。有几个线程,nThread就是多少
submit是提交执行线程。
多线程基础篇_第5张图片

Thread的常用方法

Thread currentThread()   //获取当前线程
Thread.currentThread().getName() //获取当前线程的名称 
setPriority(int newPriority)  //更改线程的优先级
getPriority()  //获取线程优先级
static void sleep(long millis) //休眠
static void yield()//当前线程对象暂停执行,执行其他线程,一直到其他线程执行完继续执行当前线程
void join() //插队相当于vip
void interrupt() //中断线程
boolean isAlive() //判断线程的是否存活
Thread.State getState()//获取线程的状态
setDaemon(true)  // 设置线程为守护线程

yield礼让线程,从运行状态转换为就绪状态,重新竞争资源,礼让不一定成功
join 霸道线程,直接插队,其他执行线程变成阻塞状态

如何优雅的暂停线程

stop/destroy是不推荐的
1、一般是通过一个标志位进行终止变量,flag=false
2、推荐线程自己停止,利用次数,不建议死循环

代理模式

静态代理:

前提:代理对象和目标对象要实现同一个接口。
就是代理对象中有目标对象,在代理对象的类中有一个方法调用了目标对象的方法。在实际使用的时候,实例化到了目标对象到代理对象中,然后代理对象调用方法。
好处:代理对象帮助目标对象做做不到的事情,目标对象只需要做自己的事情

动态代理

线程的状态

多线程基础篇_第6张图片
创建状态—new Thread()
就绪状态–start()
等待cpu调度,调度了就会运行–运行状态–线程执行线程体的代码块
阻塞状态—运行中的线程sleep了,也就是说cpu被抢夺
只有当阻塞状态接触,会重新进入就绪状态,等待cpu的调度。

Thread.State

多线程基础篇_第7张图片
NEW :新生状态
RUNNABLE :运行状态
BLOCKED :阻塞状态
WAITING:等待
TIME_WAITING:
TERMINATED:死亡
死亡后的线程不能再次启动。会报错的

线程的优先级

线程调度器是根据优先级来调度线程的,优先级高的最先被调度,同级的随机调度。
线程优先级用数字表示,范围是1-10

多线程基础篇_第8张图片

守护线程daemon

线程氛围用户线程和守护线程
虚拟机必须确保用户线程(main)执行完毕,虚拟机不用等待守护线程(gc)执行完毕。
比如后台记录日志,监控内存,垃圾回收等等都是守护线程。
当主线程运行完毕,虚拟机就关闭了,即使守护线程还在运行。

线程同步

线程同步其实就是一种等待机制,多个线程竞争这个资源的时候,会有一个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用。

队列和锁synochronized

线程同步需要队列和锁,每个对象都有一把锁,这样才能保证线程的安全性。

由于同一个进程的多个线程共享同一块存储空间,有数据的一个共享,因此会带来线程不安全和并发的问题,为了保证数据在方法中被访问时的正确性,我们在访问的时候加入锁机制synchronized,当一个线程获得对象的排它锁,就会独占资源,其他线程必须等待,当该线程使用完毕后会释放锁。
但可能会出现以下问题:多线程基础篇_第9张图片同步方法
public synchronized void method(int args){}
这个方法控制对对象的访问,每个对象都有一把锁,synchronized关键字是当访问这个方法的时候必须要获取对象的锁之后才可以访问。
弊端:锁住了整个方法,耗费资源
同步块
当方法里面有需要修改的内容才需要锁,因此出现了同步代码块

但是需要注意的是:synchronized是获取的当前对象的锁,默认锁的是this本身。因此我们需要使用同步块,同步块是同步监视器,可以监视任意对象,换句话说,锁的是共享资源。
多线程基础篇_第10张图片比如银行取钱,锁的应该是账户,多个线程访问账户的时候,都应该按照队列进行。因此我们需要使用的是同步代码快。
换句话说,锁的对象是变化的,需要增删改的对象。
synchronized是一个隐式锁,我们不知道他何时

JUC–线程安全集合CopyOnWriteArrayList

这个集合是java.util.concurrent包下的,java并发包下的
Arraylist是线程不安全的。
CopyOnWriteArrayList是线程安全的,不需要我们手动加锁。

死锁

Lock锁

从jdk5.0开始,jdk就提供了一个更强大的线程同步机制:通过显示定义同步锁对象来实现同步—Lock锁。
他是java.util.concurrent包下的,它拥有与synchronized相同的并发性和内存语义,可以显示加锁,释放锁。常用的有ReetrantLock锁(可重用锁)。
手动加锁,解锁
多线程基础篇_第11张图片简单应用:
加一个属性是ReetrantLock lock,
在run方法中手动加锁 lock.lock
手动释放锁:lock.unlock();

sycnchronized和lock的对比

1、lock是显式锁,需要手动加锁释放锁,synchronized是隐式锁,出了作用域就自动释放。
2、lock只有代码块锁,而synchronized有方法锁和代码块锁。
3、使用lock锁比使用synchronized锁的性能更好,有更好的扩展性
优先使用顺序
Lock>同步代码快>同步方法
多线程基础篇_第12张图片

线程通信

这个是线程同步的问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
对于生产者,没有生产产品之前,要通知消费者等待,生产产品之后要通知消费者消费。
对于消费者,在消费之后,要通知生产者已经结束消费,仍然需要产品消费。

在这里,synchronized是不同解决这个问题的,因为synchronized是锁住了共享资源,也就是变化的对象,只能保证对象的安全性,实现了同步,但是无法实现不同线程的消息传递。

解决方案:

通知 notify()
等待 wait()
多线程基础篇_第13张图片
多线程基础篇_第14张图片生产者–线程
消费者–线程
产品–普通类
缓冲池–普通类–有容器(集合或者数组)

使用线程池

在并发情况下,经常创建销毁线程对性能的影响很大。
因此我们选择使用线程池,提前创建好线程,放入线程池中,使用的时候直接获取,使用完后放回池中,实现重复利用。

优点:
1、提高响应速度
2、降低资源消耗
3、方便管理
线程池的大小corePoolSize
最大线程数maxinumPoolSize
线程没有任务时多久会终止keepAliveTime
线程池相关API:ExcutorService和Executors
ExecutorService:线程池接口。

常见子类:ThreadPoolExcutor
void  execute(Runnable command):执行任务,没有返回值,执行线程
<T>Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行Callable
void shutdown()关闭连接池

Executors:线程池工具类,用于常见返回不同类型的线程池
Executors.newFixedThreadPool(int nThread)创建线程池,返回的是ExcutorService,参数nThread为线程池大小

你可能感兴趣的:(JUC,java)