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. js函数命名常用动词

    get 获取/set 设置, add 增加/remove 删除 create 创建/destory 移除 start 启动/stop 停止 open 打开/close 关闭, read 读取/writ ...

  2. PL/SQL Block Structure

    [顶]ORACLE PL/SQL编程详解之二: PL/SQL块结构和组成元素(为山九仞,岂一日之功) 继上四篇:ORACLE PL/SQL编程之八:把触发器说透                ORAC ...

  3. 用 C# 轻松读取、改变文件的创建、修改、访问时间

    创建时间是文件存入到电脑中的时间,而修改时间则是改变起内容的最后时间 // 读取文件的创建.修改.访问时间FileInfo fi = new FileInfo("C://test.txt&q ...

  4. 基于JSch的Sftp工具类

    本Sftp工具类的API如下所示. 1)构造方法摘要 Sftp(String host, int port, int timeout, String username, String password ...

  5. grains

    用途 1,匹配客户端 2,配置文件里使用 3,资产管理     定义grains方法1:             方法2:        

  6. 条件变量pthread_cond_t怎么用

    #include <pthread.h> #include <stdio.h> #include <stdlib.h> pthread_mutex_t mutex ...

  7. IE8和W3C标准下IFRAME刷新和URL的区别

    一个页面中包含了一个iframe,我们要刷新这个iframe的情况 url在IE8和W3C以及IE11的区别如下: URL使用相对路径,绝对路径比如http://localhost:5568/替换成I ...

  8. easyui 中Datagrid 控件在列较多且无数据时,列显示不全的解决方案

    在onLoadSuccess 中加入如下代码就OK啦 $('#dg3').datagrid({ onLoadSuccess:function(data){ if(data.total==0){ var ...

  9. 年终福利,PHP7+Apache2.4+MySQL5.6 源码编译安装,环境配置,搭建你自己的LAMP环境

    PHP7 都出来了,你还在玩PHP5吗? MySQL5.6 早都出来了,你还在玩MySql5.2吗? Apache2.4 早都出来了,你还在玩Apache2.2吗? 笔者不才,愿意亲自搭建环境,供搭建 ...

  10. Android开发之ViewPager的简单使用

    ViewPager是V4包中的,如果你的编译器敲不出ViewPager,那么你就需要添加,看下面: 第一步:点击+号 第二步:选择第一个Library 第三步:添加这个包: 然后点击ok-->o ...