Java并行程序基础

  • 基本概念
    • 并行计算的相关概念
      • 同步(Synchronous)和异步(Asynchronous)
      • 并发(Concurrent)和并行(Parallelism)
      • 临界区
      • 阻塞(Blocking)和非阻塞(Non-Blocking)
      • 死锁(Deadlock)、饥饿(Starvation)、活锁(Livelock)
    • 并发级别
      • 阻塞(Blocking)
      • 无饥饿(Starvation-Free)
      • 无障碍(Obstruction-Free)
      • 无锁(Lock-Free)
      • 无等待(Wait-Free)
    • 关于并行的两个重要定律
      • Amdahl定律
      • Gustafson定律
    • JMM(Java内存模型)
      • 原子性(Atomicity)
      • 可见性(Visibility)
      • 有序性(Ordering)
        • 指令重排的例子
      • Happen-Before原则
  • Java并行程序基础
    • 线程的生命周期
    • 线程的基本操作
      • 新建
      • 终止
      • 中断
      • 等待(wait)和通知(notify)
      • 挂起(suspend)和继续执行(resume)
      • 等待线程结束(join)和谦让(yield)
        • join
        • yield
      • volatile
    • 线程组
      • 守护线程
    • 线程优先级
    • 线程安全与synchronized
  • Reference

基本概念

Linux Torvalds认为,并行计算只能在图像处理和服务端编程两个领域使用。

并行计算的相关概念

同步(Synchronous)和异步(Asynchronous)

同步和异步通常用来形容一次方法的调用,同步方法一旦调用,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法更像一个消息传递,一旦开始,方法调用就会立即返回,调用者可以继续后续的操作。

并发(Concurrent)和并行(Parallelism)

并发和并行都可以表示多个任务一起执行,但侧重点不同。并发侧重于多个任务交替执行,而多个任务之间有可能还是串行的。并行是真正意义上的“同时进行”,出现在拥有多个CPU的系统(例如多核CPU)。

临界区

用来表示一种公共资源或者说共享数据,可以被多个线程使用。但是每一次只能有一个线程使用他,一旦临界资源被占用。其他线程想使用这个资源就必须等待。

阻塞(Blocking)和非阻塞(Non-Blocking)

阻塞和非阻塞通常用来形容多线程间的相互影响。阻塞,一个线程占用了临界区资源,其他线程必须在临界区中等待。非阻塞,强调没有一个线程可以妨碍其他线程的执行。

死锁(Deadlock)、饥饿(Starvation)、活锁(Livelock)

死锁、饥饿和活锁都属于线程的活跃性问题。
死锁:两个或两个以上的线程在执行过程中,由于竞争资源或彼此通信而造成的一种阻塞现象,若无外力作用,他们都无法推进下去。
饥饿:指一个线程尽管能继续执行,但被调度器无限期地忽视,而不能被调用的情况。
活锁:任务执行没有被阻塞,由于某些条件没有满足,导致一直重复着尝试-失败-尝试-失败的过程。例如行人迎面来回“谦让”。

并发级别

阻塞的:阻塞、无饥饿。
非阻塞的:无障碍,无锁、无等待。

阻塞(Blocking)

如synchronized或重入锁,会产生阻塞的线程。

无饥饿(Starvation-Free)

如果线程之间是由优先级的,低优先级的线程可能会产生饥饿。也就是说对于同一个资源的调配是不公平的。下图表示了公平锁和非公平锁的两种情况。

无障碍(Obstruction-Free)

不加锁,都进入临界区,若数据不一致则进行回滚。

可使用“一致性标记”,操作之前与操作之后分别读取,若不一致则回滚。

无锁(Lock-Free)

无障碍的基础上,保证有一个线程能在有限步内完成任务离开临界区。通常包含了一个无穷循环。

while(!atomicVar.compareAndSet(LocalVar, localVar + 1)){localVar = atomicVar.get();
}

无等待(Wait-Free)

要求所有的线程必须在有限步之内完成。

例如:RCU(Read-Copy
Update,读-复制更新)。读,无限制。写,先复制成副本,在副本上做修改,最后选择时机进行替换。关键在于怎么判断所有读者已经完成访问。

关于并行的两个重要定律

Amdahl定律

定义了串行系统并行化之后的加速比的计算公式和理论上限。

加速比=优化前的系统耗时优化后的系统耗时\ 加速比 = \frac{优化前的系统耗时}{优化后的系统耗时} 加速比=优化后的系统耗时优化前的系统耗时​

Tn=T1(F+1n(1−F))\ T_{n} = T_{1}(F + \frac{1}{n}(1 - F)) Tn​=T1​(F+n1​(1−F))

加速比=T1Tn=T1T1(F+1n(1−F))=1F+1n(1−F)\ 加速比=\frac{T_{1}}{T_{n}}=\frac{T_{1}}{T_{1}(F + \frac{1}{n}(1 - F))}=\frac{1}{F + \frac{1}{n}(1 - F)} 加速比=Tn​T1​​=T1​(F+n1​(1−F))T1​​=F+n1​(1−F)1​
其中,n表示处理器个数,F表示串行比例,1-F表示并行比例。优化后的效果取决于cpu的数量和串行化比例。

Gustafson定律

执行时间:a + b(a:串行时间,b:并行时间)
总执行时间:a + nb(n:处理器个数)
加速比=a+nba+b\ 加速比=\frac{a+nb}{a+b} 加速比=a+ba+nb​
定义:F=aa+b\ F=\frac{a}{a+b} F=a+ba​为串行比例
加速比=a+nba+b=aa+b+nba+b=F+n(a+b−aa+b)=F+n(1−F)=n−F(n−1)\ 加速比=\frac{a+nb}{a+b}=\frac{a}{a+b}+\frac{nb}{a+b}=F+n(\frac{a+b-a}{a+b})=F+n(1-F)=n-F(n-1) 加速比=a+ba+nb​=a+ba​+a+bnb​=F+n(a+ba+b−a​)=F+n(1−F)=n−F(n−1)

JMM(Java内存模型)

JMM的关键技术点都是围绕着多线程的原子性可见性有序性来建立的。

原子性(Atomicity)

原子性指一个操作时不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
例如两个线程对int i进行赋值,线程A对它赋值为1,线程B对它赋值为-1。那么i的值只可能是1或者-1。但如果是32位虚拟机对long类型做赋值,则不是一个原子性操作。

可见性(Visibility)

可见性指的是当前一个线程修改了某一个共享变量的值时,其他线程能否立刻知道这个修改。缓存优化硬件优化指令重排编译器优化都可能导致可见性问题。

有序性(Ordering)

指令重排使指令顺序与原指令顺序不一致,目的是减少流水线的停顿,提高性能。

例子

class OrderExample{int a = 0;boolean flag = false;public void write(){a = 1;flag = true;}public void reader(){if (flag){int i = a + 1;......}}
}

一条指令可以简单分为以下步骤:

  1. 取指IF。
  2. 译码和取寄存器操作数ID。
  3. 执行或者有效地址计算EX。
  4. 存储器访问MEM。
  5. 写回WB。

指令重排的例子


处理 a=b+c;d=e+f;


可以进行指令重排

指令重排之后

Happen-Before原则

哪些指令不能重排:

  1. 程序顺序原则:一个线程内保证语义的串行性。
  2. volatile规则:volatile变量的写先于读发生,保证了volatile的可见性。
  3. 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前。
  4. 传递性:A先于B,B先于C,则A必然先于C。
  5. 线程的start()方法先于每一个操作。
  6. 线程的所有操作先于线程的终结(Thread.join())。
  7. 线程的中断(interrupt())先于被中断线程的代码。
  8. 对象的构造函数的执行,先于finalize()方法

Java并行程序基础

线程的生命周期

线程是轻量级的进程,是程序执行的最小单位,进程是线程的容器。

线程的基本操作

新建

Thread t1 = new Thread();
t1.start(); // 调用Thread.run()

注意:不要使用 t1.run() 启动线程,只会在当前线程中串行执行。

  1. 重写run() 方法
Thread t1 = new Thread(){@Overridepublic void run(){...}
};
t1.start();
  1. 可通过extends Thread,再重写run() 方法。但Java是单继承,继承本身也是一种宝贵的资源。
  2. 实现Runnable接口
public interface Runnable{public abstract void run();
}   
// Thread中的构造方法
public Thread(Runnable target)
public void run(){if (target != null){target.run();}
}       
public class CreateThread implements Runnable{public static void main(String[] args){Thread t1 = new Thread(new CreateThread());t1.start();}@Overridepublic void run(){逻辑代码...}
}           

终止

一般情况下线程执行完后会自行结束,但服务端线程的执行本体可能就是个大循环while(true)。
Thread.stop()方法会直接终止线程,立即释放线程所持有的锁,可能会导致数据不一致的问题。
例如:修改User中的ID和Name。

用户1 ID=1 Name=小明
用户2 ID=2 Name=小王


正确停止的做法:

public class ChangeObjectThread implements Runnable{volatile boolean stopme = false;public void stopMe(){stopme = true;}@Overridepublic void run(){while(true){if(stopme){break; // 自行选择合适的时机退出}synchronized(锁对象){逻辑代码...}Thread.yield();}}
}   

中断

在Java中,停止一个线程的主要方法就是中断。线程中断并不会导致线程立即退出,而是给线程发送一个通知,告知目标线程。接到通知后如何处理,完全由目标线程决定。

public void Thread.interrupt() // 中断,设置中断标志位
public boolean Thread.isInterrupted() // 判断是否中断
public static boolean Thread.interrupted() // 判断是否中断,并清除中断标志位
Thread t1 = new Thread(){@Overridepublic void run(){while(true){if(Thread.currentThread.isInterrupted()){ // 读取标志位break;}Thread.yield();}}
};
t1.start();
Thread.sleep(2000);
t1.interrupt(); // 中断,修改标志位              

上述代码中,与stopme() 标记的手法非常相似,但中断的功能更强。例如循环体中出现了wait() 方法或者sleep() 方法这样的操作。

Thread t1 = new Thread(){@Overridepublic void run(){while(true){if(Thread.currentThread().isInterrupted()){System.out.println("Interrupted!");break;}try{/*Thread.sleep() 方法会让当前线程休眠若干时间,他会抛出一个InterruptedException中断异常。当线程在sleep时,若被中断,这个异常就会产生。此时,他会清除中断标志位。*/Thread.sleep(2000);}catch(InterruptedException e){System.out.println("Interrupted when sleep");// 设置中断标志位Theread.currentThread().interrupt();}Thread.yield();}}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();     

等待(wait)和通知(notify)

等待和通知是为了支持多线程之间的协作。
wait() 和notify() 方法不在Thread中,而是在Object中,这意味着任何对象都可以调用这个方法。

public final void wait() throws InterruptedException
public final native void notify() 

工作过程:当一个object调用wait() 方法后,当前线程会进入这个object对象的等待队列。这个等待队列中,可能会有多个线程,因为系统运行多个线程同时等待某一个对象。当object调用notify() 方法后,会从这个等待队列上随机挑选一个线程唤醒。


注意:wait() 方法不能随便调用,无论是wait() 还是notify() 都需要首先获得目标对象的一个监视器。

public class SimpleWN{final static Object object = new Object();public static class T1 extends Thread{public void run(){synchronized(object){T1 Start!try{T1 Wait For Object...object.wait();}catch(InterruptedException e){e.printStackTrace();}T1 End!}}}public static class T2 extends Thread{public void run(){T2 Start! Notify One Threadobject.notify();T2 End!try{Thread.sleep(2000);}catch(InterruptedException e){e.printStackTrace();}}}public static void main(String[] args){Thread t1 = new T1();Thread t2 = new T2();t1.start();t2.start();     }
}   

注意:Object.wait() 方法和Thread.sleep() 方法都会让线程等待若干时间。除wait() 方法可以被唤醒之外,另一个主要的区别就是wait() 方法会释放对象的锁,而Thread.sleep() 方法不会释放任何资源。

挂起(suspend)和继续执行(resume)

这两个方法已经废弃,原因是suspend() 方法在暂停线程的同时,不会释放任何资源。


坏例子:

public class BadSuspend{public static Object u = new Object(); static ChangeObjectThread tl = new ChangeobjectThread("T1");static ChangeObjectThread t2 = new ChangeObjectThread("T2");public static class ChangeObjectThread extends Thread{public ChangeObjectThread(String natne){super.setName(name);}@Override public void run() {  synchronized (u) {System.out.println( ” in ” +getName()); Thread.currentThread().suspend(); }}}public static void main(String[] args) throws InterruptedException {tl.start(); Thread.sleep(lOO); t2.start(); tl.resume(); t2.resume(); tl.join(); t2.join();}
}

利用wait() 和notify() 方法改造

public class GoodSuspend{public static Object u = new Object();public static class ChangeObjectThread extends Thread {volatile boolean susperidme = false;public void suspendMe() {suspendme = true;}public void resumeMe() {suspendme=false; synchronized (this} {notify(};}}@Override public void run() { while (true) {synchronized (this) {while (suspendme){try{wait();}catch(InterruptedException e){e.printStackTrace();}}synchronized(u){System.out.println("in ChangeObjectThread");}Thread.yield();}}}public static class ReadObjectThread extends Thread{@Overridepublic void run(){while(true){synchronized(u){System.out.println("in ReadObjectThread");}Thread.yield();}}}public static void main(String[] args) throws InterruptedException{ChangeObjectThread t1 = new ChangeObjectThread();ReadObjectThread t2 = new ReadObjectThread();t1.start();t2.start();Thread.sleep(1000);t1.suspemdMe();System.out.println("suspend t1 2 sec");Thread.sleep(2000);System.out.println("resume t1");t1.resumeMe();}}
}                                               

等待线程结束(join)和谦让(yield)

join

public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
public class JoinMain{public volatile static int i = 0;public static class AddThread implements Runnable{@Overridepublic void run(){for(int i = 0;i < 10000000;i++);}}public static void main(String[] args)throws InterruptedException{AddThread at = new AddThread();at.start();at.join();System.out,println(i);  }
}

join() 方法的本质是让调用线程wait() 方法在当前线程对象实力上

while(isAlive()){wait(0);
}   

不要在应用程序中,在Thread对象实例上使用类似wait() 方法或者notify() 方法,可能会影响系统API的工作。

yield

public static native void yield()

当前线程让出CPU

volatile

释义:易变的、不稳定的
提醒虚拟机这个变量可能被某些线程改动。确保本条指令不会因为编译器的优化而省略。
特性:

  1. 保证可见性。
  2. 禁止指令重排。
  3. 只能保证单次读/写的原子性。不能保证i++这种操作的原子性。

线程组

public class ThreadGroupName implements Runnable {public static void main(String[] args) {ThreadGroup tg = new ThreadGroup("PrintGroup");Thread t1 = new Thread(tg, new ThreadGroupName(), "T1");Thread t2 = new Thread(tg, new ThreadGroupName(), "T2");t1.start();t2.start();System.out.println(tg.activeCount());tg.list();}@Overridepublic void run() {String groupAndName = Thread.currentThread().getThreadGroup().getName() + "-" + Thread.currentThread().getName();while (true){System.out.println("I am "+groupAndName);try{Thread.sleep(3000);}catch (InterruptedException e){e.printStackTrace();}}}
}

守护线程

public class DaemonDemo {public static class DaemonT extends Thread{@Overridepublic void run() {while (true){System.out.println("I am alive");try{Thread.sleep(1000);}catch (InterruptedException e){e.printStackTrace();}}}}public static void main(String[] args) throws InterruptedException{Thread t = new DaemonT();t.setDaemon(true);t.start();Thread.sleep(2000);}
}

线程优先级

public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;Thread high = new HighPriority();
Thread low = new LowPriority();
high.setPriority(Thread.MAX_PRIORITY);
low.setPriority(Thread.MIN_PRIORITY);
low.start();
high.start();

线程安全与synchronized

synchronized可完全替换volatile,开销大于volatile,用法如下:

  1. 指定加锁对象:对给定对象加锁,进入同步代码前需要获得给定对象的锁。
public class AccountingSync implements Runnable {static AccountingSync instance = new AccountingSync();static int i = 0;@Overridepublic void run() {for (int j = 0; j < 10000000; j++) {synchronized (instance) {i++;}}}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();t1.join();t2.join();System.out.println(i);}
}
  1. 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前需要获得当前实例的锁。
public class AccountingSync2 implements Runnable {static AccountingSync2 instance = new AccountingSync2();static int i = 0;public synchronized void increase(){i++;}@Overridepublic void run() {for (int j = 0; j < 10000000; j++) {increase();}}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();t1.join();t2.join();System.out.println(i);}
}

错误的写法:

public class AccountingSync implements Runnable {static AccountingSync instance = new AccountingSync();static int i = 0;public synchronized void increase(){i++;}@Overridepublic void run() {for (int j = 0; j < 10000000; j++) {increase();}}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(new AccountingSync());Thread t2 = new Thread(new AccountingSync());t1.start();t2.start();t1.join();t2.join();System.out.println(i);}
}

t1,t2 所包含的Runnable实例不是同一个。

  1. 对类加锁
    把上例中的increase() 方法修改为静态方法。
public class AccountingSync implements Runnable {static AccountingSync instance = new AccountingSync();static int i = 0;public static synchronized void increase(){i++;}@Overridepublic void run() {for (int j = 0; j < 10000000; j++) {increase();}}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(new AccountingSync());Thread t2 = new Thread(new AccountingSync());t1.start();t2.start();t1.join();t2.join();System.out.println(i);}
}

Reference

参考书籍:
《实战Java高并发程序设计》
参考博客:
https://www.cnblogs.com/kukri/p/9595263.html

Java并发编程(一)——并发的基本概念相关推荐

  1. Java并发编程:并发容器之CopyOnWriteArrayList(转载)

    Java并发编程:并发容器之CopyOnWriteArrayList(转载) 原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW ...

  2. 【Java并发编程】并发编程大合集

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17539599 为了方便各位网友学习以及方便自己复习之用,将Java并发编程系列内容系列内容 ...

  3. 【转】Java并发编程:并发容器之ConcurrentHashMap

    JDK5中添加了新的concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步容器将所有对容器状态的访问都串行化了,这样保证了线程的安全性,所以这种方法的代价就是严重降低了 ...

  4. 【檀越剑指大厂--并发编程】并发编程总结

    并发编程 一.并发基础 1.什么是并行和并发? 并行,表示两个线程同时(同一时间)做事情. 并发,表示一会做这个事情,一会做另一个事情,存在着调度. 单核 CPU 不可能存在并行(微观上). 2.什么 ...

  5. 并发编程——JUC并发编程知识脑图

    摘要 并发编程在软件编程中尤为突出和重要,在当今面试或工作中也是不可缺少的.作为一名高级java开发工程师,并发编程的技能已经成为了重要的一项.本博文将详细介绍并发编程中的知识点和知识脑图,帮助大家更 ...

  6. Java并发编程以及并发须知的几个概念:什么是线程安全?

    众所周知,在Java的知识体系中,并发编程是非常重要的一环,也是面试的必问题,一个好的Java程序员是必须对并发编程这块有所了解的.为了追求成为一个好的Java程序员,我决定从今天开始死磕Java的并 ...

  7. java并发编程_Java并发编程之 synchronized

    大家好,我是你们的导师,我每天都会在这里给大家分享一些干货内容(当然了,周末也要允许老师休息一下哈).上次老师跟大家分享了下Java中内存泄漏如何分析解决的相关知识,今天跟大家分享Java之 sync ...

  8. Java并发编程进阶——并发锁

    1 JAVA 多线程锁介绍 1.1 悲观锁 定义:悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改(很悲观),所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于 ...

  9. java并发编程之 并发问题及解决方法

    一.并发问题的根源 首先,我们要知道并发要解决的是什么问题?并发要解决的是单进程情况下硬件资源无法充分利用的问题.而造成这一问题的主要原因是CPU-内存-磁盘三者之间速度差异实在太大.如果将CPU的速 ...

  10. 学习笔记:Java 并发编程⑥_并发工具_JUC

    若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 配套资料: ...

最新文章

  1. AI十级「找茬」选手,非这个书生莫属,节后开源!
  2. 人工智能时代,中国或是唯一能够和美国竞争的国家!
  3. cv2.circle函数
  4. C++ 笔记 2 (C++ primer)
  5. 从微信红包的春节活动运营方案中,必读的运营策略
  6. 【opencv】——钢管计数(霍夫圆变换 + 阈值 + canny)
  7. CentOS7没有ftp命令的解决方法
  8. wordpress之插件安装和主题安装(包含常见问题)
  9. Unity Ragdoll 实现死亡效果 心得+坑点总结
  10. js 动态生成HTML,并加载事件遇到的问题
  11. wordpress主题_ripro美化子主题_虎造子主题集成后台美化包v2.0
  12. android app内存分析,Android手机App内存占用测试方法
  13. RoboWare 下载地址
  14. 金融计量模型(十一):对波动率和相关性建模
  15. 初识c语言思维导图及大纲 (内含思维导图图片及pdf版下载链接)
  16. python_sklearn机器学习算法系列之LogisticRegression(逻辑回归)----识别垃圾邮件(短信)
  17. 天猫tf卡速度测试软件,迟到的晒单:天猫5块9包邮的32GBTF卡拆箱评测
  18. ubuntu 下myeclipse下载,安装,破解
  19. 6-4 学生成绩链表处理 (20分) 本题要求实现两个函数,一个将输入的学生成绩组织成单向链表;另一个将成绩低于某分数线的学生结点从链表中删除。 函数接口定义: ```cpp struct stu
  20. Sisvel宣布成立AV1专利池 1050项专利许可面临收费

热门文章

  1. xy坐标正负方向_xy坐标分别代表什么
  2. 腾讯云服务器备案全流程 40天备案的血与泪
  3. 武汉大学计算机学院期末考试时间,【通知公告】关于2018-2019学年第二学期期末考试工作安排的通知...
  4. 问题 B: 零基础学C/C++25——判断某整数是正整数、负整数还是零
  5. WP模板阁怎么样?能买吗
  6. Spring+jdbc的例子
  7. Eagerly caching bean '' to allow for resolving potential circular references
  8. 从0到1,看职场小白如何用H5编辑器实现自我逆袭!
  9. Ubuntu学习笔记6-ESP32接收并处理cmd_vel话题
  10. 【angular-实践】实现浏览器F11全屏效果