layout: posts
title: JAVA并发
date: 2019-03-10 20:04:52
tags: java
categories: 代码
线程
下面是一个单独的线程中执行一个任务的简单过程
1)将任务代码移动到实现了Runable接口的类的run方法中。
public interface Runnable
{
void run();
}
由于Runable是一个函数式接口,可以用lambda表达式创建一个实例:
Runnable r = () ->{task code}
2)由Runnable创建一个Thread对象:
Thread t = new Thread(r);
3)启动线程:
t.start();
**4)也可通过构建一个Thread类的子类定义一个线程(从方法不推荐):
class MyThread extends Thread{
public void run()
{
task code;
}
}
线程创建调用
中断线程
线程状态
线程可以有以下6种状态:
- New(新创建)
- Runnable(可运行)
- Blocked(被阻塞)
- Waiting (等待)
- Timed waiting(计时等待)
- Terminated(被终止)
线程状态:
线程属性
线程优先级
在java中,每一个线程有一个优先级。默认情况下,一个线程继承它的父线程的优先级。可以使用setPriority()提高或降低任何一个线程的优先级。可以将优先级设置为在MIN_PRIORITY(1)与MAX_PRIORITY(10)之间的任何值。
守护线程
为其他线程提供服务。
未捕获异常处理器(*)
线程的run方法不能抛出任何受查异常,但是,非受查异常会导致线程终止。在这种情况下,线程就死亡了。
异常处理器必须属于一个实现Thread.UncaughtExceptionHandler接口的类。
同步
竞争条件的一个例子
/**
* Bank类
* A bank with a number of bank accounts
*
*/
public class Bank {
private final double[] accounts;
public Bank(int n, double initialBalance) {
accounts = new double[n];
Arrays.fill(accounts, initialBalance);
}
public void transfer(int from, int to, double amount) {
if (accounts[from] <amount) return;
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());
}
public double getTotalBalance() {
double sum = 0;
for (double a : accounts) {
sum+=a;
}
return sum;
}
public int size() {
return accounts.length;
}
}
/**
*
*This program shows data corruption when multiple threads access a data structure
*
*/
public class UnsynchBankTest {
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();
}
}
}
当两个线程试图同时更新同一个账号的时候,问题就出现了,架设两个线程同时执行指令:
accounts[to] += amount
问题在于这不是原子操作。该指令可能被处理如下:
1 ) 将 accounts [ to ] 加载到寄存器 。
2 ) 增 加 amount 。
3 ) 将结果写回 accounts [ to ] 。
现在假定第1个线程执行步骤1和步骤2,然后,他被剥夺了运行权。假定第2个线程被唤醒并修改了accounts数组中的同一项。然后,第2个线程被唤醒并完成其第3步。这一动作擦去了第2个线程所做的更新。
锁对象
有两种机制防止代码块受并发访问的干扰。java语言提供一个synchronized关键字达到这一目的》synchronized关键字自动提供一个锁以及相关的条件。
用ReentrantLock保护代码块的基本结构如下:
myLock.lock();
try
{
critical section
}
finally
{
myLock.unlock(); //make sure the lock is unlocked even if an exception is thrown
}
这一结构确保任何时刻只有一个线程进入临界区,一旦一个线程封锁了锁对象,其他任何线程都无法通过lock语句。当其他线程调用lock时,他们被阻塞,直到第一个线程释放锁对象(将锁放在finally子句之内是至关重要的)。
给代码上锁:
条件对象
一个锁对象可以有一个或多个相关的条件对象。你可以使用newCondition方法获得一个条件对象,习惯上给每一个条件对象命名为可以反映它所表达的条件的名字,例如,在此设置一个条件对象来表达“余额充足”条件。
Class Bank{
private Condition sufficientFunds;
public Bank(){
...
sufficientFunds =bankLock.newCondition();
}
}
如果transfer方法发现余额不足,它调用
sufficientFunds.await();
当前线程现在被阻塞了,并放弃了锁。我们希望这样可以使得另一个线程可以进行增加账户余额的操作。
当另一个线程转账时,它应该调用:
sufficientFunds.singalAll()
等待获得锁的线程和调用await方法的线程存在本质上的不同。一旦一个线程调用await方法,它进入该条件的等待集。当锁可用时,该线程不能马上解除阻塞。相反,他处于阻塞状态,直到另一个线程调用同一条件上的singalAll方法时为止。singalAll方法仅仅是通知正在等待的线程:此时有可能已经满足条件,值得再次去检验条件。<br />对await的调用应该在如下的形式的循环体中
while(!(ok to proceed))
condition.await();
应该何时调用 signalAll 呢?经验上讲,在对象的状态有利于等待线程的方向改变时调用signalAll。
public void transfer ( int from , int to , int amount )
{
bankLock.lock();
try
{
while ( accounts [ from ] < amount )
sufficientFunds.await();
/ / transfer funds
sufficientFunds.signalAll();
}
finally
{
bankLock.unlock();
}
}
synchronized关键字
如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。也就是说,要调用该方法,线程必须获得内部的对象锁。<br /> 换句话说:
public synchronized void method(){
method body
}
等价于
public void method(){
this.intrinsicLock.lock();
try
{
method body
}
finally{
this.intrinsiclock.unlock;
}
}
同步阻塞
略 待补
监视器概念
略 待补
final变量
略 待补
Volatile 域
略 待补
死锁
遗憾的是
Java 编程语言中没有任何东西可以避免或打破这种死锁现象。必须仔细设计
程序,以确保不会出现死锁。
线程局部变量
略 待补
锁测试与超市
线程在调用 lock 方法来获得另一个线程所持有的锁的时候,很可能发生阻塞。应该更加谨慎地申请锁。tryLock 方法试图申请一个锁,在成功获得锁后返回 true, 否则,立即返回false,而且线程可以立即离开去做其他事情。