文章目录
- JUC并发编程
-
-
-
- JUC简介
- 进程、线程的关系
- Java默认有两个线程
- 线程的六种状态
- 并发、并行的关系:
- Lock锁
- Synchronized 和 Lock的区别
- 8锁现象
-
- 问题一 : 在标准情况下,两个线程先打印 发短信 还是 打电话 ?
- 问题二 : 在发短信方法中,延迟4秒,两个线程先打印 发短信 还是 打电话?
- 问题三 : Phone类增加一个普通方法,线程B调用,那么两个线程先打印 发短信 还是 打电话?
- 问题四 : 创建两个 phone对象,线程调用不同对象的方法,那么两个线程先打印 发短信 还是 打电话?
- 问题五 : 将方法变为静态同步方法,那么两个线程先打印 发短信 还是 打电话?
- 问题六 : 现在有两个对象,调用不同对象的,那么两个线程先打印 发短信 还是 打电话?
- 问题七 : 资源类中,一个是静态同步方法,一个普通同步方法,那么两个线程先打印 发短信 还是 打电话?
- 问题八: 在标准情况下,两个线程先打印 发短信 还是 打电话 ?
- 总结:
- 集合类不安全
-
-
- 创建线程
- JUC 常用的辅助类
- 我的学习论坛
JUC并发编程
JUC简介
来源于 java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks 这三个包(简称JUC ),在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等。
进程、线程的关系
进程:一个程序,.exe Music.exe 程序的集合。 线程:一个进程往往可以包含多个线程,至少包含一个!
Java默认有两个线程
mian 线程 GC 线程
线程的六种状态
新生 NEW 运行 RUNNABLE 阻塞 BLOCKED等待 死死地等 WAITING 超时等待 TIMED_WAITING 终止 TERMINATED
并发、并行的关系:
并发(多线程操作同一个资源) 并行:(多个人一起行走) CPU 多核 ,多个线程可以同时执行; 并发编程的本质:充分利用CPU的资源
Lock锁
Lock锁是一个接口,其所有的实现类为:
ReentrantLock(可重入锁) ReentrantReadWriteLock.ReadLock(可重入读写锁中的读锁) ReentrantReadWriteLock.WriteLock(可重入读写锁中的写锁)
Lock lock = new ReentrantLock(); public void doTicket(){
lock.lock(); //加锁 try {
System.out.println(Thread.currentThread().getName()); } catch (Exception e) {
e.printStackTrace(); } finally {
lock.unlock(); // 解锁 } }
Synchronized 和 Lock的区别
来源不同 synchronize => java的内置关键字,在jvm层;Lock =>java的一个接口 获取锁得方式不同 synchronize => 自动获取锁,不能判断锁得状态;Lock => 手动获取锁,可判断是否获取到锁 线程阻塞方面 synchronize => 线程1阻塞会导致线程2永远等待;Lock=>不一定会等下去 锁得类型不同 synchronize=>可重入锁、不可中断、非公平;Lock=>可重入锁、可判断锁、非公平(可设置成公平) 使用范围不同 synchronize=>适用于少量代码块同步;Lock=>适合锁大量的同步代码块
8锁现象
8锁现象,实际对应的就是8个问题。
掌握了这8个问题后:可以清楚判断锁的是谁!永远的知道什么是锁,锁到底锁的是谁!
问题一 : 在标准情况下,两个线程先打印 发短信 还是 打电话 ?
public class Test1 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone(); // 线程A new Thread(()->{
phone.seedMsg();}, "A").start(); // 4秒延迟 TimeUnit.SECONDS.sleep(4); // 线程B new Thread(()->{
phone.call();}, "B").start(); } } class Phone{
public synchronized void seedMsg(){
System.out.println("发短信"); } public synchronized void call(){
System.out.println("打电话"); } }
结果是:先打印发短信,然后再打电话!
问题二 : 在发短信方法中,延迟4秒,两个线程先打印 发短信 还是 打电话?
public class Test2 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone(); // 线程A new Thread(()->{
phone.seedMsg();}, "A").start(); // 1秒延迟 TimeUnit.SECONDS.sleep(); // 线程B new Thread(()->{
phone.call();}, "B").start(); } } class Phone{
public synchronized void seedMsg() throws Exception{
TimeUnit.SECONDS.sleep(4); System.out.println("发短信"); } public synchronized void call(){
System.out.println("打电话"); } }
结果:还是先打印发短信。
首先知道 锁的对象是谁?因为 synchronized 加在方法上,所以锁的对象是 方法的调用者,所以两个方法用的是同一个锁,谁先拿到谁先执行!
解释:
phone对象,就是方法的调用者,也就是手机,它可以打电话和发短信。 现在有两个人线程A 和 线程B,他们一个想打电话,一个想发短信。 线程A,先拿到锁(也就是手机),抱着锁(手机)睡了4秒。 线程B肯定拿不到锁(手机),需要等待。
问题三 : Phone类增加一个普通方法,线程B调用,那么两个线程先打印 发短信 还是 打电话?
public class Test3 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone(); // 线程A new Thread(()->{
phone.seedMsg();}, "A").start(); // 1秒延迟 TimeUnit.SECONDS.sleep(1); // 线程B new Thread(()->{
phone.hello();}, "B").start(); } } class Phone{
//同步方法 public synchronized void seedMsg(){
// 1秒延迟 try {
TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("发短信"); } //普通方法 public void hello(){
System.out.println("hello"); } }
结果:先打印hello,然后打印发短信。
解释:
锁的是方法的调用者 现在线程B 调用普通方法,相当于可以远程操控,不需要接收消息
问题四 : 创建两个 phone对象,线程调用不同对象的方法,那么两个线程先打印 发短信 还是 打电话?
public class Test4 {
public static void main(String[] args) throws Exception {
Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(()->{
phone1.seedMsg(); }, "A").start(); // 1秒延迟 TimeUnit.SECONDS.sleep(1); new Thread(()->{
phone2.call(); }, "B").start(); } } class Phone{
//同步方法 public synchronized void seedMsg(){
// 4秒延迟 try {
TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("发短信"); } //同步方法 public synchronized void call(){
System.out.println("打电话"); } //普通方法 public void hello(){
System.out.println("hello"); } }
结果:先打印打电话。
解释:synchronized用在方法上,那么锁的是方法的调用者。现在有两个调用者,所以互不影响。
问题五 : 将方法变为静态同步方法,那么两个线程先打印 发短信 还是 打电话?
问题六 : 现在有两个对象,调用不同对象的,那么两个线程先打印 发短信 还是 打电话?
public class Test5 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone(); // 线程A new Thread(()->{
phone.seedMsg();}, "A").start(); // 1秒延迟 TimeUnit.SECONDS.sleep(1); // 线程B new Thread(()->{
phone.call(); }, "B").start(); // 问题6 // Phone phone1 = new Phone(); // Phone phone2 = new Phone(); // new Thread(()->{ phone1.seedMsg();}, "A").start(); // new Thread(()->{ phone2.call();}, "B").start(); } } class Phone{
// 静态同步方法 public static synchronized void seedMsg(){
// 4秒延迟 try {
TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("发短信"); } // 静态同步方法 public static synchronized void call(){
System.out.println("打电话"); } }
结果:先打印发短信。
解释:5、6的问题一样,对于static静态方法来说,对于整个类Class只有一份,对于不同的对象使用的是同一份方法,相当于这个方法是属于这个类,如果静态static方法使用synchronized锁定,那么这个synchronized锁会锁住整个对象!不管多少个对象,对于静态的锁都只有一把锁,谁先拿到这个锁就先执行,其他的进程都需要等待!
问题七 : 资源类中,一个是静态同步方法,一个普通同步方法,那么两个线程先打印 发短信 还是 打电话?
public class Test7 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone(); new Thread(()->{
phone.seedMsg(); }, "A").start(); // 1秒延迟 TimeUnit.SECONDS.sleep(1); new Thread(()->{
phone.call(); }, "B").start(); } } // 手机 class Phone{
// 静态同步方法 public static synchronized void seedMsg(){
// 4秒延迟 try {
TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("发短信"); } // 普通同步方法 public synchronized void call(){
System.out.println("打电话"); } }
结果:先打印打电话,然后打印发短信
解释:因为一个锁的是Class类,一个锁的是对象调用者。后面那个打电话不需要等待发短信,可以直接运行。
问题八: 在标准情况下,两个线程先打印 发短信 还是 打电话 ?
public class Test8 {
public static void main(String[] args) throws Exception{
Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(()->{
phone1.seedMsg(); }, "A").start(); TimeUnit.SECONDS.sleep(1); new Thread(()->{
phone2.call(); }, "B").start(); } } class Phone{
// 静态同步方法 public static synchronized void seedMsg(){
// 4秒延迟 try {
TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("发短信"); } // 普通同步方法 public synchronized void call(){
System.out.println("打电话"); } }
结果:先打印打电话,然后打印发短信
解释:两把锁锁的不是同一个东西,所以后面的第二个对象不需要等待第一个对象的执行。
总结:
new 和 this 是一个对象。 static 和 Class 是唯一的一个模板。
集合类不安全
List不安全
ArrayList 在并发情况下是不安全的!
解决:
Vector就是线程安全的 使用Collections.synchronizedList(new ArrayList<>()); 使用 List arrayList = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList:写入时复制, 是计算机程序设计领域的一种优化策略
多个线程调用的时候,list,读取的时候,固定的,写入(存在覆盖操作);在写入的时候避免覆盖,造成数据错乱的问题;
Set不安全
和List、Set同级的还有一个BlockingQueue 阻塞队列;
使用Collections工具类的synchronized包装的Set类 使用CopyOnWriteArraySet 写入复制的JUC解决方案
Map不安全
使用Collections.synchronizedMap(new HashMap<>());处理; 使用ConcurrentHashMap进行并发处理
创建线程
之前创建线程:
new Thread(()->{
System.out.println("创建handsome线程");},"handsome").start();
Callable创建线程:
Java库具有FutureTask类型,该类型实现Runnable和Future,并方便地将两种功能组合在一起。可以通过为其构造函数提供Callable来创建FutureTask。然后,将FutureTask对象提供给Thread的构造函数以创建Thread对象。因此,间接地使用Callable创建线程。

注意:使用Callable进行多线程操作,多个线程结果会被缓存,效率高。这个get 方法可能会产生阻塞!把他放到 最后,或者使用异步通信来处理!
Callable接口与Runnable接口的区别:
是否有返回值 是否抛出异常 一个是call(),一个是run()
JUC 常用的辅助类
CountDownLatch
这个类使一个线程等待其他线程各自执行完毕后再执行。
主要方法
countDown 减一操作。 await 等待计数器归零。
public static void main(String[] args) throws InterruptedException {
//总数6个 CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() +" 执行do"); //每个线程都数量-1 countDownLatch.countDown(); },String.valueOf(i)).start(); } //等待计数器归零 countDownLatch.await(); System.out.println("必须其他线程都执行完,在执行这里"); //最后执行的... }
CyclickBarrier
用于对多个线程任务进行同步执行。
主要方法
await 在所有线程任务都到达之前,线程任务都是阻塞状态
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙的线程~"); }); for (int i=1;i<=7;i++){
int atI = i; new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" 收集了第" + atI +"颗龙珠"); cyclicBarrier.await(); //加法计数 等待 } catch (InterruptedException e) {
e.printStackTrace(); } catch (BrokenBarrierException e) {
e.printStackTrace(); } },"线程"+i).start(); } }
应用:
CyclickBarrier可以根据基于子线程进行处理其他线程的结果,处理比较复杂的业务。并且可以通过reset方法重新执行方法。 CountDownLoatch则必须在主线程才能处理,一般用于任务执行初始化数据
Semaphore
信号量,在信号量定义两种操作:
acquire(获取)当一个线程调用acquire操作,它通过成功获取信号量(信号量-1),有阻塞,直到有线程释放信号量,或者超时。 release(释放)实际上将信号量的值+1,然后唤醒等待的线程。
public static void main(String[] args) {
//停车位为3个 Semaphore semaphore = new Semaphore(3); // 参数设为1即相当于Synchronized,即可设置占用锁的时间。 // Semaphore semaphore = new Semaphore(1); for (int i=1 ; i<=10; i++){
int atI = i; new Thread(()->{
try {
semaphore.acquire(); //得到 System.out.println(Thread.currentThread().getName() + " 抢到停车位" + atI); TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName() + " 离开停车场"); } catch (Exception e) {
e.printStackTrace(); } finally {
semaphore.release(); //释放 } },"线程"+i).start(); } }
作用: 多个共享资源互斥的使用! 并发限流,控制最大的线程数!
读写锁ReadWriteLock
读写锁:更加细粒度的锁 读-读:可以共存 读-写:不能共存 写-写:不能共存 JUC的目的,就是将锁的粒度变的更细,提高并发效率;至少读-读,可以共存
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache mycache = new MyCache(); //开启5个线程 写入数据 for (int i = 1; i <=5 ; i++) {
int finalI = i; new Thread(()->{
mycache.put(String.valueOf(finalI),String.valueOf(finalI)); }).start(); } //开启10个线程去读取数据 for (int i = 1; i <=10 ; i++) {
int finalI = i; new Thread(()->{
String o = mycache.get(String.valueOf(finalI)); }).start(); } } } class MyCache{
private volatile Map<String,String> map = new HashMap<>(); //普通锁 //private Lock lock = new ReentrantLock(); //使用读写锁 private ReadWriteLock lock = new ReentrantReadWriteLock(); public void put(String key,String value){
//写锁 lock.writeLock().lock(); try {
//写入 System.out.println(Thread.currentThread().getName()+" 线程 开始写入"); map.put(key, value); System.out.println(Thread.currentThread().getName()+" 线程 写入完成"); } finally {
lock.writeLock().unlock(); } } public String get(String key){
//读锁 lock.readLock().lock(); String o; try {
System.out.println(Thread.currentThread().getName()+" 线程 开始读取"); o = map.get(key); System.out.println(Thread.currentThread().getName()+" 线程 读取完成"); } finally {
lock.readLock().unlock(); } return o; } }
对于读取,我们运行多个线程同时读取,也能在一定程度上提高效率。
阻塞队列



ArrayBlockingQueue : 一个由数组结构组成的有界阻塞队列。 LinkedBlockingQueue : 一个由链表结构组成的有界阻塞队列。 PriorityBlockingQueue : 一个支持优先级排序的无界阻塞队列。 DelayQueue: 一个使用优先级队列实现的无界阻塞队列。 SynchronousQueue: 一个不存储元素的阻塞队列。 LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。 LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。
线程池
优点:
降低资源消耗 提高响应速度 提高线程的可管理性
池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。

线程池的使用
Executors:
// 创建线程池 public static void main(String[] args) {
// 单个线程 // ExecutorService executorService = Executors.newSingleThreadExecutor(); // 创建一个固定的线程池的大小 // ExecutorService executorService = Executors.newFixedThreadPool(5); // 可伸缩的 ExecutorService executorService = Executors.newCachedThreadPool(); try {
for (int i=1; i<=80; i++){
//使用线程池之后创建线程 executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+ " ok"); }); } } finally {
executorService.shutdown(); } }
源码分析:线程池的真正实现类是ThreadPoolExecutor,有7大参数


阿里巴巴开发手册

ThreadPoolExecutor
自定义创建线程池,参数类型就是7大参数,分别是 核心线程池大小、最大的线程池大小、超时了没有人调用就会释放、超时单位、阻塞队列、线程工厂 创建线程的 一般不用动、拒绝策略
public static void main(String[] args) {
// 注意: ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() //拒绝策略 ); try {
for (int i=1; i<=80; i++){
//使用线程池之后创建线程 threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+ " ok"); }); } } finally {
threadPoolExecutor.shutdown(); } }
等待队列已经满了,塞不下新任务,同时,线程池中max线程也达到了,无法继续新任务。这个时候就需要拒绝策略机制
拒绝策略有4种:
AbortPolicy:如果阻塞队列满了,直接抛出异常阻止系统正常运行,队列容量大小 + maxPoolSize CallerRunsPolicy:如果阻塞队列满了,该策略不会抛弃任务,也不抛出异常,而是将任务回退给调用者 DiscardPolicy:如果阻塞队列满了,丢弃无法处理的任务,不抛出异常,如果允许任务丢失,是最好的策略 DiscardOldestPolicy:如果阻塞队列满了,抛弃队列中等待最久的任务,把当前任务加入队列中再次提交
如何去设置线程池的最大大小如何去设置?
CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小Runtime.getRuntime().availableProcessors() // 获取CPU核数 I/O密集型:在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。
Java内置核心四大函数式接口

我的学习论坛
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/208553.html原文链接:https://javaforall.net
