原文:http://wangqiang6028.iteye.com/blog/1887342
1、首先我们先来了解一下进程、线程、并发执行的概念:
进程是指:一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程是指:进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
一般来说,当运行一个应用程序的时候,就启动了一个进程,当然有些会启动多个进程。启动进程的时候,操作系统会为进程分配资源,其中最主要的资源是内存空间,因为程序是在内存中运行的。
在进程中,有些程序流程块是可以乱序执行的,并且这个代码块可以同时被多次执行。实际上,这样的代码块就是线程体。线程是进程中乱序执行的代码流程。当多个线程同时运行的时候,这样的执行模式成为并发执行。
线程的状态
1、线程共有下面4种状态:
新建状态(New):新创建了一个线程对象,当你用new创建一个线程时,该线程尚未运行。
就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
a. 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
b. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM把该线程放入锁。
c. 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):
a. 由于run方法的正常退出而自然死亡;
b. 没有捕获到的异常事件终止了run方法的执行,从而导致线程突然死亡
2、若要确定某个线程当前是否活着,可以使用 isAlive 方法。
如果该线程是可运行线程或者被中断线程,那么该方法返回true;如果该线程仍然是个新建线程,或者该线程是个死线程,那么该方法返回false
3、注意:你无法确定一个活线程究竟是处于可运行状态还是被中断状态,也无法确定一个可运行线程是否正处在运行之中。另外,你也无法对尚未成为可运行的线程与已经死掉的线程进行区分。
4、线程必须退出中断状态,并且返回到可运行状态,方法是使用与进入中断状态相反的过程:
a. 如果线程已经处于睡眠状态,就必须经过规定的毫秒数
b. 如果线程正在等待输入或输出操作完成,那么必须等待该操作完成
c. 如果线程调用了wait方法,那么另外一个线程必须调用notifyAll或者notify方法
d. 如果线程正在等待另一个线程拥有的对象锁,那么另一个线程必须放弃该锁的所有权
2、了解完多线程的相关知识,下面来介绍一下在java中多线程的实现方式
JAVA多线程实现方式
JAVA多线程实现方式主要有以下三种:
1、继承Thread类
2、实现Runnable接口
3、使用ExecutorService、Callable、Future实现有返回结果的多线程。
其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。其中最常用的也是前两种实现方式。
3、线程状态的转换实例
sleep方法与wait方法的区别:
sleep方法是静态方法,wait方法是非静态方法。
sleep方法在时间到后会自己“醒来”,但wait不能,必须由其它线程通过notify(All)方法让它“醒来”。
sleep方法通常用在不需要等待资源情况下的阻塞,像等待线程、数据库连接的情况一般用wait。
sleep/wait与yeld方法的区别:
调用sleep或wait方法后,线程即进入block状态,而调用yeld方法后,线程进入runnable状态。
wait与join方法的区别:
wait方法体现了线程之间的互斥关系,而join方法体现了线程之间的同步关系。
wait方法必须由其它线程来解锁,而join方法不需要,只要被等待线程执行完毕,当前线程自动变为就绪。
join方法的一个用途就是让子线程在完成业务逻辑执行之前,主线程一直等待直到所有子线程执行完毕。
4、线程的同步问题
在实际应用中,我们通常会遇到多线程安全问题。多线程安全问题:当多条语句在操作同一线程共享数据是,一个线程对多条语句只执行了一部分,还没有执行完, 此时另一个线程参与进来执行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
Java 对于多线程的安全提供了专业的解决方式。
线程的同步是保证多线程安全访问竞争资源的一种手段,对于同步,在具体的Java代码中需要完成一下两个操作:
把竞争访问的资源标识为private;
同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。
synchronized(对象){
代码块
...
}
同步的前提:
1、必须要有两个或者两个以上的线程运行;
2、必须是多个线程使用同一个锁;
好处:解决了多线程的安全问题;
弊端:多个线程需要判断锁,较为消耗资源;
注意: 非静态同步函数的对象锁为this,静态同步函数所使用的锁是该方法所在类的字节码文件对象,即类名.class,静态方法里的同步锁都是使用的是类的字节码对象。
//静态同步函数锁
public static synchronized void show(){
ticket++;
System.out.println(Thread.currentThread().getName()+"runtime..."+ticket--);
}
对于同步,除了同步方法外,还可以使用同步代码块,有时候同步代码块会带来比同步方法更好的效果。
追其同步的根本的目的,是控制竞争资源的正确的访问,因此只要在访问竞争资源的时候保证同一时刻只能一个线程访问即可,因此Java引入了同步代码快的策略,以提高性能。
用到线程的同步,随之可能会带来死锁问题。
导致死锁的原因:两个线程互相等待竞争资源,导致两边都无法得到资源,而使自己无法运行。
5、最后我再说说:生产者消费者的问题
对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的。
实际上,准确说应该是“生产者-消费者-仓储”模型,离开了仓储,生产者消费者模型就显得没有说服力了。
对于此模型,应该明确一下几点:
1、生产者仅仅在仓储未满时候生产,仓满则停止生产;
2、消费者仅仅在仓储有产品时候才能消费,仓空则等待;
3、当消费者发现仓储没产品可消费时候会通知生产者生产;
4、生产者在生产出可消费产品时候,应该通知等待的消费者去消费。
此模型将要结合java.lang.Object的wait与notify、notifyAll方法来实现以上的需求。这是非常重要的。
package thread;
/**
* Java线程:生产者消费者模型
* @author Chu 2013-06-15 05:32:29
*/
public class ProductTest {
public static void main(String[] args) {
Godown godown = new Godown(20);
Consumer c1 = new Consumer(80, godown);
Consumer c2 = new Consumer(30, godown);
Consumer c3 = new Consumer(20, godown);
Producer p1 = new Producer(5, godown);
Producer p2 = new Producer(5, godown);
Producer p3 = new Producer(5, godown);
Producer p4 = new Producer(10, godown);
Producer p5 = new Producer(20, godown);
Producer p6 = new Producer(35, godown);
Producer p7 = new Producer(50, godown);
c1.start();
c2.start();
c3.start();
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
p6.start();
p7.start();
}
}
/** 仓库 */
class Godown {
public static final int max_size = 100; //最大库存量
public int curnum; //当前库存量
Godown() {
}
Godown(int curnum) {
this.curnum = curnum;
}
/**
* 生产指定数量的产品
* @param neednum
*/
public synchronized void produce(int neednum) {
//测试是否需要生产
while (neednum + curnum > max_size) {
System.out.println("要生产的产品数量" + neednum + "超过剩余库存量" + (max_size - curnum) + ",暂时不能执行生产任务!");
try {
//当前的生产线程等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//满足生产条件,则进行生产,这里简单的更改当前库存量
curnum += neednum;
System.out.println("已经生产了" + neednum + "个产品,现仓储量为" + curnum);
//唤醒在此对象监视器上等待的所有线程
notifyAll();
}
/**
* 消费指定数量的产品
* @param neednum
*/
public synchronized void consume(int neednum) {
//测试是否可消费
while (curnum < neednum) {
try {
//当前的生产线程等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//满足消费条件,则进行消费,这里简单的更改当前库存量
curnum -= neednum;
System.out.println("已经消费了" + neednum + "个产品,现仓储量为" + curnum);
//唤醒在此对象监视器上等待的所有线程
notifyAll();
}
}
/** 生产者 */
class Producer extends Thread {
//生产产品的数量
private int neednum;
//仓库
private Godown godown;
Producer(int neednum, Godown godown) {
this.neednum = neednum;
this.godown = godown;
}
public void run() {
//生产指定数量的产品
godown.produce(neednum);
}
}
/** 消费者 */
class Consumer extends Thread {
//生产产品的数量
private int neednum;
//仓库
private Godown godown;
Consumer(int neednum, Godown godown) {
this.neednum = neednum;
this.godown = godown;
}
public void run() {
//消费指定数量的产品
godown.consume(neednum);
}
}
运行结果:
已经消费了20个产品,现仓储量为0
已经生产了5个产品,现仓储量为5
已经生产了5个产品,现仓储量为10
已经生产了5个产品,现仓储量为15
已经生产了20个产品,现仓储量为35
已经生产了50个产品,现仓储量为85
已经消费了80个产品,现仓储量为5
已经生产了10个产品,现仓储量为15
已经生产了35个产品,现仓储量为50
已经消费了30个产品,现仓储量为20
说明:
对于本例,要说明的是当发现不能满足生产或者消费条件的时候,调用对象的wait方法,wait方法的作用是释放当前线程的所获得的锁,并调用对象的notifyAll() 方法,通知(唤醒)该对象上其他等待线程,使得其继续执行。这样,整个生产者、消费者线程得以正确的协作执行。
notifyAll() 方法,起到的是一个通知作用,不释放锁,也不获取锁。只是告诉该对象上等待的线程可以竞争执行了。
以上这个例子仅仅是生产者消费者模型中最简单的一种表示,在这个例子中,如果消费者消费的仓储量达不到满足,而又没有生产者,则程序会一直处于等待状态,这当然是不对的。实际上可以将此例进行修改,修改为,根据消费驱动生产,同时生产兼顾仓库,如果仓不满就生产,并对每次最大消费量做个限制,这样就不存在此问题了,当然这样的例子更复杂,更难以说明这样一个简单模型。