并发相关要点记录

  • 进程和线程的本质区别在于每个进程拥有自己的一整套变量,而线程共享数据。
  • 创建线程
1. 实现Runnable接口(函数式接口),并将业务代码放到run方法中
	Runnable r = () ->{ task code};
2. 由Runnable创建一个Thread对象
	Threa t = new Thread(r);
3. 启动线程
	t.start();
  • 中断线程

    1. run方法执行方法体中最后一条语句后,并经由执行return语句返回时。
    2. 出现了在方法中没有捕获的异常时

    出现以上两种情况时,线程会终止。
    并没有一种方法可以强制让线程终止,但使用interrupt方法可以请求终止线程。
    每一个线程都有一个中断状态,而且每个线程都应该不时的检查这个标志,以判断线程是否中断。
    使用Thread.currentThread.isInterrupted()来检查,但如果线程被阻塞,则无法检查状态,会产生InterruptedException异常。
    当调用interrupt方法时,线程的中断状态会被置位。

  • 线程状态
    可使用getState方法来检查线程的当前状态。具体如下:
    在这里插入图片描述

  • 线程属性

    1. 线程优先级
      使用setPriority()设置,具体优先级高度依赖于系统。
    2. 守护线程:setDaemon()
      唯一用途是为其他线程提供服务,应该永远不去访问固有资源。如计时线程。
    3. 未捕获异常处理器
      该处理器需要是一个实现Thread.UncaughtExceptionHandler的类。可以使用setUncaughtExceptionHandler方法为线程安装一个处理器,也可以使用静态方法setDefaultUncaughtExceptionHandler为所有线程安装一个默认处理器。
  • 同步
    例子:转账

public void transfer(int from, int to, double amount)
{
	System.out.print(Thread.currentThread());
	accounts[from] -= amount;
	accounts[to]   += amount;
}

假设有10个账户,每个账户起始金额均为1000,那么程序运行一段时间后,总金额会不等于10000。这是因为有可能当前线程还没有转账成功就被其他线程抢占。出现的原因是操作不是原子的
锁对象
使用ReentrantLock可以防止代码块受并发问题的干扰,具体如下:

myLock.lock(); //a ReentrantLock object
try
{
	critical section
}
finally
{
	myLock.unlock();
}

这里把解锁操作放在finally子句之内是至关重要的。如果在临界区的代码抛出异常,锁必须被释放。否则,其他线程将永远阻塞。但是不能使用带资源的try语句。

条件对象
通常,线程进入临界区,却发现在某一条件满足之后它才能执行。
如果不是并发程序,我们可以这样实现

if (bank.getBalance(from) >= amount)
	bank.transfer(from, to, amount);

但如果是并发程序,当前线程有可能在成功的完成测试且在调用transfer方法之前被中断。我们可以使用锁来保护检查的代码:

public void transfer(int from, int to, double amount)
{
	bankLock.lock();
	try
	{
		while(accounts[from] < anount)
		{ 
			// wait
			...
		}
		// transfer funds
		...
	}
	finally {
		bankLock.unlock();
	}
}

上述程序在账户中没有足够的余额时,会一直等待。直到另一个线程向账户中添加资金。那么,问题来了,这个线程是具有bankLock的排他性访问的,也就是不允许其他线程访问,那么别的线程是无法添加的,那么我们就需要条件对象来处理。

class Bank
{
	private Condition sufficientFounds;
	...
	public Bank(){
		...
		sufficientFounds = bankLock.newCondition();
	}
}

// 如果transfer方法发现余额不足,它会调用
sufficientFounds.await();
// 这样会导致当前线程阻塞,并且放弃锁

// 当里一个线程转账时,它应该调用
sufficientFounds.signalAll();
// 这样会激活因为条件变量而等待的所有线程。

锁和条件的关键

1. 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。
2. 锁可以管理试图进入被保护代码段的线程
3. 锁可以拥有一个或多个相关的条件对象
4. 每个条件对象管理那些已经进入被保护代码段但还不能运行的线程。

synchronized关键字
Java中的每一个对象都有一个内部锁。如果一个方法用synchronized声明,那么对象的锁将保护整个方法。

public synchronized void method{
	method body
}

内部对象锁只有一个相关条件。

使用wait方法添加一个线程到等待集中
notifyAll/notify方法解除等待线程的阻塞状态

同步阻塞
获取Java对象内部锁的另一种机制

// 获得obj的锁
synchronized (obj) {
	critical section
}

可以使用一个对象的锁实现额外的原子操作。
如使用同步的Vector来存储银行余额,以下时transfer方法的原始实现:

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

虽然Vector类的get和set方法是同步的,但是transfer方法内部还是异步的,所以可以如下实现:

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

监视器
特性:
1. 监视器只包含私有域的类。
2. 每个监视器类的对象有一个相关锁
3. 使用该锁对所有的方法进行加锁
4. 该锁可以有任意多个相关条件
但是,Java设计并没有完全遵循监视器,导致没有那么的安全。

Volatile域
为实例域的同步提供了一种免锁机制。
如果声明一个域为volitile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
但Volatile变量不能提供原子性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值