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. 阿里云日志api创建logStore

    , shardCount =  });                 string date = FormatRfc822Date(time);                 string con ...

  2. curl命令

    定位后端接口是否ok,经常使用到curl -b/cookie  <name=string/file> cookie字符串或文件读取位置 curl http://localhost --co ...

  3. Codevs 1910递归函数

    1910 递归函数  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题目描述 Description 对于一个递归函数w(a, b, c). 如果a <= ...

  4. PCL 库安装

    参考资料: http://www.cnblogs.com/newpanderking/articles/4022322.html VS2010+PCL配置 PCL共有两种安装方式 安全安装版,个人配置 ...

  5. centos6-honeyd安装&配置

    安装 需要装 libpcap libevent libdnet 等(!) 有些用的yum,有些下载的安装包手动安装 (wget tar configure make install 非常linux) ...

  6. 项目分享一:在项目中使用 IScroll 所碰到的那些坑

    最近做了个 WEB APP 项目,用到了大名鼎鼎的 IScroll,滚动的效果的确很赞,但是坑也是特别多,下面总结一下,希望自后来者有帮助. 该项目现已开源在 github 上,https://git ...

  7. Linux epoll 笔记(高并发事件处理机制)

    wiki: Epoll优点: Epoll工作流程: Epoll实现机制: epollevent; Epoll源码分析: Epoll接口: epoll_create; epoll_ctl; epoll_ ...

  8. OpenFlow

    What is OpenFlow? OpenFlow is an open standard that enables researchers to run experimental protocol ...

  9. 十天冲刺---Day1

    站立式会议 由于第一天冲刺,所以有些没有昨天完成项和遇到的问题. 站立式会议内容总结: git上Issues内容: 燃尽图(做错了,将每天的燃尽图误以为是每天添加任务然后到一天结束后生成燃尽图(?)) ...

  10. SpringMVC学习--拦截器

    简介 Spring Web MVC 的处理器拦截器类似于Servlet 开发中的过滤器Filter,用于对处理器进行预处理和后处理. 拦截器定义 定义拦截器,实现HandlerInterceptor接 ...