多线程编程资源争用有关问题
一 基本概念
(1)执行任务有先后顺序时,称为同步(Synchronized),任务部分先后一同执行称为异步。
(2)实现实现多线程编程的两种方法:
A.继承类Thread(所属的jar包 java.lang.Thread)。重写run方法。简单例子
Publicclass ThreadA extends Thread{
Publicvoid run(){//重写方法
Sleep();//直接调用因为继承自父类 父类中含有这个方法
}
Publicvoid main(String []ars)
{
ThreadA thread=new Thread();
Thread.start();//这里直接调用方法即可
}
}
B.实现接口 Runnable,重写run方法。简单例子:
Public class ThreadB implements Runnable{
Publicvoid run(){//重写方法
Thread.sleep();// 如果需要调用sleep()方法的话,需要Thread静态方法。因为这里只是实现了接口
}
Publicvoid main(String []ars)
{
ThreadB threadB=new ThreadB();
(newThreadB(threadB)).start();//这里使用了装饰者模式注意两种方法启动线程的区别因为只是实现了接口,并没有实现 start方法。
}
}
(3) 线程概念 以及五个状态
一般操作系统都支持同时运行多个任务,每个任务就是一个程序。系统级别的任务称之为进程,一个程序又可以同时执行多个任务。程序级别的任务称之为线程。
线程与进程的具体区别:
http://www.cnblogs.com/lmule/archive/2010/08/18/1802774.html
进程是并发的,OS 将时间划分为若干个时间片,在每个时间片段中执行不
同的任务。OS 尽可能均匀地将时间片分给每个任务,所以在微观上,所有的程序都是走走停停,在宏观上所有程序都在同时运行,这种现象叫做并发。
线程的五个状态:new,runnable,running, block ,dead.
Thread.yeald() 方法会主动让出此CPU 时间,主动从running 状态回到runnable。但让出的时间不可控。
java中,每个线程都需经历新生(new)、就绪(runnable)、运行(running)、阻塞(block)和死亡(dead)五种状态,线程从新生到死亡的状态变化称为生命周期。 用new运算符和Thread类或其子类建立一个线程对象后,该线程就处于新生状态。 新生(new)--->就绪(runnable):通过调用start()方法 就绪(runnable)--->运行(running):处于就绪状态的线程一旦得到CPU,就进入运行状态并自动调用自己的run()方法 运行--->阻塞:处于运行状态的线程,执行sleep()方法,或等待I/O设备资源,让出CPU并暂时中止自己运行,进入阻塞状态 阻塞--->就绪:睡眠时间已到,或等待的I/O设备空闲下来,线程便进入就绪状态,重新到就绪队列中等待CPU。当再次获得CPU时,便从原来中止位置开始继续运行。 运行--->死亡:(1)(正常情况下)线程任务完成 (2)(非正常状况)线程被强制性的中止,如通过执行stop()或destroy()方法来终止一个线程
(4)wait sleep notify方法以及synchronized()进程块
4.1 wait与sleep
Sleep 是线程类(Thread)的方法(调用sleep方法由运行状态进入阻塞状态),它使当前线程在指定时间内,将执行机会给其他线程,但不会释放对象锁。wait 是Object 类的方法,3,此对象调用wait(方调用后会立即释放对象锁,线程挂起也就是说wait下面的程序都不再执行,当其他线程对此对象调用notify后才会执行) 方法导致本线程释放对象锁,只有针对此对象发出notify(或notifyAll)方法后本线程才再次试图获得对象锁并进入运行状态。需要注意notify后不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。
二 实际案例分析
2.1案例源码
package KFC;
public class Ham {
static Objectbox=new Object();//监控对象
static int totalmaterial=10;//可用的材料
static int sales=0;//销售的个数
static int production=5;//总共生产了多少个
}
package KFC;
public class Hassistant implements Runnable {
public void sell() {
/*注意 这里必须是先加锁 然后再判断。如果是先判断,后加锁,则可能出现异步(和Hmaker同时执行的情况),会导致这里的判断结果不准确。以后在写程序时也应当注意这点,都是先加锁再进行判断。
下例是错误的,现进行说明:
1. 先判断了Ham.production ==Ham.sales 是否成立,假设成立
2. 这时如果Hmaker也执行,production++,改变了production的数量,注意这时还有汉堡。但是此时Hassistant方法仍然执行了synchronized (Ham.box)操作,告诉顾客已经没汉堡了,导致与实际情况相矛盾。
if (Ham.production ==Ham.sales) {
synchronized (Ham.box) {
System.out.println("营业员-" +this.getClass().getName()
+ ":顾客朋友,汉堡没了,请稍等。。");
try {
Ham.box.wait();//此时整个线程会挂起 if语句外面的也不在执行
System.out.println("-----线程挂起-------");
} catch (Exception e) {
e.printStackTrace();
}
}
}
*/
synchronized (Ham.box) {
if (Ham.production ==Ham.sales) {
System.out.println("营业员-" +this.getClass().getName()
+ ":顾客朋友,汉堡没了,请稍等。。");
try {
Ham.box.wait();//此时整个线程会挂起 if语句外面的也不在执行
System.out.println("-----线程挂起-------");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Ham.sales++;
System.out.println("营业员-:顾客朋友汉堡来了,总共卖了:" + (Ham.sales) + "个,还剩"
+ (Ham.production - Ham.sales) +"个,总生产了" + Ham.production
+ "个");
}
@Override
public void run() {
while (Ham.production <= Ham.totalmaterial) {
try {
sell();
Thread.sleep(1000);// 1s卖一个
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package KFC;
import java.lang.*;
public class Hmaker implements Runnable {//厨师
public void make() {
synchronized (Ham.box)//对资源进行加锁
{
(Ham.production)++;//汉堡数量增加
System.out.println("厨师-" +this.getClass().getName()
+" 总共卖了:" + (Ham.sales)
+ "个,还剩"+(Ham.production-Ham.sales)+"个,总生产了"+Ham.production+"个");
Ham.box.notify();//资源解锁
System.out.println("--资源解锁了---");
}
}
@Override
public void run() {
while (Ham.production <= Ham.totalmaterial)//还有材料
{
try {
Thread.sleep(3000);// 3s做一个汉堡
} catch (Exception e) {
e.printStackTrace();
}
this.make();
}
}
}
package KFC;
public class ThreadTest {
public static void main(String []args)
{
Hmaker maker=new Hmaker();
Hassistantassistant=newHassistant();
new Thread(assistant).start();
new Thread(maker).start();
}
}
2.2源码分析
代码执行过程如下:
时间 |
Hmaker |
Hassistant |
production |
sales |
left |
1s |
|
销售1个 |
5 |
1 |
4 |
2s |
|
销售1个 |
5 |
2 |
3 |
3s |
|
销售1个 |
5 |
3 |
2 |
4s |
生产1个,此时虽然执行了notify方法,没有线程处在wait()对象锁的线程 |
销售1个 |
6 |
4 |
2 |
5s |
|
销售1个 |
6 |
5 |
1 |
6s |
|
销售1个 |
6 |
6 |
0 |
7s |
|
此时,由于盒子里已经没有汉堡,因此执行wait()方法,释放资源锁,线程挂起进入block状态 |
|
|
|
8s |
生产了一个,执行notify方法,此时,处在wait()对象锁状态的只有Hassistant线程一个。 |
由block状态先进入runnable状态,获取cpu时间,进入running状态,销售一个 |
7 |
7 |
0 |
9s |
|
此时,由于盒子里已经没有汉堡,因此执行wait()方法,释放资源锁,线程挂起进入block状态 |
|
|
|
10s |
… |
… |
…. |
… |
… |
11s |
|
|
|
|
|
12s |
|
|
|
|
|
13s |
|
|
|
|
|
14s |
|
|
|
|
|
15s |
|
|
|
|
|
16s |
|
|
|
|
|
17s |
|
|
|
|
|
18s |
|
|
|
|
|
19s |
|
|
|
|
|
20s |
|
|
|
|
|
21s |
|
|
|
|
|
22s |
|
|
|
|
|
23s |
|
|
|
|
|