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. wk_04

    函数 函数是对程序逻辑进行结构化或过程化的一直编程方法.能将整块代码巧妙的隔离成易于管理的小块,把重复代码放到函数中而不是进行大量的拷贝--这样既能节省空间,也有助于保持一致性,因为你只需要改变单个的 ...

  2. zlog学习笔记(mdc)

    mdc.h #ifndef __zlog_mdc_h #define __zlog_mdc_h #include "zc_defs.h" typedef struct zlog_m ...

  3. ASP.NET点击按钮弹出确认对话框方法

    开发asp.net网页应用程序的时候,有些页面的按钮需要增加一个确认对话框,比如: 实现这个功能比较简单,代码这样写: Button.Attributes["onclick"] = ...

  4. Java 集合系列06之 Vector详细介绍(源码解析)和使用示例

    概要 学完ArrayList和LinkedList之后,我们接着学习Vector.学习方式还是和之前一样,先对Vector有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它.第1部分 Vec ...

  5. [LINK]OpenResty

    http://openresty.org/ http://www.tuicool.com/articles/M3yI3y http://www.oschina.net/question/28_6046 ...

  6. Python-01-基础

    一.安装Python 官方下载地址:https://www.python.org/downloads/ Windows可直接下载安装,安装时勾选自动配置环境变量即可. Linux/OS X默认装有Py ...

  7. warning: #870-D: invalid multibyte character sequence

    warning: #870-D: invalid multibyte character sequence2011-03-12 9:18warning: #870-D: invalid multiby ...

  8. servlet乱码问题总结

    在学习时servlet乱码问题还是挺严重的,总结一下有三种情况 1.新建HTML页面后浏览出现乱码 2.以post形式请求时出现乱码 3.以get形式请求时出现乱码 让我们一个一个来解决吧 1.新建H ...

  9. MySQL5.6 实现主从复制,读写分离,分散单台服务器压力

    闲来无事,在本地搭建几台虚拟机,准备配一个mysql读写分离的主从配置,版本选用最新版的,mysql.5.6.28 版本,本处使用源码安装(鄙人一向喜欢源码安装,因为centos中鄙人不知道yum安装 ...

  10. 必须要会的技能(一) 如何实现设计时Binding

    今天我们来分享一个主题:DesignTime Binding设计时绑定. 这一项技术可以使用在所有包括WPF及其衍生出来的技术上,比如Sliverlight,当然也包括UWP 先来说明一下设计时Bin ...