java_多线程

java_多线程

进程

​ 程序是静止的,只有真正运行时(CPU资源分配给程序)的程序,才被称为进程

​ 单核CPU,在任何时间点上,只能运行一个进程;宏观并行、微观串行

线程

​ 线程(轻量级进程)。程序中的一个顺序控制流程,同时也是CPU基本的调度单位。

多线程

​ 进程由多个线程组成,彼此间完成不同的工作,交替执行,称为多线程。

线程的组成

​ 1、CPU时间片:操作系统(OS)会为每个线程分配执行时间。

​ 2、运行数据:

​ 对空间:存储线程需使用的对象,多个线程可以共享堆中的对象

​ 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。

​ 3、线程的逻辑代码

创建线程的两种方式

第一种方式

1、继承Thread类。2、覆盖run()方法。3、创建子类的对象。4、调用start()方法

第二种方式(常用)

1、实现Runnable接口。2、覆盖run()方法。3、创建实现类的对象。4、创建线程对象。5、调用start()方法。

扫描二维码关注公众号,回复: 3051567 查看本文章

线程的状态(基本)

这里写图片描述

常见方法

•  休眠:
    •  public static void sleep(long millis)
    •  当前线程主动休眠 millis毫秒。
•  放弃:
    •  public static void yield()
    •  当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
•  结合:
    •  public final void join()
    •  允许其他线程加入到当前线程中。

线程的状态(等待)

这里写图片描述

线程的安全问题

线程不安全:

当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。

临界资源:共享资源(同一对象)一次仅允许一个线程使用,才可保证其正确性。

原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。

线程安全–同步方式(1)

•  同步代码块:
synchronized( 临界资源对象 ){ // 对临界资源对象加锁
    // 代码(原子操作)
}

每个对象都有一个互斥锁标记,用来分配给线程的 。
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
线程退出同步代码块时,会释放相应的互斥锁标记。

线程安全–同步方式(2)

•  同步方法:
synchronized 返回值类型 方法名称(形参列表){//对当前对象this加锁
    // 代码(原子操作)
}

只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
线程退出同步方法时,会释放相应的互斥锁标记.

同步规则

• 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。

• 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。

• 已知 JDK 中线程安全的类:

    •  StringBuffer
    •  Vector
    •  Hashtable
    •  以上类中的公开方法,均为 synchonized 修饰的同步方法。

线程的状态(阻塞)

这里写图片描述

阻塞状态的进入条件:未得到标记锁

注:JDK5 之后就绪、运行统称Runnable

线程通信( wait()和notify() )

•  等待:
    •   public final void wait()
    •  public final void wait(long timeout)
    //•必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁,进入等待队列。
•  通知:
    •  public final void notify()
    •  public final void notifyAll()
    //•必须在对obj加锁的同步代码块中。从obj的Waiting中释放一个或全部线程。对自身没有任何影响。

经典问题

• 死锁:

​ • 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。

​ • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。

• 生产者、消费者:

​ • 若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。

高级多线程

• 现有问题:

​ • 线程是宝贵的内存资源,单个线程约占 1MB 空间,过多分配易造成内存溢出。

​ • 频繁的创建及销毁 线程 会增加虚拟机回收频率,资源开销;造成程序性能下降

• 线程池:

​ • 线程容器,可设定线程分配的数量上限。

​ • 将预先创建线程对象存入池中,并重用线程池中的线程对象。

线程池原理

• 将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程。


获取线程池

Executor 顶级接口

ExecutorService线程池接口,通过submit(Runnable task)提价任务代码

Executors工厂类

newFixedThreadPool(int nThreads)
//获取固定数量的线程池。参数:指定线程池中线程的数量。
----------------------------------------------------------
newCachedThreadPool()
//获得动态数量的线程池,如不够则创建新的,没有上限

Callable接口与Runnable

• JDK5 加入Callable,与 Runnable 接口类似,实现之后代表一个线程任务。

• Callable**具有泛型返回值可以声明异常**。Runnable不能声明异常

Future接口

概念:异步接收 ExecutorService.submit() 所返回的状态结果,当中包含了 call() 的返回值。

方法:V get()阻塞形式等待 Future 中的异步处理结果( call() 的返回值)

Lock接口

• JDK5加入,与 synchronized 比较,显示定义,结构更灵活。

• 提供更多实用性方法,功能更强大、性能更优越 。
• 常用方法:

void lock() //获取锁,如锁被占用,则等待。
boolean tryLock()//尝试获取锁(功返回true失败返回false,不阻塞)
void unlock() //释放锁

重入锁ReentrantLock–>Lock的实现类

与 synchronized 一样具有互斥锁功能

读写锁

ReentrantReadWriteLock :

• 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。

• 支持多次分配读锁,使多个读操作可以并发执行。

互斥规则

• 写- - 写:互斥,阻塞 。

• 读- - 写:互斥,读阻塞写、写阻塞读。

• 读- - 读:不互斥、不阻塞 。

• 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。

Collection集合体系(高亮:线程安全集合)

List 、Set 、Queue

Conllections也提供了获得线程安全集合的方法

public static <T> Collection<T> synchronizedCollection(Collection<T> c)
--------------------------------------------------
public static <T> List<T> synchronizedList(List<T> list)
--------------------------------------------------
public static <T> Set<T> synchronizedSet(Set<T> s)
-----------------------------------------
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
--------------------------------------------------
public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
--------------------------------------------------
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)

JDK1.2 提供,接口统一、维护性高,但性能没有提升,均以 synchonized 实现

CopyOnWriteArrayList

• 线程安全的ArrayList,加强版读写分离。

• 写有锁,读无锁,读写之间不阻塞,优于读写锁。

• 写入时,先copy一个容器副本、再添加新元素,最后替换引用。

• 使用方式与ArrayList无异。

CopyOnWriteArraySet

• 线程安全的 Set ,底层使用 CopyOnWriteArray List 实现 。

• 唯一不同在于,使用 addIfAbsent() 添加元素,会遍历数组,

• 如存在元素,则不添加(扔掉副本)。

ConcurrentHashMap

• 初始容量默认为 16 段( Segment ),使用分段锁设计。

• 不对整个 Map 加锁,而是为每个 Segment 加锁。

• 当多个对象存入同一个 Segment 时,才需要互斥。

• 最理想状态为 16 个对象分别存入 16 个 Segment ,并行数量 16 。

• 使用方式与 HashMap

Queue接口(队列)

• Collection 的子接口,表示队列 FIFO ( First In First Out )

• 常用方法:

•  抛出异常:
    •  boolean add(E e)
    // 顺序添加一个元素(到达上限后,再添加则会抛出异常)
    •  E remove() 
    // 获得第一个元素并移除(如果队列没有元素时,则抛异常)
    •  E element()
    // 获得第一个元素但不移除(如果队列没有元素时,则抛异常)
•  返回特殊值: 推荐使用
    •  boolean offer(E e) 
    // 顺序添加一个元素 (到达上限后,再添加则会返回 false )
    •  E poll() 
    // 获得第一个元素并移除 (如果队列没有元素时,则返回 null )
    •  E keep()
    // 获得第一个元素但不移除 (如果队列没有元素时,则返回 null

ConcurrentLinkedQueue

• 线程安全、可高效读写的队列,高并发下性能最好的队列。

• 无锁、 CAS 比较交换算法,修改的方法包含三个核心 参数 ( V,E,N )

• V :要更新的变量、E :预期值、 N :新值。

• 只有当 V==E 时, V=N

BlockingQueue接口(阻塞队列)

• Queue 的子接口,阻塞的队列,增加了两个线程状态为无限期 等待的方法 。

• 方法:

​ • void put(E e) // 将指定元素插入此队列中 ,如果没有可用空间,则等待。

​ • E take() // 获取并移除此队列头部 元素,如果没有可用元素,则等待。

• 可用于解决生产生、消费者问题

阻塞队列

• ArrayBlockingQueue :

​ 数组结构实现,有界队列。(手工固定上限)

• LinkedBlockingQueue :

​ • 链表结构实现,无界队列。(默认上限 Integer.MAX_VALUE)

小结

•  ExecutorService 线程池接口、 Executors 工厂。
•  Callable 线程任务、 Future 异步返回值。
•  Lock 、 ReentrantLock 重入锁、 ReentrantReadWriteLock 读写锁。
•  CopyOnWriteArrayList 线程安全的 ArrayList 。
•  CopyOnWriteArraySet 线程安全的 Set 。
•  ConcurrentHashMap 线程安全的 HashMap 。
•  ConcurrentLinkedQueue 线程安全的 Queue 。
•  ArrayBlockingQueue 线程安全的阻塞 Queue 。(生产者、消费者)

猜你喜欢

转载自blog.csdn.net/qq_38928944/article/details/79763887