目录
一. Overview
二. 中断线程
三. 线程状态
四. 线程属性
- 线程优先级
- 守护线程
- 未捕获异常处理器
五. 同步
- 条件对象
- 同步阻塞
- Volatile关键词
- 锁测试与超时
六. 阻塞队列
七. 线程安全的集合
八. 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方法;
二. 中断线程
三. 线程状态
线程有六种状态:
- New 新创建
- Runnable 可运行
- Blocked 被阻塞
- Waiting 等待
- Timed waiting 计时等待
- 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个办法:
- 第一个方法是
signal()
方法, 会随机选择一个线程唤醒, 但是这可能出现一个情况, 就是这个线程还是不满足条件, 他就会继续留在Condition的等待集里面, 这样就没有线程还活着了, 也就没有机会唤醒剩下的其他线程了, 这种情况被称为死锁(deadlock); - 第二种方法是用
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建议这样写, 然后这里的wait
或notifyAll
是等价于await
或signalAll
的;
这里的wait
和notifyAll
都是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来获得大小;
我尼玛是真看不下去了, 以后再好好学这个把!!!呜呜呜