Java基础 自学讲义 11. 并发

目录
一. Overview
二. 中断线程
三. 线程状态
四. 线程属性
  1. 线程优先级
  2. 守护线程
  3. 未捕获异常处理器
五. 同步
  1. 条件对象
  2. 同步阻塞
  3. Volatile关键词
  4. 锁测试与超时
六. 阻塞队列
七. 线程安全的集合
八. Callable与Future
九. 执行器同步器
十. 线程与Swing

一. Overview


这样定义一个线程:

         Thread a = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Aluka");
            }
         });
         a.start();

也可以用lambda表达式来写Runnable:

         Thread a = new Thread(()->{
             System.out.println("Aluka");
         });

这个线程会在线程start的时候自动执行run方法;

二. 中断线程

三. 线程状态

线程有六种状态:

  1. New 新创建
  2. Runnable 可运行
  3. Blocked 被阻塞
  4. Waiting 等待
  5. Timed waiting 计时等待
  6. Terminated 被终止

可以用getState方法获得线程所处的状态;
几个状态的解释有些含糊, 关系图如下:

四.线程属性

1.线程优先级

java的线程优先级有1-10的等级, 但是具体实现依赖于系统, 比如windows有7个优先级, 会自动映射到系统优先级去;
每当线程调度器有机会选择新线程时会优选高优先级线程;

2.守护线程

这样设置守护线程:

a.setDaemon(true);
a.start();

要记得在start之前设置守护线程;
守护线程可以看做是一个后台线程, 比如一个计时器守护线程, 在其他所有非守护线程终止的时候守护线程也会随着JVM一起终止;
CoreJava提示守护线程永远不应该调用固有资源比如文件, 数据库, 因为他随时都有可能会中断;

3.未捕获异常处理器

要用setUncaughtExceptionHandler方法为线程安装一个实现了Thread.UncaughtExceptionHandler接口的异常处理器, 如果不安装的话就默认处理器为空, 然后就会变成一个ThreadGroup类:
在这里插入图片描述

五.同步

不同的线程有可能同一时间访问相同的数据, 而最后的结果取决于进程运行的精确时序, 这就是竞争条件(race condition);
可以用reentrantLock来在某个数据被调用的时候暂时锁住这个数据避免造成非同步读写操作;

1. 条件对象

这个东西还是有点东西的, 要好好看一下:
今天发烧了不想写代码, 贴一段示范代码, 解读一下:

package synch;

/**
 * This program shows how multiple threads can safely access a data structure.
 * @version 1.31 2015-06-21
 * @author Cay Horstmann
 */
public class SynchBankTest
{
   public static final int NACCOUNTS = 100;
   public static final double INITIAL_BALANCE = 1000;
   public static final double MAX_AMOUNT = 1000;
   public static final int DELAY = 10;
   
   public static void main(String[] args)
   {
      Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE);
      for (int i = 0; i < NACCOUNTS; i++)
      {
         int fromAccount = i;
         Runnable r = () -> {
            try
            {
               while (true)
               {
                  int toAccount = (int) (bank.size() * Math.random());
                  double amount = MAX_AMOUNT * Math.random();
                  bank.transfer(fromAccount, toAccount, amount);
                  Thread.sleep((int) (DELAY * Math.random()));//这个似乎没什么意义, 就是加个时延罢了
                  //可能是为了同步输出考虑, 但是我去掉这句话好像也能同步输出
               }
            }
            catch (InterruptedException e)
            {
            }            
         };
         Thread t = new Thread(r);
         t.start();
      }
   }
}

首先是Test的主函数部分, 这部分很简单:
现在银行初始化一些银行账户, 然后不停的做随机转账, 然后为什么这里不会发生非同步的竞争的错误呢? 就要看一下Bank类的实现了:

package synch;

import java.util.*;
import java.util.concurrent.locks.*;

/**
 * A bank with a number of bank accounts that uses locks for serializing access.
 * @version 1.30 2004-08-01
 * @author Cay Horstmann
 */
public class Bank
{
   private final double[] accounts;
   private Lock bankLock;
   private Condition sufficientFunds;

   /**
    * Constructs the bank.
    * @param n the number of accounts
    * @param initialBalance the initial balance for each account
    */
   public Bank(int n, double initialBalance)
   {
      accounts = new double[n];
      Arrays.fill(accounts, initialBalance);
      bankLock = new ReentrantLock();
      sufficientFunds = bankLock.newCondition();
   }

   /**
    * Transfers money from one account to another.
    * @param from the account to transfer from
    * @param to the account to transfer to
    * @param amount the amount to transfer
    */
   public void transfer(int from, int to, double amount) throws InterruptedException
   {
      bankLock.lock();
      try
      {
         while (accounts[from] < amount)
            sufficientFunds.await();
         System.out.print(Thread.currentThread());
         accounts[from] -= amount;
         System.out.printf(" %10.2f from %d to %d", amount, from, to);
         accounts[to] += amount;
         System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
         sufficientFunds.signalAll();
      }
      finally
      {
         bankLock.unlock();
      }
   }

   /**
    * Gets the sum of all account balances.
    * @return the total balance
    */
   public double getTotalBalance()
   {
      bankLock.lock();
      try
      {
         double sum = 0;

         for (double a : accounts)
            sum += a;

         return sum;
      }
      finally
      {
         bankLock.unlock();
      }
   }

   /**
    * Gets the number of accounts in the bank.
    * @return the number of accounts
    */
   public int size()
   {
      return accounts.length;
   }
}

上面就是这个Bank类的代码, 先看一下构造函数部分:

   private final double[] accounts;
   private Lock bankLock;
   private Condition sufficientFunds;

   public Bank(int n, double initialBalance)
   {
      accounts = new double[n];
      Arrays.fill(accounts, initialBalance);
      bankLock = new ReentrantLock();
      sufficientFunds = bankLock.newCondition();
   }

他前面先初始化创建了一些账户, 这都是基本操作, 但是他声明了两个东西, 一个Lock, 一个Condition, 然后在初始化的时候他先实例化了一个ReentrantLock赋给了Lock, 然后又用之前的Lock初始化了一个newCondition赋给了Condition, 这里的Condition应该可以理解成一个条件变量的集合, 关于这个条件判定不满足的线程都会进这个集合里面, 那么它是怎么用的呢, 看一下transfer的实现:

   public void transfer(int from, int to, double amount) throws InterruptedException
   {
      bankLock.lock();
      try
      {
         while (accounts[from] < amount)
            sufficientFunds.await();
         System.out.print(Thread.currentThread());
         accounts[from] -= amount;
         System.out.printf(" %10.2f from %d to %d", amount, from, to);
         accounts[to] += amount;
         System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
         sufficientFunds.signalAll();
      }
      finally
      {
         bankLock.unlock();
      }
   }

在方法开始的时候它先用lock锁住了数据, 防止出现竞争错误, 然后此时他先进行了一个判断:

		while (accounts[from] < amount)
		   sufficientFunds.await();

当被转出账户没有足够的余额的时候, 就把这个线程的转账的事情放到Condition(就是这里的sufficientFunds对象)的等待集合里面(此时这个线程处于Blocked状态), 等到满足这个条件的时候再来transfer, 这里是这样操作的, 先放入等待集是用的await()方法, 然后这个线程什么时候再回来检查, 是否满足条件要运行这个方法呢, 这取决于其他线程什么时候来唤醒这个线程, 唤醒有2个办法:

  1. 第一个方法是signal()方法, 会随机选择一个线程唤醒, 但是这可能出现一个情况, 就是这个线程还是不满足条件, 他就会继续留在Condition的等待集里面, 这样就没有线程还活着了, 也就没有机会唤醒剩下的其他线程了, 这种情况被称为死锁(deadlock);
  2. 第二种方法是用signalAll()方法, 这会提醒其他所有线程来检查是否满足条件了, 所以这个例子在每个账户的余额发生变动的时候唤醒所有线程来检查;

然后是一些Condition和Lock常用的方法:

呜呜

然后呢 上面这样用Lock-Condition的方法固然是可以的, 但是写起来比较烦, java提供了synchronized关键字, 怎么用呢, 看一下对上面transfer方法的改写:


   public synchronized void transfer(int from, int to, double amount) throws InterruptedException
   {
      while (accounts[from] < amount)
         wait();
      System.out.print(Thread.currentThread());
      accounts[from] -= amount;
      System.out.printf(" %10.2f from %d to %d", amount, from, to);
      accounts[to] += amount;
      System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
      notifyAll();
   }

这就等效于之前的那个transfer方法, 不用去定义Lock和Condition方法了, 这比较方便, CoreJava建议这样写, 然后这里的waitnotifyAll是等价于awaitsignalAll的;
这里的waitnotifyAll都是Object类里定义的final方法;

上一些方法图:

在这里插入图片描述

小声BB: 所以这么说似乎给这个wait加个最长等待时间也不错 ?好像挺稳健的:D

2.同步阻塞, 客户端锁定来实现额外的原子操作

有这样一种锁:

private Object lock = new Object();	
public void transfer(){
	synchronized(lock){
		...code
	}
}

考虑这样的情况:

public void transfer(Vector<Double> accounts, int from, int to, int amount) {
		synchronized(accounts){
			accounts.set(from, accounts.get(from) - amount);
			accounts.set(to, accounts.get(to) + amount);
		}
	}

Vector的所有方法都是线程安全的, 这意味着在调用某个方法的时候是同步的, 但是方法与方法之间的间隔是不安全的, 就有可能在两个方法调用的间隔时发生冲突, 所以要给整体代码加锁;
因为如果不采用这样的synchronized(accounts)锁, 有可能在get方法完成之后, set方法执行之前, transfer方法的使用权就被其他线程夺走了;
书上说, 这依赖于Vector的所有方法都是线程安全的, 但是我觉得这很奇怪, 因为我觉得这里的所有方法都已经被synchronized(accounts)锁定了, 应该不会出现线程冲突呀;

这里有个监视器的概念, 没太看懂

BrianGoetz给出了下述 “同步格言”:“如果向一个变量写入值,而这个变量接下 来可能会被另一个线程读取, 或者, 从一个变量读值, 而这个变量可能是之前被另一个 线程写入的, 此时必须使用同步”。
3.Volatile关键词

volatile 只能保证 “可见性”,不能保证 “原子性”。count++; 这条语句由3条指令组成: (1)将 count 的值从内存加载到 cpu 的某个寄存器r (2)将 寄存器r 的值 +1,结果存放在 寄存器s (3)将 寄存器s 中的值写回内存所以,如果有多个线程同时在执行 count++;,在某个线程执行完第(3)步之前,其它线程是看不到它的执行结果的。在没有 volatile 的时候,执行完 count++;,执行结果其实是写到CPU缓存中,没有马上写回到内存中,后续在某些情况下(比如CPU缓存不够用)再将CPU缓存中的值flush到内存。正因为没有马上写到内存,所以不能保证其它线程可以及时见到执行的结果。在有 volatile 的时候,执行完 count++;,执行结果写到CPU缓存中,并且同时写回到内存,因为已经写回内存了,所以可以保证其它线程马上看到执行的结果。但是,volatile 并没有保证原子性,在某个线程执行(1)(2)(3)的时候,volatile 并没有锁定 count 的值,也就是并不能阻塞其他线程也执行(1)(2)(3)。可能有两个线程同时执行(1),所以(2)计算出来一样的结果,然后(3)存回的也是同一个值。

final变量
原子性:可以用一些支持原子性的类来保证操作的同步性;
死锁
线程局部变量

4. 锁测试与超时

读写锁

六.阻塞队列

七.线程安全的集合

有的数据结构不是线程安全的, 有的时候如果两个线程同时访问一个数据结构可能会出问题, 所以可以使用一些线程安全的集合比较稳健:
java.util.concurrent 包提供了映射、 有序集和队列的高效实现: ConcurrentHashMap、 ConcurrentSkipListMap > ConcurrentSkipListSet 和 ConcurrentLinkedQueue
有的并发散列映射太过庞大, 不能用int的size来获取大小, 可以用mappingCount返回一个long来获得大小;

我尼玛是真看不下去了, 以后再好好学这个把!!!呜呜呜

目 录 第一章 JAVA入门 10 计算机语言发展史 10 机器语言 10 汇编语言 10 高级语言 10 其他高级语言 11 JAVA发展简史 12 JAVA为什么能够流行? 13 JAVA各版本的含义 13 JAVA技术体系架构 14 JAVA的特性和优势 14 JAVA应用程序的运行机制 15 JVM(JAVA VIRTUAL MACHINE) 16 Java运行时环境JRE(Java Runtime Environment) 17 JAVA语言应用范围 18 第一个JAVA程序 18 JAVA开发环境搭建 18 一个典型的JAVA程序的编写和运行过程 19 第一个程序常见错误 20 第一个JAVA程序的总结和提升 20 常用Java开发工具 20 常用dos命令 21 本章笔试作业 21 本章上机操作 21 第二章(1) 编程的基本概念 22 注释 22 标识符 22 关键字/保留字 23 变量(variable) 24 常量(Constant) 25 命名规则(规范) 25 基本数据类型(primitive data type) 26 整型变量 26 浮点型 27 字符型(2个字节): 28 boolean类型 29 运算符(operator) 29 二元运算符 29 一元运算符 30 布尔逻辑表达符 30 位运算符 30 扩展运算符 31 字符串连接符 31 三目条件运算符 31 运算符优先级的问题 31 自动类型转换 32 基本类型转化时常见错误和问题 33 方法 33 简单的键盘输入和输出 33 本章思考作业 34 上机操作 34 第二章(2) 控制语句 35 顺序结构 35 选择结构 35 if单选择结构 35 if-else双选择结构 35 If-elseif-else多选择结构 36 switch多选择结构 37 循环结构 39 While和dowhile的区别 41 For循环 42 break语句和continue语句 47 语句块 48 递归结构 49 本章作业 50 本章上机操作 51 第三章 JAVA面向对象程序开发 52 编程语言发展史 52 类和对象是如何产生发展的?如何进化的? 52 面向对象思想初步(OOP初步Object Oriented Programming) 53 面向对象编程的语言的三大特征简介 56 对象和类的概念 56 类和对象初步 57 测试类的定义方式 57 简单的学生类编写示例 58 内存分析 59 属性(field,或者叫成员变量) 59 引用类型 60 类的方法 60 对象的创建和使用 60 构造器(或者叫做构造方法,constructor) 60 垃圾回收机制(Garbage Collection) 63 方法的重载(overload),构造方法的重载 63 this关键字 65 static 关键字 66 静态初始化块(经常用来初始化类,加载类信息时执行!) 67 package 68 JDK中的主要包 68 import 68 eclipse的使用 69 继承(extend, inheritance) 70 为什么需要继承?继承的作用? 70 继承介绍 70 如何实现继承? 70 继承使用要点 71 Object类 72 toString方法 72 equals方法 73 super关键字 74 方法的重写(override) 74 隐藏/封装(encapsulation) 75 为什么需要封装?封装的作用和含义? 75 使用访问控制符,实现封装 76 封装的使用细节 76 多态(polymorphism) 76 为什么需要多态? 76 如何实现多态? 77 方法绑定(method binding) 77 静态绑定 77 动态绑定 77 多态的使用要点 78 对象的转型(casting) 79 final 81 抽象类 82 抽象类的使用要点 83 接口 83 为什么需要接口? 84 如何定义接口? 84 接口的本质探讨 84 接口使用要点 85 接口的多继承 86 面向接口编程 87 OOP更多应用 87 组合 87 内部类(innerclasses) 88 字符串(java.lang.String类)的使用 90 字符串相等的判断 92 思考作业 93 上机作业 94 第四章 异常机制 95 导引问题 95 异常(Exception)的概念 96 异常分类 96 Error 97 Error和Exception的区别 97 Exception 97 异常的处理办法之一,捕获异常 99 try块 99 catch 99 finally 100 try, catch,finally ,return 执行顺序 100 异常的处理办法之二,声明异常: throws子句 101 方法重写中声明异常原则 102 异常的处理办法之三,手动抛出异常,throw子句 103 自定义异常 103 使用异常机制建议 104 总结 105 思考作业 105 上机作业 105 第五章 数组 106 数组概述和特点 106 创建数组和初始化 106 数组常见操作 108 数组的拷贝 108 数组排序 109 多维数组 110 附录(面试前复习一下!!) 111 冒泡排序 111 二分法查找 112 命令行参数的问题 113 增强for循环 114 思考作业 114 上机作业 115 第六章 常用类的使用 117 基本数据类型的包装类 117 包装类基本知识 117 包装类的用途 118 自动装箱和拆箱?autoboxing,unboxing 119 字符串相关类(String、 StringBuffer 、 StringBuilder) 120 String类的常用方法(已讲过,不再讲!) 120 StringBuffer和StringBuilder 121 String和StringBuffer和StringBuilder使用要点 123 时间处理相关类 124 Date时间类(java.util.Date) 124 DateFormat类和SimpleDateFormat类 125 Calendar日历类 126 可视化日历的编写 128 Math类 131 File类 132 File类的基本用法 132 树状结构展现文件结构 133 枚举 133 上机作业 135 第七章 容器(Collection) 136 容器的作用和概览 136 容器中的接口层次结构 136 Collection接口 137 LIST接口 137 SET接口 138 Map接口 138 Iterator接口 139 遍历集合 140 Collections工具类 141 Comparable接口 141 equals和hashcode方法 143  泛型 144 思考作业 145 上机作业 145 第八章 IO技术 146 为什么需要学习IO技术 146 基本概念 146 数据源 146 流的概念 146 第一个简单的IO流程序及深入(将文件中的数据读入) 146 Java中流的概念细分 148 Java中IO流类的体系 149 四个IO基本抽象类 150 InputStream 150 OutputStream 150 常用InputStream和OutputStream子类用法 150 FileInputStream和FileOutputStream 150 ByteArrayInutStream和ByteArrayOutputStream 154 BufferedInputStream和BufferedOutputStream 156 DataInputStream和DataOutputStream 157 ObjectInputStream和ObjectOutputStream 158 PrintStream 158 Reader 158 Writer 159 FileReader和FileWriter 159 BufferReader和BufferWriter 159 InputStreamReader和OutputStreamWriter 161 JAVA对象的序列化和反序列化 161 为什么需要序列化和反序列化 161 对象的序列化主要有两种用途 161 序列化涉及的类和接口 162 序列化/反序列化的步骤和实例 162 综合的序列化和反序列化练习 163 JAVA.IO包相关流对象用法总结(尚学堂1002班王鑫) 165 IO中其他常用类 165 File类 165 RandomAccessFile 166 思考作业 166 上机作业 166 提高课外作业 166 第九章 多线程技术 167 基本概念 167 程序 167 进程 167 线程 167 线程和进程的区别 167 进程与程序的区别 168 JAVA中如何实现多线程(重点!!) 168 通过继承Thread类实现多线程 168 通过Runnable接口实现多线程 169 线程状态和sleep/yield/join/stop/destroy方法 170 新生状态 170 就绪状态 170 运行状态 170 死亡状态 170 终止线程的典型方法(重要!!!) 171 阻塞状态(sleep/yield/join方法) 171 线程基本信息和优先级别 173 线程同步和死锁问题 175 死锁及解决方案 179 生产者/消费者模式 181 线程回顾总结 184 任务调度(补充内容,了解即可!) 184 思考作业 185 上机作业 185 第十章 网络编程 186 基本概念 186 什么是计算机网络 186 计算机网络的主要功能 186 什么是网络通信协议 186 网络通信接口 186 为什么要分层 186 通信协议的分层规定 186 数据封装 188 数据拆封 188 IP 188 端口 188 URL 189 TCP协议和UDP协议 189 区别 189 TCP协议 189 UDP协议 190 JAVA网络编程 190 InetAddress 190 InetSocketAddress 191 URL类 191 基于TCP协议的SOCKET编程和通信 193 UDP通讯的实现 201 思考作业 203 上机作业(分组完成,3人一组,周末完成) 204 第十一章 JAVA多媒体编程 205 字体 205 字体示例和效果 205 颜色 206 颜色编程示例 206 图形绘制 206 绘制各种图形示例 207 图像处理 208 加载图片示例 208 图片任意缩放并输出新图片 209 使用JMF处理音频和视频 211 JMF的下载 211 JMF的安装和配置 211 使用JMF播放音频文件 212 上机作业 213 第十二章 GUI编程之AWT 214 为什么需要GUI?GUI是什么? 214 AWT是什么? 214 AWT的优势 214 AWT的缺点 214 为什么还需要学习AWT? 215 AWT各组件的整体关系 215 组件 215 容器 216 布局管理器 218 为什么需要布局管理器? 218 FlowLayout布局管理器 218 BorderLayout布局管理器 219 CardLayout布局管理器 220 GridLayout布局管理器 220 GridBagLayout布局管理器 221 综合应用组件和容器和布局管理器 221 AWT事件处理模型 223 问题 223 事件处理模型是什么? 223 最简单的事件处理程序 224 AWT中事件处理代码编写 225 编写过程 225 处理过程 225 简化理解上面过程 225 事件和事件源对象 225 事件适配器 232 为什么需要事件适配器 232 事件监听器常见的定义形式 233 AWT其他组件 233 菜单的实现 233 特点 233 代码示例和效果 233
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值