Java 多线程总结
本文内容主要来自《Java核心技术》、《操作系统概念》
——————————————目录——————————————
1 进程和线程
2 线程状态
3 同步 重入锁 条件对象
进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。程序只是被动实体,当一个可执行文件被装入内存时,一个程序才能成为进程。
线程:是操作系统能够进行运算调度的最小单位。线程是CPU使用的基本单位,它由线程ID、程序计数器、寄存器集合和栈组成。它与属于同一进程的其他线程共享代码段、数据段和其他操作系统资源。
线程状态
新创建:用new操作符创建一个新线程
可运行:调用start方法,线程处于runnable状态
被阻塞:当一个线程试图获取一个内部的对象锁,而该锁被其他线程持有,该线程进入阻塞状态。
等待:当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。(Object.wait、Thread.join或者是等待Lock或Condition)
计时等待:调用带有超时参数的方法。(Thread.sleep)
被终止:线程因run方法正常退出而自然死亡或一个没有捕获的异常终止了run方法意外死亡。
同步
在多线程应用中,两个或以上的线程需要共享对同一数据的存取。
模拟有若干银行账户存取
public void transfer(int from, int to, double amount) { if(accounts[from] < amount) return; System.out.println(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()); }
几个线程更新银行账户余额,错误就会不知不觉出现。假设两个线程同时执行指令
accounts[to] += amount;
问题在于这不是原子操作,指令可能被处理为:
1.将accounts[to]加载到寄存器 2.增加amount 3.将结果写回accounts[to]。
如果第一个线程执行完步骤1和2,然后被剥夺运行期,线程2被唤醒并修改了accounts数组中的同一项,然后第一个线程被唤醒完成其第3步,这一动作将擦去线程2所作的更新。
原子操作:原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束。
用ReentrantLock保护代码块
public void transfer(int from, int to, double amount) { bankLock.lock(); try{ if(accounts[from] < amount) return; System.out.println(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()); } finally { bankLock.unlock(); } }
条件对象
线程进入临界区,发现在某一条件满足之后才能执行,要使用一个条件对象来管理那些已经获得一个锁但是不能做有工作的线程
如if(accounts[from] < amount) bank.transfer(from, to, amount);线程有可能在成功完成测试,调用transfer方法之前被中断。此时可以采用条件对象:
public void transfer(int from, int to, double amount) throws InterruptedException { bankLock.lock(); try{ //if(accounts[from] < amount) return; while (accounts[from] < amount) sufficientFunds.await(); System.out.println(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(); } }
调用await方法后,当前线程被阻塞并放弃了锁,它进入该条件的等待集。
当锁可用时,该线程不能马上解除阻塞,直到另一个线程调用同一条件上的signalAll方法为止。
当线程从等待集移出时,再次成为可运行,调度器将再次激活它们,它们会重新尝试获得与之绑定的锁,一旦锁成为可用,它们中的某个将从await调用返回,获得该锁并从阻塞的地方继续执行。
synchronized关键字
Java中的每一个对象都有一个内部锁,如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。
静态方法声明为synchronized,调用这种方法,该方法获得相关的类对象的内部锁。如Bank类有一个静态同步的方法,当该方法调用时,Bank.class对象的锁被锁住,没有其他线程可以调用同一个类的这个或任何其他的同步静态方法。