- 进程和线程的本质区别在于每个进程拥有自己的一整套变量,而线程共享数据。
- 创建线程
1. 实现Runnable接口(函数式接口),并将业务代码放到run方法中
Runnable r = () ->{ task code};
2. 由Runnable创建一个Thread对象
Threa t = new Thread(r);
3. 启动线程
t.start();
-
中断线程
- run方法执行方法体中最后一条语句后,并经由执行return语句返回时。
- 出现了在方法中没有捕获的异常时
出现以上两种情况时,线程会终止。
并没有一种方法可以强制让线程终止,但使用interrupt方法可以请求终止线程。
每一个线程都有一个中断状态,而且每个线程都应该不时的检查这个标志,以判断线程是否中断。
使用Thread.currentThread.isInterrupted()来检查,但如果线程被阻塞,则无法检查状态,会产生InterruptedException异常。
当调用interrupt方法时,线程的中断状态会被置位。 -
线程状态
可使用getState方法来检查线程的当前状态。具体如下:
-
线程属性
- 线程优先级
使用setPriority()设置,具体优先级高度依赖于系统。 - 守护线程:setDaemon()
唯一用途是为其他线程提供服务,应该永远不去访问固有资源。如计时线程。 - 未捕获异常处理器
该处理器需要是一个实现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变量不能提供原子性。