java并发编程学习一


什么是进程和线程?

进程是操作系统进行资源分配的最小单位
进程跟进程之间的资源是隔离的,同一个进程之中的线程可以共享进程的资源。
线程是进程的一个实体,是CPU 调度和分派的基本单位,,依赖于进程存在。
线程无处不在:

任何一个程序都必须要创建线程,特别是Java 不管任何程序都必须启动一个
main 函数的主线程; Java Web 开发里面的定时任务、定时器、JSP 和Servlet、异
步消息处理机制,远程访问接口RM 等,任何一个监听事件, onclick 的触发事件等都
离不开线程和并发的知识。


多核心:也指单芯片多处理器( Chip Multiprocessors,简称CMP),CMP 是由美国斯坦福大学提出的,其思想是将大规模并行处理器中的SMP(对称多处理器)集成到同一芯片内,各个处理器并行执行不同的进程。这种依靠多个CPU 同时并行地运行程序是实现超高速计算的一个重要方向,称为并行处理多线程: Simultaneous Multithreading.简称SMT.让同一个处理器上的多个线程同步执行并共享处理器的执行资源。

核心数、线程数:目前主流CPU 都是多核的。增加核心数目就是为了增加线程数,因为操作系统是通过线程来执行任务的,一般情况下它们是1:1 对应关系,也就是说四核CPU 一般拥有四个线程。但Intel 引入超线程技术后,使核心数与线程数形成1:2 的关系



我们平时在开发的时候,感觉并没有受cpu 核心数的限制,想启动线程就启动线程,哪怕是在单核CPU 上,为什么?这是因为操作系统提供了一种CPU 时间片轮转机制。时间片轮转调度是一种最古老、最简单、最公平且使用最广的算法,又称RR调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
百度百科对CPU 时间片轮转机制原理解释如下:
如果在时间片结束时进程还在运行,则CPU 将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU 当即进行切换。调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾,时间片轮转调度中唯一有趣的一点是时间片的长度。从一个进程切换到另一个进程是需要定时间的,包括保存和装入寄存器值及内存映像,更新各种表格和队列等。假如进程切( processwitch),有时称为上下文切换( context switch),需要5ms,再假设时间片设为20ms,则在做完20ms 有用的工作之后,CPU 将花费5ms 来进行进程切换。CPU 时间的20%被浪费在了管理开销上了。为了提高CPU 效率,我们可以将时间片设为5000ms。这时浪费的时间只有0.1%。但考虑到在一个分时系统中,如果有10 个交互用户几乎同时按下回车键,将发生什么情况?假设所有其他进程用足它们的时间片的话,最后一个不幸的进程不得不等待5s 才获得运行机会。多数用户无法忍受一条简短命令要5 才能
做出响应,同样的问题在一台支持多道程序的个人计算机上也会发结论可以归结如下:时间片设得太短会导致过多的进程切换,降低了CPU 效率:而设得太长又可能引起对短的交互请求的响应变差。将时间片设为100ms 通常是一个比较合理的折衷。


启动线程的方式有:
1、X extends Thread;,然后X.start
2、X implements Runnable;然后交给Thread 运行
区别:
Thread 才是Java 里对线程的唯一抽象,Runnable 只是对任务(业务逻辑)
的抽象。Thread 可以接受任意一个Runnable 的实例并执行。

中止
stop()还是interrupt() ? 判断方法: isInterrupted()和static方法interrupted()
suspend()、resume()和stop(),暂定、恢复、停止等方法不建议使用,因为这些方法都不会释放资源。
建议使用interrupt() 修改中断标示位(boolean值),当前获取到CPU运行权限的线程通过isInterrupted()或者interrupted()来判断,之后自己决定是否释放CPU权限。

注意:处于死锁状态的线程无法被中断


run()和start()
Thread 类是Java 里对线程概念的抽象,可以这样理解:我们通过new Thread()
其实只是new 出一个Thread 的实例,还没有操作系统中真正的线程挂起钩来。
只有执行了start()方法后,才实现了真正意义上的启动线程。

start()方法让一个线程进入就绪队列等待分配cpu,分到cpu 后才调用实现
的run()方法,start()方法不能重复调用,如果重复调用会抛出异常。
而run 方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方
法并没有任何区别,可以重复执行,也可以被单独调用。

yield()方法:使当前线程让出CPU 占有权,但让出的时间是不可设定的,
不会释放锁资源。注意:并不是每个线程都需要这个锁的,而且执行yield( )的线
程不一定就会持有锁,我们完全可以在释放锁后再调用yield 方法。
所有执行yield()的线程有可能在进入到就绪状态后会被操作系统再次选中
马上又被执行。

join()方法:把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。
比如在线程B 中调用了线程A 的Join()方法,直到线程A 执行完毕后,才会继续
执行线程B。

线程的优先级:在Java 线程中,通过一个整型成员变量priority 来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认
优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。
设置线程优先级时,针对频繁阻塞(休眠或者I/O 操作)的线程需要设置较
高优先级,而偏重计算(需要较多CPU 时间或者偏运算)的线程则设置较低的
优先级,确保处理器不会被独占。在不同的JVM 以及操作系统上,线程规划会
存在差异,有些操作系统甚至会忽略对线程优先级的设定。

Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调
度以及支持性工作。这意味着,当一个Java 虚拟机中不存在非Daemon 线程的
时候,Java 虚拟机将会退出。可以通过调用Thread.setDaemon(true)将线程设置
为Daemon 线程。我们一般用不上,比如垃圾回收线程就是Daemon 线程。
sleep(不会释放锁),它是线程的方法。
wait()释放锁、 notify()、notifyAll() ,Object的方法,代表着必须是在同步代码中才能执行,只有拿到锁的对象才能调用当前的方法,不然会抛异常。
wait()被唤醒之后会顺着上次执行到的地方继续执行。

随便找的一个模拟死锁的代码:

public class DeadLock implements Runnable{private static Object obj1 = new Object();private static Object obj2 = new Object();private boolean flag;public DeadLock(boolean flag){this.flag = flag;}@Overridepublic void run(){System.out.println(Thread.currentThread().getName() + "运行");if(flag){synchronized(obj1){System.out.println(Thread.currentThread().getName() + "已经锁住obj1");try {  Thread.sleep(1000);  } catch (InterruptedException e) {  e.printStackTrace();  }  synchronized(obj2){// 执行不到这里System.out.println("1秒钟后,"+Thread.currentThread().getName()+ "锁住obj2");}}}else{synchronized(obj2){System.out.println(Thread.currentThread().getName() + "已经锁住obj2");try {  Thread.sleep(1000);  } catch (InterruptedException e) {  e.printStackTrace();  }  synchronized(obj1){// 执行不到这里System.out.println("1秒钟后,"+Thread.currentThread().getName()+ "锁住obj1");}}}}}

控制flag的true或者false开启多线程,导致两个线程在synchronized的状态下条件都不满足,导致死锁。

Java中线程的状态:
1、初始:创建了线程,但是还没有调用start方法。
2、运行:这里其实分为两种情况,一种就绪等待CPU,一种已经抢到CPU执行中。
3. 阻塞:线程被锁所阻塞。
4. 等待:wait,不满足对应的执行条件而等待,这里其实跟io阻塞很类似,io阻塞需要等待其他硬件执行返回。wait是自己设置了条件,可能被其他线程唤醒之后还是不满足条件,自己又进入wait,如果满足条件则进入就绪状态。
5. 超时等待:自己设置了等待时间,时间到了就可以进入就绪状态强CPU执行权。
6. 终止:线程执行完了任务。
引用一下别人画的图:状态之间的变迁如下图所示


synchronized 内置锁
synchronized 可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线
程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量
访问的可见性和排他性,又称为内置锁机制。

Volatile 最轻量的同步机制
Valatile修饰的变量当发生改变时能够被其他线程感知,适合场景:一个线程写,多个线程读。

yield() 、sleep()被调用后,都不会释放当前线程所持有的锁。
调用wait()方法后,会释放当前线程持有的锁,而且当前被唤醒后,会重新去竞争锁,锁竞争到后才会执行wait 方法后面的代码。
调用notify()系列方法后,对锁无影响,线程只有在syn 同步代码执行完后才
会自然而然的释放锁,所以notify()系列方法一般都是syn 同步代码的最后一行。
Synchronized和vaolatile后面会详细讲解。

线程的状态切换:


ThreadLocal
为每一个线程提供了一个变量副本,相当于每一个线程都复制了一份在本地,起到了线程的隔离,使用的都是本地变量。
使用场景Spring里面实现事物的时候实现了ThreadLocal

ThreadLocal 类接口很简单,只有4 个方法,我们先来了解一下:
• void set(Object value)
设置当前线程的线程局部变量的值。
• public Object get()
该方法返回当前线程所对应的线程局部变量。
• public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK
5.0 新增的方法。需要指出的是,当线程结束后,该线程的局部变量将自动
被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
• protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected 的方法,显然是为
了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1 次调用get()
或set(Object)时才执行,并且仅执行1 次。ThreadLocal 中的缺省实现直接返回一
个null。

public final static ThreadLocal<String> RESOURCE = new ThreadLocal<String>();线程内部的ThreadLocal,而ThreadLocal内部维护一个static class ThreadLocalMap {}ThreadLocalMap 内部维护的Entry static class Entry extends java.lang.ref.WeakReference<java.lang.ThreadLocal<?>> {java.lang.Object value;Entry(java.lang.ThreadLocal<?> threadLocal, java.lang.Object o) { /* compiled code */ }}

静态的,直接类名.方法调用,set、get、remove是主要的方法
看到entry我们就知道是键值对的形式存储。
value是我们的数据源,key是以当前线程的ThreadLocal作为key,它不是静态的,而且是虚引用创建,所以在高并发的情况下如果我们对ThreadLocal的数据使用完之后不调用remove会造成内存泄漏

每一个线程Thread里面都有一个内部类ThreadLocal,是线程的成员变量之一,而每一个ThreadLocal 内部都有一个静态类ThreadLocalMap,而ThreadLocalMap 内部又维护了一个键值对的Entry。

这个数据结构一点都不复杂,我们关注的是有可能的内存泄漏,所以我们要知道这个Entry的创建情况。

代码非常简单,我们可以看到这个ThreadLocal是虚引用创建的,所以它容易被回收,但是里面的静态ThreadLocalMap肯定是不会被回收的,Entry变量最多置空为原始状态。

ThreadLocalMap.Entry e = map.getEntry(this);

这个代码是ThreadLocal获取掉用get方法里面,如果ThreadLocal被回收了,那么这个this本身作为key就不存在了。

图中的虚线表示弱引用。
这样,当把threadlocal 变量置为null 以后,没有任何强引用指向threadlocal
实例,所以threadlocal 将会被gc 回收。这样一来,ThreadLocalMap 中就会出现
key 为null 的Entry,就没有办法访问这些key 为null 的Entry 的value,如果当前
线程再迟迟不结束的话,这些key 为null 的Entry 的value 就会一直存在一条强
引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而这块value 永
远不会被访问到了,所以存在着内存泄露。
只有当前thread 结束以后,current thread 就不会存在栈中,强引用断开,
Current Thread、Map value 将全部被GC 回收。最好的做法是不在需要使用
ThreadLocal 变量后,都调用它的remove()方法,清除数据。


工作密取
即当前线程的Task 已经全被执行完毕,则自动取到其他线程的Task 池中取出Task 继续执行。
ForkJoinPool 中维护着多个线程(一般为CPU 核数)在不断地执行Task,每个线程除了执行自己职务内的Task 之外,还会根据自己工作线程的闲置情况去获取其他繁忙的工作线程的Task,如此一来就能能够减少线程阻塞或是闲置的时间,提高CPU 利用率。
我们要使用ForkJoin 框架,必须首先创建一个ForkJoin 任务。它提供在任务中执行fork 和join 的操作机制,通常我们不直接继承ForkjoinTask 类,只需要直接继承其子类。

  1. RecursiveAction,用于没有返回结果的任务
  2. RecursiveTask,用于有返回值的任务
    task 要通过ForkJoinPool 来执行,使用submit 或invoke 提交,两者的区
    别是:invoke 是同步执行,调用之后需要等待任务完成,才能执行后面的代码;
    submit 是异步执行。
    join()和get 方法当任务完成的时候返回计算结果。
    在我们自己实现的compute 方法里,首先需要判断任务是否足够小,如果足够小就直接执行任务。如果不足够小,就必须分割成两个子任务,每个子任务在调用invokeAll 方法时,又会进入compute 方法,看看当前子任务是否需要继续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。使用join方法会等待子任务执行完并得到其结果。

ForkJoin的使用:它是一个抽象类,我们一般实现它的子类,重写compute方法,然后将我们的业务核心写在该方法,可以设置一个阈值,当达不到这个变量的时候继续拆分,通过join方法加入到线程池中,可以理解为递归调用。当一个大的任务,可以拆分成无数个小任务,且内容核心不变的时候可以使用,当业务数量越大的时候优势越明显,例如统计SQL或者excel的数据量,百万千万数量级别的时候比较明显。


使用:
Static CountDownLatch latch = new CountDownLatch(int);在哪里创建,哪个线程需要等待就在那个线程创建,满足了条件该线程就可以继续执行。

latch.await(), 等待中。可以设置最多等待时间
例如:

if(latch != null){try {latch.await(20, TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}}

latch.countdown(); //可以理解为减1,当int = 0 时候等待的线程才会继续往下执行。
创建latch,根据业务需求传入具体需要设置int数量。
需要等待的线程拿到latch 对象设置await等待。
其他线程根据业务需求当运行到业务节点的时候调用latch.countdown(),相当于int减1,当到了0的时候则等待的线程会被唤醒继续执行,或者到了最大的等待时间也会被唤醒。

注意,该计数器无法重复使用。

明白前面的CountDownLatch,当前这个CyclicBarrier就很容易理解了。
使用:
Private static CyclicBarrier barrier = new CyclicBarrier(int);
在需要等待的线程中使用barrier.await();
有几个需要等待的线程上面的int就设置几,当所有的线程都执行到barrier.await();这个代码的时候,所有等待的线程开始执行,在这之前先执行到这里的线程都必须等待。

Private static CyclicBarrier barrier = new CyclicBarrier(int,RunnablebarrierAction);
它有两个构造函数,还可以再接收一个线程,意思是当执行到当前设置的屏障barrier.await();的时候,Thread这个线程可以汇总之前等待的几个线程执行的结果,让当前这个Thread先执行完之后其他等待的线程再执行。

barrier.await();是可以反复调用,当触发当前屏障的时候就会执行一次Thread。

只支持两个线程进行Exchange,当两个线程在处理同一类数据的时候,A类型执行之后调用Exchange,B线程执行之后也调用Exchange,将需要交换的数据丢入exchange(xxx);方法中,JDK保证数据同步安全,使用场景比较狭窄。


我们都知道runable实现的接口run方法并没有返回值,当我们需要线程执行之后返回对象的时候怎么办呢? 这个时候Callable就出现了,它跟runable基本非常类似,实现它,它里面有一个call()方法,这个实现类运行你传入一个泛型,你传入的泛型是什么类型,它最后就返回什么类型的数据。
Thread里面没有任何一个构造函数能接受一个Callable,所以FutureTask就出现了。

public class FutureTask <V> implements java.util.concurrent.RunnableFuture<V> {}public interface RunnableFuture <V> extends java.lang.Runnable, java.util.concurrent.Future<V> {void run();
}

它实现了Runanble,所以它也可以当做一个runnable扔到Thread里面去执行,同时它又实现了Future,Future可以看做是定义了一系列辅助Callable的方法的接口。
一个FutureTask 对象可以对
调用了Callable 和Runnable 的对象进行包装,由于FutureTask 也是调用了
Runnable 接口所以它可以提交给Executor 来执行。

例如OKhttp就是。

Future里面定义什么方法来辅助呢?

public interface Future <V> {boolean cancel(boolean b);boolean isCancelled();boolean isDone();V get() throws java.lang.InterruptedException, java.util.concurrent.ExecutionException;V get(long l, java.util.concurrent.TimeUnit timeUnit) throws java.lang.InterruptedException, java.util.concurrent.ExecutionException, java.util.concurrent.TimeoutException;
}

从方法名我们就知道什么意思了,这里就不详细讲解了。
Future 就是对于具体的Runnable 或者Callable 任务的执行结果进行取消、查
询是否完成、获取结果。必要时可以通过get 方法获取执行结果,该方法会阻塞
直到任务返回结果。

我们在前面看到线程的中止方法并没有cancel这种,它的核心其实也是调用interrupt(),所以能不能成功中止线程还是要看具体的操作者有没有添加对应的判断,愿意停下来不。

本篇算是一个科普,讲的都是基础,很多都是概念,但是还是有必要了解的东西。

java并发编程学习二

java并发编程学习一相关推荐

  1. java并行任务,Java 并发编程学习(五):批量并行执行任务的两种方式

    Java 并发编程学习(五):批量并行执行任务的两种方式 背景介绍 有时候我们需要执行一批相似的任务,并且要求这些任务能够并行执行.通常,我们的需求会分为两种情况: 并行执行一批任务,等待耗时最长的任 ...

  2. 简明高效的 Java 并发编程学习指南

    你好,我是宝令,<Java 并发编程实战>专栏作者,很高兴你能看到这篇内容. 对于一个Java程序员而言,能否熟练掌握并发编程是判断他优秀与否的重要标准之一.因为并发编程是Java语言中最 ...

  3. Java并发编程学习 + 原理分析(建议收藏)

    总结不易,如果对你有帮助,请点赞关注支持一下 微信搜索程序dunk,关注公众号,获取博客源码 Doug Lea是一个无私的人,他深知分享知识和分享苹果是不一样的,苹果会越分越少,而自己的知识并不会因为 ...

  4. 海子Java并发编程学习总结

    2019独角兽企业重金招聘Python工程师标准>>> Java并发编程:进程和线程之由来 Java并发编程:如何创建线程? Java并发编程:Thread类的使用 Java并发编程 ...

  5. Java并发编程学习记录

    Java并发编程汇总 并发问题的分解 多线程并发的特性 volatile 在并发编程中可能出现的问题: 管程 wait() 的正确姿势 notify() 何时可以使用 在使用多线程编程的时候,开启多少 ...

  6. 【并发入门】Java 并发编程学习笔记

    注:该笔记主要记录自 B站 up主 遇见狂神说的个人空间_哔哩哔哩_bilibili 1.什么是 JUC Java 工具类中的 并发编程包 学习:源码 + 官方文档 业务:普通的线程代码 Thread ...

  7. java并发编程学习juc工具类之Executors

    文章目录 Executors 重要方法 1.newCachedThreadPool 2.newFixedThreadPool 3.newScheduledThreadPool 示例代码 4.newSi ...

  8. java 并发编程学习之二 ---- lock

    在Java中有两种方法实现锁机制,一种是在前一篇博客中([java7并发编程实战]-–线程同步机制:synchronized)介绍的synchronized,而另一种是比synchronized更加强 ...

  9. JAVA并发编程学习笔记之CAS操作

    http://blog.csdn.net/aesop_wubo/article/details/7537960 CAS操作 CAS是单词compare and set的缩写,意思是指在set之前先比较 ...

最新文章

  1. Linux headtail命令
  2. 二进制在计算机电路中得到广泛的应用,模拟电子和数字电子技术的区别及应用...
  3. mysql 结构体_mysql模块使用结构体生成数据库表,不识别long类型
  4. uniapp 仿钉钉考勤统计页面的日历组件,通过日历展示每日考勤打卡情况,支持在日历上打两种不同类型的点,大致适配各种分辨率
  5. PHP导航猫网址导航系统源码V2.4.5
  6. linux多目录多域名,一个空间放多个网站(多域名绑定到同一空间不同目录)
  7. 行存储索引改换成列存储索引_索引策略–第2部分–内存优化表和列存储索引
  8. 阿里HSF(服务框架)
  9. java生成短网址_http 长网址、短网址(短网址生成API)
  10. 微信公众帐号开发教程第8篇-QQ表情的发送与接收
  11. hotmail手机端_Hotmail邮箱客户端下载-Hotmail手机版下载 苹果版v2.48.0-PC6苹果网
  12. 【DOORS】如何基于DOORS实施需求管理
  13. 不会吧,不会吧,全网最细汉诺塔讲解,不会有人不知道吧。面试官直呼内行,看完只想默默找水喝(C语言)
  14. 三相全控tc787触发电路_何为可控硅整流器三相桥和双反星整流电路?
  15. STM32F103+SDIO wifi Marvell8801/Marvell88w8801 介绍(九) ---- Marvell 8801/Marvell 88w8801 实现AP功能/实现热点功能
  16. Linux常用基础指令
  17. [洛谷P4233]射命丸文的笔记
  18. 微信小程序使用promise 一次封装全部接口
  19. 用photoshop制作海报原来如此简单,快来看看吧
  20. 求 objectdock 的 tray系统托盘插件

热门文章

  1. 坑爹的vcruntime140d.dll
  2. Python int()的有趣用法
  3. 基于协同过滤算法的书籍推荐 毕业设计-附源码101555
  4. android 炫酷进度条,Android打造炫酷进度条效果
  5. 实在腾不出手来还好可以刷脸支付
  6. Opencv 环境配置
  7. 回顾 | 九载同行,云和恩墨重磅支持数据技术嘉年华
  8. 腾讯安全SOC+荣获“鑫智奖”,助力金融业数智化转型
  9. js倒计时----距离活动结束还有 xx天xx时xx分xx秒
  10. ‘.sync‘ modifier on ‘v-bind‘ directive is deprecated. Use ‘v-modelpropName‘ instead.