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 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(nullfalse,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:

  抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e, time, unit)
移除 remove() poll() take() poll(time, unit)
检查 element() peek() 不可用 不可用

BlockingQueue 不接受 null 元素。试图 addputoffer 一个 null 元素时,某些实现会抛出 NullPointerExceptionnull 被用作指示 poll 操作失败的警戒值。

有已知实现类:
ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue

Java并发和多线程(一)基础知识的更多相关文章

  1. Java并发编程笔记—摘抄—基础知识

    什么是线程安全 当多个线程访问某个类时,不管运行环境采用何种调度方式或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的. 竞态 ...

  2. JAVA面试题集之基础知识

                           JAVA面试题集之基础知识 基础知识:  1.C 或Java中的异常处理机制的简单原理和应用. 当JAVA程序违反了JAVA的语义规则时,JAVA虚拟机就 ...

  3. Java中实现异常处理的基础知识

    Java中实现异常处理的基础知识 异常 (Exception):发生于程序执行期间,表明出现了一个非法的运行状况.许多JDK中的方法在检测到非法情况时,都会抛出一个异常对象. 例如:数组越界和被0除. ...

  4. 总结了零基础学习Java编程语言的几个基础知识要点

    很多Java编程初学者在刚接触Java语言程序的时候,不知道该学习掌握哪些必要的基础知识.本文总结了零基础学习Java编程语言的几个基础知识要点. 1先了解什么是Java的四个方面   初学者先弄清这 ...

  5. java Reflection(反射)基础知识讲解

    原文链接:小ben马的java Reflection(反射)基础知识讲解 1.获取Class对象的方式 1.1)使用 "Class#forName" public static C ...

  6. Java多线程原理+基础知识(超级超级详细)+(并发与并行)+(进程与线程)1

    Java多线程 我们先来了解两个概念!!!! 1.什么是并发与并行 2.什么是进程与线程 1.什么是并发与并行 1.1并行:两个事情在同一时刻发生 1.2并发:两个事情在同一时间段内发生 并发与并行的 ...

  7. Java多线程通关——基础知识挑战

    等掌握了基础知识之后,才有资格说基础知识没用这样的话.否则就老老实实的开始吧.     对象的监视器 每一个Java对象都有一个监视器.并且规定,每个对象的监视器每次只能被一个线程拥有,只有拥有它的线 ...

  8. Java多线程一些基础知识

    最近复习了一些多线程方面的基础知识,做一下总结,多以自己的理解来文字叙述,如果有漏点或者理解错的地方,欢迎各位大佬多多指出: ps:线程分为用户线程和守护线程,当程序中的所有的用户线程都执行完了之后, ...

  9. Java并发和多线程(二)Executor框架

    Executor框架 1.Task?Thread? 很多人在学习多线程这部分知识的时候,容易搞混两个概念:任务(task)和线程(thread). 并发编程可以使我们的程序可以划分为多个分离的.独立运 ...

随机推荐

  1. Python的高级特性2:列表推导式,生成器与迭代器

    一.列表推导式 1.列表推导式是颇具python风格的一种写法.这种写法除了高效,也更简短. In [23]: {i:el for i,el in enumerate(["one" ...

  2. 关于Xcode7.2版本访问相册问题

    前些天自己将xcode升级到7.2版本后,在我写的代码中有需要访问到相册的功能,但是却发现被告知无权限 一开始以为是手机问题,在设置里面找也没有找到,然后看代码,但是代码也没问题,后来才发现是我的pl ...

  3. jquery选择器空格与大于号、加号与波浪号的区别

    空格:$('parent childchild')表示获取parent下的所有的childchild节点,所有的子孙. 大于号:$('parent > child')表示获取parent下的所有 ...

  4. Jboss EAP:native management API学习

    上一节已经学习了CLI命令行来控制JBOSS,如果想在程序中以编码方式来控制JBOSS,可以参考下面的代码,实际上在前面的文章,用代码控制Jboss上的DataSource,已经有所接触了,API与C ...

  5. Java并发编程实战(使用synchronized实现同步方法)

    本文介绍java最基本的同步方式,即使用synchronized关键字来控制一个方法的并发访问,如果一个对象已用synchronized关键字声明,那么只有一个执行线程允许去访问它,其它试图访问这个对 ...

  6. 跟我学习Storm_Storm基本架构

    Storm集群类似于一个Hadoop集群. 然而你在Hadoop的运行“MapReduce job”,在Storm上你运行 “topologies”. “job”和“topologies”本身有很大的 ...

  7. windows7下启动mysql服务出现服务名无效

    出现提示: WIN 7 cmd命令行下,net start mysql,出现 服务名无效提示: 问题原因: mysql服务没有安装. 解决办法: 在 mysql bin目录下 以管理员的权限 执行 m ...

  8. Webwork 学习之路【03】核心类 ServletDispatcher 的初始化

    1. Webwork 与 Xwork 搭建环境需要的的jar 为:webwork-core-1.0.jar,xwork-1.0.jar,搭建webwork 需要xwork 的jar呢?原因是这样的,W ...

  9. 更好的逐帧动画函数 — requestAnimationFrame 简介

    本文将会简单讲讲 requestAnimationFrame 函数的用法,与 setTimeout/setInterval 的区别和联系,以及当标签页隐藏时 requestAnimationFrame ...

  10. 仿造slither.io第二步:加个地图,加点吃的

    前言 上一篇博文讲了如何造一条蛇,现在蛇有了,要让它自由的活动起来,就得有个地图啊,而且只能走也不行呀,还得有点吃的,所以还得加点食物,这一篇博文就来讲讲如何添加地图和食物. 预览效果 当前项目最新效 ...