java高级特性多线程基础

java高级特性多线程基础TOC 一 Java 多线程 1 Java 多线程基础知识 Java 给多线程编程提供了内置的支持 一条线程指的是进程中一个单一顺序的控制流 一个进程中可以并发多个线程 每条线程并行执行不同的任务 进程 一个进程包括由操作系统分配的内存空间 包含一个或多个线程 一个线程不能独立的存在 它必须是进程的一部分 一个进程一直运行 直到所有的非守护线程都结束运行后才能结束 2

大家好,我是讯享网,很高兴认识大家。



[TOC]

一、Java多线程

1.Java多线程基础知识

进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

2.Java线程的生命周期

  • 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  • 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
    线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  • 阻塞(BLOCKED):表示线程阻塞于锁。
    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
    • 其他阻塞:通过调用线程的Sleep()或Join发出I/O请求,线程就会进入到阻塞状态,当Sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕,线程就会进入就绪状态。
  • 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  • 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  • 终止(TERMINATED):表示该线程已经执行完毕。
    线程生命周期

3.线程的优先级

每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。

Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

4.创建线程

Java创建线程提供了以下三种方法:

  • 通过实现Runnable接口
  • 通过继承Thread类本身
  • 通过Callable和Future创建线程
4.1 通过实现Runnable接口来创建线程

通过Runnable接口需要重写run() 方法

 
讯享网 
讯享网

这里,threadOb 是一个实现 Runnable 接口的类的实例,并且 threadName 指定新线程的名字。

新线程创建之后,你调用它的 start() 方法它才会运行。

 
4.2 通过继承Thread来创建线程

Thread方法:

序号 方法描述 1 public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 2 public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 3 public final void setName(String name) 改变线程名称,使之与参数 name 相同。 4 public final void setPriority(int priority) 更改线程的优先级。 5 public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 6 public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。 7 public void interrupt() 中断线程。 8 public final boolean isAlive()

Thread静态类方法

序号 方法描述 1 public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 2 public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 3 public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。 4 public static Thread currentThread() 返回对当前正在执行的线程对象的引用。 5 public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。
4.3 通过Callable和Future创建线程
  • 创建 Callable 接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
  • 创建 Callable 实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该 Callable 对象的 call() 方法的返回值。
  • 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
  • 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
    For Example:
讯享网

5.线程的取消和中断

  • 不安全的取消
    Stop()/suspend()/resume()是过期的API,容易导致死锁或者数据不一致
  • 安全的终止线程
    • interrupt() 中断线程,本质是将线程的中断标志位设为true,其他线程向需要中断的线程打个招呼。是否真正进行中断由线程自己决定
    • isInterrupted() 线程检查自己的中断标志位
    • 静态方法Thread.interrupted() 将中断标志位复位为false

6.常用的方法理解

  • run()和start()

    run就是一个普通的方法,跟其他类的实例方法没有任何区别。
  • Sleep <Br>
    不会释放锁,所以我们在用sleep时,要把sleep放在同步代码块的外面。
  • yield()<Br>
    当前线程出让cpu占有权,当前线程变成了可运行状态,下一时刻仍然可能被cpu选中,不会释放锁。
  • wait()和 notify()/notiyfAll()

    调用以前,当前线程必须要持有锁,调用了wait() notify()/notiyfAll()会释放锁。

通知机制:<Br>
线程 A调用了对象O的wait方法进入等待状态,线程 B调用了对象O的notify方法进行唤醒,唤醒的是在对象O上wait的线程(比如线程A)
notify() 唤醒一个线程,唤醒哪一个完全看cpu的心情(谨慎使用)

notiyfAll() 所有在对象O上wait的线程全部唤醒(应该用notiyfAll())

6.线程关键字

6.1 volatile

多个线程同时访问一个共享的变量的时候,每个线程的工作内存有这个变量的一个拷贝,变量本身还是保存在共享内存中。
Violate修饰字段,对这个变量的访问必须要从共享内存刷新一次。最新的修改写回共享内存。可以保证字段的可见性。绝对不是线程安全的,没有操作的原子性。
适用场景:

1、一个线程写,多个线程读;

2、volatile变量的变化很固定

 
 
6.2 synchronized

关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制。
Synchronized的类锁和对象锁,本质上是两把锁,类锁实际锁的是每一个类的class对象。对象锁锁的是当前对象实例。

 
6.3等待和通知机制
  • 等待方原则:

1、获取对象锁

2、如果条件不满足,调用对象的wait方法,被通知后依然要检查条件是否满足

3、条件满足以后,才能执行相关的业务逻辑

 
  • 通知方原则

1、获得对象的锁;<Br>
2、 改变条件;<Br>
3、 通知所有等待在对象的线程

 
6.4管道输入输出流

文件输入输出,网络输入输出,管道输入输出流用于线程中间的数据传递,传输媒介的内存
pipedOutputStream/input 面向的字节

pipedReader/Writer 面向的是字符

只适合线程间一对一的通信,适用范围较狭窄。

 
6.5 join方法

线程A,执行了thread.join(),线程A等待thread线程终止了以后,A在join后面的语句才会继续执行

6.6 一些有用的方法

很多时候我们希望在元素不存在时插入元素,我们一般会像下面那样写代码

 

putIfAbsent(key,value)方法原子性的实现了同样的功能

 

如果key对应的value不存在,则put进去,返回null。否则不put,返回已存在的value。
boolean remove(Object key, Object value)
如果key对应的值是value,则移除K-V,返回true。否则不移除,返回false。
boolean replace(K key, V oldValue, V newValue)
如果key对应的当前值是oldValue,则替换为newValue,返回true。否则不替换,返回false。

二、并发工具和并发容器

1.Hash

散列,任意长度的输入,通过一种算法,变换成固定长度的输出。属于压缩的映射。Md5,Sha,取余都是散列算法,ConcurrentHashMap中是wang/jenkins算法

2.ConcurrentHashMap

在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。

HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞或轮询状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。

3.ConcurrentHashMap在JDK1.7中的实现

分段锁的设计思路

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment实际是一种可重入锁(ReentrantLock),HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组。Segment的结构和HashMap类似,是一种数组和链表结构。一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得与它对应的Segment锁。

ConcurrentHashMap

ConcurrentHashMap初始化方法是通过initialCapacity、loadFactor和concurrencyLevel(参数concurrencyLevel是用户估计的并发级别,就是说你觉得最多有多少线程共同修改这个map,根据这个来确定Segment数组的大小concurrencyLevel默认是DEFAULT_CONCURRENCY_LEVEL = 16;)。

ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。HashEntry代表每个hash链中的一个节点,可以看到其中的对象属性要么是final的,要么是volatile的。

4.ConcurrentHashMap在1.8下的实现


Table数组+单向链表+红黑树

5.ConcurrentSkipListMap和ConcurrentSkipListSet

ConcurrentSkipListMap TreeMap的并发实现
有序Map
ConcurrentSkipListSet TreeSet的并发实现
有序Set

6.SkipList

如果需要的是一个能够进行二分查找,又能快速添加和删除元素的数据结构,首先就是二叉查找树,二叉查找树在最坏情况下可能变成一个链表。

于是,就出现了平衡二叉树,根据平衡算法的不同有AVL树,B-Tree,B+Tree,红黑树等,但是AVL树实现起来比较复杂,平衡操作较难理解,这时候就可以用SkipList跳跃表结构。

传统意义的单链表是一个线性结构,向有序的链表中插入一个节点需要O(n)的时间,查找操作需要O(n)的时间。

跳表

如果我们使用上图所示的跳跃表,就可以减少查找所需时间为O(n/2),因为我们可以先通过每个节点的最上面的指针先进行查找,这样子就能跳过一半的节点。

比如我们想查找19,首先和6比较,大于6之后,在和9进行比较,然后在和12进行比较......最后比较到21的时候,发现21大于19,说明查找的点在17和21之间,从这个过程中,我们可以看出,查找的时候跳过了3、7、12等点,因此查找的复杂度为O(n/2)。

7.ConcurrentLinkedQueue 无界非阻塞队列

常用方法:

Add,offer:添加元素

Peek:get头元素并不把元素拿走

poll():get头元素把元素拿走

8.CopyOnWriteArrayList和CopyOnWriteArraySet

写的时候进行复制,可以进行并发的读。

适用读多写少的场景:比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索。

弱点:内存占用高,数据一致性弱

Tips 多用isEmpty()少用 Size()

9.Lock

Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;

Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

1.接口类

 
  • lock 获取锁
 
  • tryLock() 尝试获取锁 返回类型: boolean
 
  • lockInterruptibly() 线程中断
 

三、阻塞对列

队列满额,线程数据无法插入,就会被阻塞等待数据插入,直到队列可以插入为止,当队列为空时,会被阻塞,直到数据插入后才可以唤醒线程移除。

1.常用的方法

方法 抛出异常 返回值 一直阻塞 超时退出 插入 Add offer put offer 移除 remove poll take poll 检查 element peek 没有 没有

2.常用阻塞队列

  • ArrayBlockingQueue

数组结构组成有界阻塞队列,先进先出原则,初始化必须传大小,take和put时候用的同一把锁。

  • LinkedBlockingQueue

链表结构组成的有界阻塞队列,先进先出原则,初始化可以不传大小,put,take锁分离

  • PriorityBlockingQueue

支持优先级排序的无界阻塞队列,自然顺序升序排列,更改顺序:类自己实现compareTo()方法,初始化PriorityBlockingQueue指定一个比较器Comparator

  • DelayQueue

使用了优先级队列的无界阻塞队列,支持延时获取,队列里的元素要实现Delay接口。DelayQueue非常有用,可以将DelayQueue运用在以下应用场景:

缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。还有订单到期,限时支付等.

  • SynchronousQueue

不存储元素的阻塞队列,每个put操作必须要等take操作

  • LinkedTransferQueue

链表结构组成的无界阻塞队列,Transfer,tryTransfer,生产者put时,当前有消费者take,生产者直接把元素传给消费者

  • LinkedBlockingDeque

链表结构组成的双向阻塞队列,可以在队列的两端插入和移除,xxxFirst头部操作,xxxLast尾部操作。工作窃取模式。

3.阻塞队的实现原理

Java 阻塞队列使用 Condititon、Lock,在元素进入使用等待wait(),出队使用唤醒。

4.For/Join框架

java高级特性多线程基础
kyijQs.png

①ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务
中执行fork()和join()操作的机制。通常情况下,我们不需要直接继承ForkJoinTask类,只需要继承它的子类,Fork/Join框架提供了以下两个子类。
·RecursiveAction:用于没有返回结果的任务。
·RecursiveTask:用于有返回结果的任务。

5.CountDownLatch

允许一个或多个线程等待其他线程完成操作。CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传入N。当我们调用CountDownLatch的countDown方法时,N就会减1,CountDownLatch的await方法会阻塞当前线程,直到N变成零。由于countDown方法可以用在任何地方,所以这里说的N个点,可以是N个线程,也可以是1个线程里的N个执行步骤。用在多个线程时,只需要把这个CountDownLatch的引用传递到线程里即可。

6.CyclicBarrier

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

CyclicBarrier和CountDownLatch的区别

四 、Java 8高级特性

1、Lambda 表达式

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

  • Style 1 无参,无返回值,Lambda只需要一条语句
 
  • Style 2 一个参数
 
  • Style 3 参数省略括号
 
  • Style 4 2个参数,具有返回值
 
  • Style 5 省略return和大括号
 
  • Style 6 类型推断写法
 

2.Stream API

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

  • 创建Stream

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

 

3.Stream 中间操作

原始集合:

 
  • List转Map
 
  • 分组
 
  • 过滤 filter
 
  • 求和
小讯
上一篇 2024-12-28 12:00
下一篇 2024-12-27 07:36

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/4807.html