java中的线程

来源:互联网 发布:照片素描软件 编辑:程序博客网 时间:2024/06/05 21:57

线程同步问题:

在运行多线程程序时,有可能在运行多次之后出现不合理的结果,例如银行提款系统。若夫妻两人用同一个账户在银行存了5000元,某一天丈夫和妻子都去取钱,而且双方不知道,丈夫用银行卡从ATM机取,妻子用存折在银行柜台处取,两人同时取现的情况下,可能会出现两人取现总金额大于5000的情况(当然只是例子,银行不可能会出现这种情况)。

这种情况出现的原因就是没有处理线程同步的问题。

package Thread;

public class Account {

public int count;

public Account(int count){

this.count =count;

}

public int getCash(int cash){if(cash>count){

//System.out.println("余额不够,请重新取现");

return -1;

}

//模拟柜台人员或者ATM机数钱过程

try {

Thread.sleep(1000);

} catch (InterruptedExceptione) {

e.printStackTrace();

}

count =count-cash;

return count;

}

}

例如上面这段代码,写的是一个账户类,代表着夫妻二人的账户,cash表示账户余额,getCash()方法表示从账户中提取现金。若提取金额合理(提取金额少于账户余额),则返回现在的账户余额,若金额不合理,则返回-1,表示提取失败。

package Thread;

public class Peopleextends Thread{

public String type;

public Account account;

public int cash;

public People(Accountaccount,String type,int cash){

this.account=account;

this.cash=cash;

this.type=type;

}

public void run(){

excute();

}

public void excute(){

int result =account.getCash(cash);

if(result<0){

System.out.println("银行余额不足。取现方式为"+type+",取现失败,取现额度为:"+cash);

}

else{

System.out.println("取现方式为"+type+",取现成功,取现额度为:"+cash+",银行余额为:"+account.count);

}

}

}

上面代码表示个人类并继承Thread类,成为一个线程,并对其重写run()方法。type表示取款方式(暂定ATM和柜台取款),account表示此类所拥有的账户,cash表示此类所要提取的现金。Excute()方法根据账户类的getCash()方法返回的值判断是否取现成功。若成功则打印取现方式为:___,取现成功,取现额度为:___,银行余额为:___,反之,则打印不成功并输出账户余额。

 

package Thread;

public class Test {

public static void main(String[]args){

Account account = new Account(5000);

People people1 = new People(account,"ATM",4000);

People people2 = new People(account,"存折",3000);

people1.start();

people2.start();

}

}

上面代码表示程序的入口。建立一个账户类account,存入5000元,并新建两个个人类people1people2。两人共用一个账户。启动两个个人类线程,在运行若干次后可发现,程序运行出了违反逻辑的结果:

银行余额不足。取现方式为ATM,取现失败,取现额度为:4000

取现方式为存折,取现成功,取现额度为:3000,银行余额为:-2000

取现方式为存折,取现成功,取现额度为:3000,银行余额为:2000

取现方式为ATM,取现成功,取现额度为:4000,银行余额为:2000

不管哪种结果,显然都不是我们想要的,这种结果的出现就是没有处理线程同步的结果。若要得到正常结果,只要在出现线程同步的地方加上synchronizedsynchronized有两种用法,一种是加在方法的方法名称上,例如public synchronized int getCash(int cash){}

还有一种就是加在方法体中

public synchronized int getCash(int cash){

synchronized(this){

if(cash>count){

//System.out.println("余额不够,请重新取现");

return -1;

}

//模拟柜台人员或者ATM机数钱过程

try {

Thread.sleep(1000);

} catch (InterruptedExceptione) {

e.printStackTrace();

}

}

count =count-cash;

return count;

}

synchronized后面的括号中表示的是发生线程同步的对象。因为Account类中的count属性被两个People类共用。

 

 

接下来体验下wait()notify()方法。

java中,每个对象都有从Object父类继承而来的两个关于线程间通讯的方法wait()notify(),如其方法名所示,一个是等待,一个是通知,当在一个对象上调用wait()方法时,当前线程就会进行wait状态,直到收到另一个对象的notify()发出通知,才会执行下一步计算。

注意:在javawaitnotify必须在同步锁之内使用。

  同步锁锁定对象和waitnotify对象必须同一个。

  当对象wait挂起状态时候是会释放同步锁的。

 

首先,写一个消费者和生产者之间关系的代码。新建一个ArrayList类当做仓库,产品为Phone类,消费者Customer和生产者Producter。若仓库里有手机,则被消费者消费,若无,则生产者生产一部手机放入仓库。也就是说仓库中最多只能有一部手机。

package com.huaxin.Customer;

 

public class Phone {

public String name;

public Phone(String name){

this.name=name;

}

 

}

 

 

Phone类,。只有一个name属性,由构造方法传入

 

 

package com.huaxin.Customer;

 

import java.util.ArrayList;

 

public class Customerextends Thread{

public ArrayList<Phone>list;

public Customer(ArrayList<Phone>list){

this.list =list;

}

public void run(){

while(true){

synchronized (list) {

//检查容器中是否还有产品,若没有产品,则等待并继续检测

if(list.size()==0){

try {

list.wait();

} catch (InterruptedExceptione) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

//否则从容器中拿出一个产品

else{

Phone phone = list.remove(0);

System.out.println("消费者消费了一个手机,型号是:"+phone.name);

list.notify();

}

}

}

}

}

 

Customer类继承Thread,有一个代表仓库的ArrayList属性,由构造方法传入。然后重写线程的run()方法,在run()方法中对仓库的库存进行判断,在这里用synchronized (list)处理线程同步(产生原因是list被消费者和生产者共用)。利用while(true)判断,若库存为1,则消费一部Phone,并立即告诉生产者Producter(使用notify()方法)。若库存为0,则等待(使用wait()方法)。

package com.huaxin.Customer;

 

import java.util.ArrayList;

 

public class Producterextends Thread{

public ArrayList<Phone>list;

public Producter(ArrayList<Phone>list) {

this.list=list;

}

@Override

public void run() {

int index = 1;

//检测容器list,若有产品则不生产

while(true){

synchronized (list) {

if(list.size()>0){

//有产品,等待产品被消费者消费

try {

list.wait();

} catch (InterruptedExceptione) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

else{

//没有产品,则生产一个

Phone phone = new Phone(index+"号手机");

list.add(phone);

index++;

System.out.println("生产者生产了一个手机,型号是:"+phone.name);

//通知消费者消费产品

list.notify();

}

}

}

}

 

}

Producter类和Customer类一个到理,只是判断条件不同。

 

package com.huaxin.Customer;

 

import java.util.ArrayList;

 

public class Test {

public static void main(String[]args) {

ArrayList<Phone> list = new ArrayList<Phone>();

//生产者线程

Producter pro1 = new Producter(list);

//消费者线程

Customer cus1 = new Customer(list);

Customer cus2 = new Customer(list);

Customer cus3 = new Customer(list);

//启动线程

pro1.start();

cus1.start();

cus2.start();

cus3.start();

}

}

最后程序入口,利用一个生产者三个消费者共四个线程,运行程序得出结果

生产者生产了一个手机,型号是:1号手机

消费者消费了一个手机,型号是:1号手机

生产者生产了一个手机,型号是:2号手机

消费者消费了一个手机,型号是:2号手机

由此得到:在线程wait状态时,线程会一直阻塞我,直到notify的到来,而notify会使线程的wait状态变为就绪状态。

进程的状态分为:新建状态、就绪状态、运行状态、睡眠状态、死亡状态、阻塞状态,其中睡眠状态和阻塞状态执行完毕(睡眠状态的睡眠时间完成,阻塞状态得到notify)之后会变味就绪状态。

 

 

线程池

首先,线程使用时需要创建,运行完之后又会进入死亡状态,再次需要时仍要创建,但线程池的存在使得线程会一直存在,一直等待任务的到来,不会消失,所以使用时只需创建一次,不必创建很多个线程,从而减小了运行内存的占用。

 

package com.huaxin.pool;

 

import java.util.LinkedList;

 

public class MyPool {

//数组存储工人

public Worker[]workers;

//ArrayList存储任务

public LinkedList<MyTask>  list =new LinkedList<MyTask>() ;

//构造方法

public MyPool(int size) {

workers = new Worker[size];

//定义工人

for(int i=0;i<workers.length;i++){

Worker worker =new Worker(i+"号工人",list);

worker.start();

workers[i] =worker;

}

}

//增加任务(线程)

public void getTask(MyTasktask){

synchronized (list){

//增加任务

list.add(task);

//通知给工人

list.notify();

}

}

}

首先,上述代码创建了一个线程池类MyPool,里面有两个容器,数组用来存放Worker类,表示工人;LinkedList用来存放MyTask类,表示任务。getTask()方法表示向LinkedList中增加任务。

 

 

 

package com.huaxin.pool;

 

public class MyTaskimplements Runnable{

public String name;

public MyTask(Stringname){

this.name =name;

}

public void work(){

System.out.println(name+"开始执行");

try {

Thread.sleep(3000);

} catch (InterruptedExceptione) {

e.printStackTrace();

}

System.out.println(name+"任务执行完毕");

}

 

public void run() {

work();

}

}

MyTask类实现了Runnable接口,并重写了其run()方法,表示任务。

 

 

 

package com.huaxin.pool;

import java.util.LinkedList;

public class Workerextends Thread{

public String name;

//队列

public LinkedList<MyTask>list;

public Worker(Stringname, LinkedList<MyTask> list) {

this.name =name;

this.list =list;

}

//重写run方法、

public void run(){

//对任务池进行判断

while(true){

MyTask task;

synchronized (list) {

//任务池为空,则线程进入阻塞状态

if(list.size()==0){

try {

list.wait();

} catch (InterruptedExceptione) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

//else{

//若任务池不为空,则线程进入就绪状态并执行任务

task = list.removeFirst();

//}

}

System.out.println(this.name+"正在执行:"+task.name);

task.work();

System.out.println(this.name+"执行完毕:"+task.name);

}

}

}

Worker类,继承了Thread类,并重写了run方法,在run方法里对LinkedList类的对象list进行判断,若list中没有任务,则工人等待(wait()方法),若list中有任务,则取出来并执行。

 

 

package com.huaxin.pool;

 

public class Test {

public static void main(String[]args) {

//10个工人

MyPool pool = new MyPool(5);

//20个任务

for (int i = 0;i < 10; i++) {

MyTask task = new MyTask(i+"号任务");

pool.getTask(task);

}

}

}

程序入口,再次创建了一个包含5个工人的线程池并新建10个任务供工人们工作。

运行程序得到结果:

1号工人正在执行:1号任务

2号工人正在执行:2号任务

3号工人正在执行:3号任务

0号工人正在执行:0号任务

0号任务开始执行

3号任务开始执行

2号任务开始执行

1号任务开始执行

4号工人正在执行:4号任务

4号任务开始执行

2号任务任务执行完毕

4号任务任务执行完毕

1号任务任务执行完毕

2号工人执行完毕:2号任务

3号任务任务执行完毕

3号工人执行完毕:3号任务

0号任务任务执行完毕

3号工人正在执行:6号任务

6号任务开始执行

2号工人正在执行:5号任务

4号工人执行完毕:4号任务

1号工人执行完毕:1号任务

4号工人正在执行:7号任务

5号任务开始执行

0号工人执行完毕:0号任务

7号任务开始执行

1号工人正在执行:8号任务

0号工人正在执行:9号任务

9号任务开始执行

8号任务开始执行

6号任务任务执行完毕

5号任务任务执行完毕

3号工人执行完毕:6号任务

7号任务任务执行完毕

4号工人执行完毕:7号任务

9号任务任务执行完毕

8号任务任务执行完毕

1号工人执行完毕:8号任务

0号工人执行完毕:9号任务

2号工人执行完毕:5号任务

由结果看,线程池中的线程在运行完分配的任务后并没有死亡,而是进入阻塞状态,若有任务就会进入就绪状态执行任务。

 

 

原创粉丝点击