Java并发和多线程(一)基础知识
1.java线程状态
Java中的线程可以处于下列状态之一:
- NEW: 至今尚未启动的线程处于这种状态。
- RUNNABLE: 正在 Java 虚拟机中执行的线程处于这种状态。
- BLOCKED: 受阻塞并等待某个监视器锁的线程处于这种状态。
- WAITING: 无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
- TIMED_WAITING: 等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
- TERMINATED: 已退出的线程处于这种状态。
在给定时间点上,一个线程只能处于一种状态。这些状态是虚拟机状态,它们并没有反映所有操作系统线程状态。
1.1 New
Thread t = new MyThread(r);后线程处于new状态。
1.2 Runnable
调用start方法后,线程处于Runnable状态。一个Runnable(可运行)的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间。
1.3 Blocked
一个线程试图获取一个内部的对象锁(不是java.util.concurrent中的锁),而该锁被其他线程持有的时候,该对象进入阻塞状态(Blocked)。
1.4 waiting
某一线程因为调用下列方法之一而处于等待状态:
- 不带超时值的 Object.wait
- 不带超时值的 Thread.join
- java.util.concurrent中的Lock或Condition
1.5 timed_waiting
某一线程因为调用以下带有指定正等待时间的方法之一而处于定时等待状态:
- Thread.sleep
- 带有超时值的 Object.wait
- 带有超时值的 Thread.join
- java.util.concurrent中的Lock.tryLock以及Condition.await的计时版
1.6 terminated
线程被终止的两种原因:
- run方法正常退出而自然终止
- 因为一个没有捕获的异常终止了run方法而意外终止
2.同步(一些比较底层的解决方案)
同步有两方面的含义:互斥与内存可见性
- 2.1 锁对象Lock和条件对象Condition
- 2.2synchronized关键字
- 2.3volatile域
- 2.4原子类
- 2.5ThreadLocal
2.1 锁对象Lock和条件对象Condition
Java中为了方便程序员编写并发代码引入了synchronized关键字。为了深刻理解synchronized关键字代表的含义就必须得先知道两个概念:锁对象 和 条件对象。
2.1.1 锁对象
引入锁对象是为了确保任何时候只有一个线程能访问临界区。使用Lock保护代码的基本结构如下:
myLck.lock();
try{
critical section
}
finally{
myLock.unlock();
}
这种结果确保了任何时刻只有一个线程进入临界区。一旦一个线程持有了该锁对象,任何其他线程都无法通过lock语句。当其他其他线程调用lock时,它们被阻塞,直到第一个线程释放锁对象。
使用一个锁来保护Bank类的transfer方法的例子:
public class Bank {
private Lock bankLock = new ReentrantLock();
//... public void transfer(int from, int to,int amount){
bankLock.lock();
try{
System.out.println(Thread.currentThread());
accounts[from] -= amount;
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n",getTotalBalance());
}
finally{
bankLock.unlock();
}
} }
可重入锁?
上述Bank例子中的锁是可重入的。可重入锁可参考这儿。
2.1.2 条件对象
为什么需要条件对象?
上述Bank例子演示了从一个银行账户向另一个银行账户转账的过程。但上述内部转账流程仍需完善:如果转出账户的余额不够的话,应该等待其他账户转给自己后再执行上述转账操作。
public void transfer(int from, int to,int amount){
bankLock.lock();
try{
while (accounts[from] < amount) {
// wait
...
}
// transfer funds
...
}
finally{
bankLock.unlock();
}
}
如果转出账户的余额不够的话,应该等待其他账户转给自己后再执行上述转账操作。但是,当前线程刚刚获得了对bankLock的排他性访问,因此别的线程不可能有执行该transfer方法的机会。怎么办呢?解决方案就是让当前线程释放bankLock锁对象并且等待账户余额满足条件后继续执行。条件对象可以帮我们解决这个问题,这就是为什么我们需要条件对象的原因。
一个锁可以有一个或者多个相关的条件对象。可以使用Lock对象的newCondition()实例方法获取一个条件对象。
调用Condition对象的await()方法,会使得当前线程进入该条件的等待集。
调用Condition对象的signalAll()方法,会使得所有等待在该条件对象上的进程被唤醒。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class Bank {
private Lock bankLock = new ReentrantLock();
private Condition sufficientFunds = bankLock.newCondition();
private final double[] accounts; public Bank(int n,double initialBalance){
accounts = new double[n];
for (int i = 0; i < accounts.length; i++) {
accounts[i] = initialBalance;
}
} public void transfer(int from, int to,int amount) throws InterruptedException{
bankLock.lock();
try{
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();
}
} public double getTotalBalance() {
bankLock.lock();
try{
double sum = 0;
for (double d : accounts) {
sum += d;
}
return sum;
}
finally{
bankLock.unlock();
}
} }
注意:对await的调用应该在如下循环体中:
while(!(ok to proceed))
condition.await();
总结:
- 锁用来保护代码片段,任何时刻只有一个线程执行被保护的代码。
- 锁可以管理试图进入被保护代码段的线程。
- 锁一个拥有一个或者多个相关的条件对象。
- 每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。
2.2 synchronized关键字
从1.0版开始,java中的每个对象都有一个内部锁。如果一个方法用synchronized关键字声明,那么该方法所属对象的锁将保护整个方法。换句话说,要调用该方法,线程必须获得内部的对象锁。
换句话说,
public synchronized void method(){
method body
}
等价于
public void method(){
this.intrinsicLock.lock();
try{
method body
}
finally{
this.intrinsicLock.unlock();
}
}
内部对象锁只有一个相关的条件对象。Object类中的wait方法添加一个线程到等待集中,notify/notifyAll方法解除等待线程的阻塞状态。调用wait或notifyAll等价于
intrinsicLock.await();
intrinsicLock.signalAll();
理解了wait,notify/notifyAll等方法的内部等价形式就很容易明白为什么对wait,notify/notifyAll等方法的调用只能在同步控制方法或者同步控制块里面使用。
面试题:Java中sleep和wait的区别?
- 来源:sleep来自Thread类,和wait来自Object类。
- 锁: 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
- 使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。
synchronized版的Bank类如下:
public class Bank { private final double[] accounts; public synchronized void transfer(int from, int to,int amount) throws InterruptedException{
while (accounts[from] < amount)
wait();
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());
notifyAll();
} public synchronized double getTotalBalance() { ... }
}
2.3volatile域
同步包含两方面:互斥和内存可见性。volatile修饰的Field保证了内存可见性,但不保证互斥(原子性)。
加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。
2.4 原子类
java.util.concurrent.atomic包中有许多类使用了很高效的机器级指令(而不是使用锁)来保证其操作的原子性。
应用程序员不应该使用这些类,它们仅供那些开发并发工具的系统程序员使用。
2.5 ThreadLocal类
有时候可能要避免共享变量,使用ThreadLocal类为各个线程提供各自的实例。
ThreadLocal类包装的字段通常是static变量,否则如果是实例变量则完全没必要。
package think.in.java.chap21.section3; import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; /**
* private ThreadLocal<Integer> value = new ThreadLocal<Integer>();
* 该列子说明:
* 如果去掉static,则每个ThreadLocalVariableHolder对象有一个value域,并且访问该对象的每个线程有一个本地value变量。
*
*/
public class ThreadLocalVariableHolder extends Thread{
private ThreadLocal<Integer> value = new ThreadLocal<Integer>(){
private Random random = new Random(47);
protected synchronized Integer initialValue(){
return random.nextInt(10000);
}
};
public void increment() {
value.set(value.get()+1); }
public int get() {
return value.get();
}
public void run(){
increment();
System.out.println(Thread.currentThread()+" | "+this);
}
public String toString() {
return "["+get()+",value=" + value + "]";
}
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newFixedThreadPool(8);
for (int i = 0; i < 2; i++) {
ThreadLocalVariableHolder t = new ThreadLocalVariableHolder();
for (int j = 0; j < 4; j++) {
exec.execute(t);
}
}
TimeUnit.SECONDS.sleep(3);
exec.shutdownNow();
}
}
3. 阻塞队列BlockingQueue(同步的高层解决方案)
前面介绍了java并发编程的底层构件块,实际编程中应该尽量远离底层结构。多线程中的许多问题都是生产者-消费者模型,可以通过一个或者多个队列将其优雅地形式化。从5.0开始,JDK在java.util.concurrent包里提供了阻塞队列的官方实现。
阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完全清空队列。
BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(null 或 false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:
抛出异常 | 特殊值 | 阻塞 | 超时 | |
插入 | add(e) |
offer(e) |
put(e) |
offer(e, time, unit) |
移除 | remove() |
poll() |
take() |
poll(time, unit) |
检查 | element() |
peek() |
不可用 | 不可用 |
BlockingQueue 不接受 null 元素。试图 add、put 或 offer 一个 null 元素时,某些实现会抛出 NullPointerException。null 被用作指示 poll 操作失败的警戒值。
有已知实现类:
ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue
Java并发和多线程(一)基础知识的更多相关文章
- Java并发编程笔记—摘抄—基础知识
什么是线程安全 当多个线程访问某个类时,不管运行环境采用何种调度方式或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的. 竞态 ...
- JAVA面试题集之基础知识
JAVA面试题集之基础知识 基础知识: 1.C 或Java中的异常处理机制的简单原理和应用. 当JAVA程序违反了JAVA的语义规则时,JAVA虚拟机就 ...
- Java中实现异常处理的基础知识
Java中实现异常处理的基础知识 异常 (Exception):发生于程序执行期间,表明出现了一个非法的运行状况.许多JDK中的方法在检测到非法情况时,都会抛出一个异常对象. 例如:数组越界和被0除. ...
- 总结了零基础学习Java编程语言的几个基础知识要点
很多Java编程初学者在刚接触Java语言程序的时候,不知道该学习掌握哪些必要的基础知识.本文总结了零基础学习Java编程语言的几个基础知识要点. 1先了解什么是Java的四个方面 初学者先弄清这 ...
- java Reflection(反射)基础知识讲解
原文链接:小ben马的java Reflection(反射)基础知识讲解 1.获取Class对象的方式 1.1)使用 "Class#forName" public static C ...
- Java多线程原理+基础知识(超级超级详细)+(并发与并行)+(进程与线程)1
Java多线程 我们先来了解两个概念!!!! 1.什么是并发与并行 2.什么是进程与线程 1.什么是并发与并行 1.1并行:两个事情在同一时刻发生 1.2并发:两个事情在同一时间段内发生 并发与并行的 ...
- Java多线程通关——基础知识挑战
等掌握了基础知识之后,才有资格说基础知识没用这样的话.否则就老老实实的开始吧. 对象的监视器 每一个Java对象都有一个监视器.并且规定,每个对象的监视器每次只能被一个线程拥有,只有拥有它的线 ...
- Java多线程一些基础知识
最近复习了一些多线程方面的基础知识,做一下总结,多以自己的理解来文字叙述,如果有漏点或者理解错的地方,欢迎各位大佬多多指出: ps:线程分为用户线程和守护线程,当程序中的所有的用户线程都执行完了之后, ...
- Java并发和多线程(二)Executor框架
Executor框架 1.Task?Thread? 很多人在学习多线程这部分知识的时候,容易搞混两个概念:任务(task)和线程(thread). 并发编程可以使我们的程序可以划分为多个分离的.独立运 ...
随机推荐
- Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details. ...
- SharePoint Foundation 2013 with SP1
终于支持在 Windows Server 2012 R2 上安装了. 下载 另外,还有一个针对SharePoint Foundation 2013的重要更新.可以在安装SP1之前或之后安装. Micr ...
- SharePoint GroupedItemPicker Control
这个控件SharePoint用来选择Field ,和Content Type, 以下是一个完整的示例. <SharePoint:GroupedItemPicker ID="Select ...
- css3属性书写顺序
今天写了个小demo想要利用transition 和transform以及transition-delay来实现鼠标移上去的延时动画,结果发现不能实现transition的变化效果.调试后发现只有把 ...
- codevs1842 递归第一次
难度等级:白银 1842 递归第一次 题目描述 Description 同学们在做题时常遇到这种函数 f(x)=5 (x>=0) f(x)=f(x+1)+f(x+2)+1 (x<0) 下面 ...
- Java调用C/C++编写的第三方dll动态链接库(zz)
这里主要用的方法是JNI.在网上查资料时看到很多人说用JNI非常的复杂,不仅要看很多的文档,而且要非常熟悉C/C++编程.恐怕有很多人在看到诸如此类的评论时已经决定绕道用其他方法了.本文将做详细的介绍 ...
- HTML5 postMessage 跨域交换数据
前言 之前简单讲解了利用script标签(jsonp)以及iframe标签(window.name.location.hash)来跨域交换数据,今天我们来学习一下HTML5的api,利用postMes ...
- ASP.NET MVC 中应用Windows服务以及Webservice服务开发分布式定时器
ASP.NET MVC 中应用Windows服务以及Webservice服务开发分布式定时器一:闲谈一下:1.现在任务跟踪管理系统已经开发快要结束了,抽一点时间来写一下,想一想自己就有成就感啊!! ...
- node 学习笔记 - Modules 模块加载系统 (1)
本文同步自我的个人博客:http://www.52cik.com/2015/12/11/learn-node-modules-path.html 用了这么久的 require,但却没有系统的学习过 n ...
- ASP.NET MVC 多语言实现技巧 最简、最易维护和最快速开发
说说传统做法的缺点 1.做过多语言的都知道这玩意儿太花时间 2.多语言架构一般使用资源文件.XML或者存储数据库来实现.这样就在一定程序上降低了性能 3.页面的可读性变差,需要和资源文件进行来回切换 ...