layout: posts
title: JAVA并发
date: 2019-03-10 20:04:52
tags: java
categories: 代码

线程

下面是一个单独的线程中执行一个任务的简单过程
1)将任务代码移动到实现了Runable接口的类的run方法中。

  1. public interface Runnable
  2. {
  3. void run();
  4. }

由于Runable是一个函数式接口,可以用lambda表达式创建一个实例:

  1. Runnable r = () ->{task code}

2)由Runnable创建一个Thread对象:

  1. Thread t = new Thread(r);

3)启动线程:

  1. t.start();

**4)也可通过构建一个Thread类的子类定义一个线程(从方法不推荐):

  1. class MyThread extends Thread{
  2. public void run()
  3. {
  4. task code;
  5. }
  6. }

线程创建调用
线程创建调用.png

中断线程

线程中断.png

线程状态

线程可以有以下6种状态:

  • New(新创建)
  • Runnable(可运行)
  • Blocked(被阻塞)
  • Waiting (等待)
  • Timed waiting(计时等待)
  • Terminated(被终止)

线程状态:
线程状态.png

线程属性

线程优先级

在java中,每一个线程有一个优先级。默认情况下,一个线程继承它的父线程的优先级。可以使用setPriority()提高或降低任何一个线程的优先级。可以将优先级设置为在MIN_PRIORITY(1)与MAX_PRIORITY(10)之间的任何值。

守护线程

为其他线程提供服务。

未捕获异常处理器(*)

线程的run方法不能抛出任何受查异常,但是,非受查异常会导致线程终止。在这种情况下,线程就死亡了。
异常处理器必须属于一个实现Thread.UncaughtExceptionHandler接口的类。

同步

竞争条件的一个例子

  1. /**
  2. * Bank类
  3. * A bank with a number of bank accounts
  4. *
  5. */
  6. public class Bank {
  7. private final double[] accounts;
  8. public Bank(int n, double initialBalance) {
  9. accounts = new double[n];
  10. Arrays.fill(accounts, initialBalance);
  11. }
  12. public void transfer(int from, int to, double amount) {
  13. if (accounts[from] <amount) return;
  14. System.out.print(Thread.currentThread());
  15. accounts[from]-=amount;
  16. System.out.printf("%10.2f from %d to %d", amount, from, to);
  17. accounts[to]+=amount;
  18. System.out.printf("Total Balance: %10.2f%n", getTotalBalance());
  19. }
  20. public double getTotalBalance() {
  21. double sum = 0;
  22. for (double a : accounts) {
  23. sum+=a;
  24. }
  25. return sum;
  26. }
  27. public int size() {
  28. return accounts.length;
  29. }
  30. }
  1. /**
  2. *
  3. *This program shows data corruption when multiple threads access a data structure
  4. *
  5. */
  6. public class UnsynchBankTest {
  7. public static final int NACCOUNTS = 100 ;
  8. public static final double INITIAL_BALANCE = 1000 ;
  9. public static final double MAX_AMOUNT = 1000 ;
  10. public static final int DELAY = 10;
  11. public static void main(String[] args){
  12. Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE);
  13. for (int i = 0; i < NACCOUNTS; i++) {
  14. int fromAccount =i;
  15. Runnable r = () -> {
  16. try {
  17. while (true) {
  18. int toAccount = (int) (bank.size() * Math.random());
  19. double amount = MAX_AMOUNT * Math.random();
  20. bank.transfer(fromAccount, toAccount, amount);
  21. Thread.sleep((int) (DELAY * Math.random()));
  22. }
  23. } catch (InterruptedException e) {
  24. }
  25. };
  26. Thread t = new Thread(r);
  27. t.start();
  28. }
  29. }
  30. }

当两个线程试图同时更新同一个账号的时候,问题就出现了,架设两个线程同时执行指令:

accounts[to] += amount

问题在于这不是原子操作。该指令可能被处理如下:

1 ) 将 accounts [ to ] 加载到寄存器 。
2 ) 增 加 amount 。
3 ) 将结果写回 accounts [ to ] 。

现在假定第1个线程执行步骤1和步骤2,然后,他被剥夺了运行权。假定第2个线程被唤醒并修改了accounts数组中的同一项。然后,第2个线程被唤醒并完成其第3步。这一动作擦去了第2个线程所做的更新。

锁对象

有两种机制防止代码块受并发访问的干扰。java语言提供一个synchronized关键字达到这一目的》synchronized关键字自动提供一个锁以及相关的条件。
  用ReentrantLock保护代码块的基本结构如下:

  1.   myLock.lock();
  2. try
  3. {
  4. critical section
  5. }
  6. finally
  7. {
  8. myLock.unlock(); //make sure the lock is unlocked even if an exception is thrown
  9. }

这一结构确保任何时刻只有一个线程进入临界区,一旦一个线程封锁了锁对象,其他任何线程都无法通过lock语句。当其他线程调用lock时,他们被阻塞,直到第一个线程释放锁对象(将锁放在finally子句之内是至关重要的)。

给代码上锁:

上锁.png

条件对象

一个锁对象可以有一个或多个相关的条件对象。你可以使用newCondition方法获得一个条件对象,习惯上给每一个条件对象命名为可以反映它所表达的条件的名字,例如,在此设置一个条件对象来表达“余额充足”条件。

  1. Class Bank{
  2. private Condition sufficientFunds;
  3. public Bank(){
  4. ...
  5. sufficientFunds =bankLock.newCondition();
  6. }
  7. }
  1. 如果transfer方法发现余额不足,它调用
  1. sufficientFunds.await();
  1. 当前线程现在被阻塞了,并放弃了锁。我们希望这样可以使得另一个线程可以进行增加账户余额的操作。

当另一个线程转账时,它应该调用:

  1. sufficientFunds.singalAll()
  1. 等待获得锁的线程和调用await方法的线程存在本质上的不同。一旦一个线程调用await方法,它进入该条件的等待集。当锁可用时,该线程不能马上解除阻塞。相反,他处于阻塞状态,直到另一个线程调用同一条件上的singalAll方法时为止。singalAll方法仅仅是通知正在等待的线程:此时有可能已经满足条件,值得再次去检验条件。<br />对await的调用应该在如下的形式的循环体中
  1. while(!(ok to proceed))
  2. condition.await();
  1. 应该何时调用 signalAll 呢?经验上讲,在对象的状态有利于等待线程的方向改变时调用signalAll
  1. public void transfer ( int from , int to , int amount )
  2. {
  3. bankLock.lock();
  4. try
  5. {
  6. while ( accounts [ from ] < amount )
  7. sufficientFunds.await();
  8. / / transfer funds
  9. sufficientFunds.signalAll();
  10. }
  11. finally
  12. {
  13. bankLock.unlock();
  14. }
  15. }

synchronized关键字

  1. 如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。也就是说,要调用该方法,线程必须获得内部的对象锁。<br /> 换句话说:
  1. public synchronized void method(){
  2. method body
  3. }
  1. 等价于
  1. public void method(){
  2. this.intrinsicLock.lock();
  3. try
  4. {
  5. method body
  6. }
  7. finally{
  8. this.intrinsiclock.unlock;
  9. }
  10. }

同步阻塞

略 待补

监视器概念

略 待补

final变量

略 待补

Volatile 域

略 待补

死锁

遗憾的是
Java 编程语言中没有任何东西可以避免或打破这种死锁现象。必须仔细设计
程序,以确保不会出现死锁。

线程局部变量

略 待补

锁测试与超市

  1. 线程在调用 lock 方法来获得另一个线程所持有的锁的时候,很可能发生阻塞。应该更加谨慎地申请锁。tryLock 方法试图申请一个锁,在成功获得锁后返回 true, 否则,立即返回false,而且线程可以立即离开去做其他事情。

读写锁

读写锁.png

阻塞队列