线程的同步
线程的特点
计算机中一个程序可以启动多个进程,一个进程又可以启动多个线程。进程之间在内存中是相互独立的,它们不能彼此进行访问,已不能修改彼此的数据,那么进程之间怎样做到共享数据呢,一般有两种方法,第一种是通过网络,即进程A把数据发到服务器,然后服务器再将这些数据发给进程B,同样进程B也可以把数据发到服务器,然后服务器再将这些数据发给进程A;第二种方式是进程A把数据存到本地的硬盘文件中,然后进程B再到硬盘里去读这个文件,同样进程B也可以把数据存到本地的硬盘文件中,然后进程A再到硬盘里去读这个文件。但是,进程启动的多个线程中间是可以共享内存里的数据的,也可以修改这些数据。还有进程之间可以并发执行,即同步执行。
线程同步的好处
当一个计算很复杂或是很庞大的时候,我们可以把它分为若干个比较简单的计算模块,然后再把这若干个计算模块交给若干个线程去处理,最后把这些返回的计算结果统计整理。比如我们有统计计算机中所有文件的个数。我们就可以启动多个线程去统计每一个盘符里面的文件个数,然后再把它们全部加起来,就可以得到文件的总个数。
其代码如下:
public class Filecount extends Thread{
private int num;//用来保存文件个数的属性
private String name;//文件的路径
private boolean bl=false;//用来判断文件是否统计完的一个属性
/**
* 重载构造方法,得到要统计的文件名
*/
public Filecount(String name){
this.name=name;
}
//获得统计后的文件个数
public int getNum(){
return num;
}
//一个或得统计状态的方法,true代表已经统计完了,false代表没有统计完
public boolean isFinish(){
return bl;
}
//重写线程的run方法
public void run(){
count(name);
bl=true;
}
/**
* 统计文件的方法
*/
public void count(String name){
// 根据文件路径创建文件对象
java.io.File file=new java.io.File(name);
// 如果表示的文件不存在
if(!file.exists()){
return;
}
// 将该文件夹下的所有文件装在一个文件数组中
java.io.File[] myfs=file.listFiles();
if(myfs==null){
return;
}
// 遍历数组
for(int i=0;i<myfs.length;i++){
java.io.File fs=myfs[i];
// 得到文件的地址
String s=fs.getAbsolutePath();
// 如果是一个标准文件
if(fs.isFile()){
//文件加一
num++;
//如果是文件夹
}else if(fs.isDirectory()){
//递归调用
try{//进行适当的休眠以免cpu被过度占用
Thread.sleep(20);
}catch(Exception ef){
ef.printStackTrace();
}
this.count(s);
}
}
}
}
测试的主函数
public class Counter extends Thread{
private int filenum=0;//用来保存文件个数
public static void main(String[] args) {
Counter count=new Counter();//创建一个统计所有文件个数的对象
count.start(); //启动线程去统计所有线程返回的结果
}
//重载run方法
public void run(){
Filecount E_counter=new Filecount("E:\\");//创建一个统计E盘的对象
E_counter.start();
Filecount F_counter=new Filecount("F:\\");//创建一个统计F盘的对象
F_counter.start();
while(true){
if(E_counter.isFinish()&&F_counter.isFinish()){//不断的判断线程是否已经把所有文件统计完了
break;//跳出循环
}
try{//进行适当的休眠
Thread.sleep(200);
}catch(Exception ex){
ex.printStackTrace();
}
}
filenum=E_counter.getNum()+F_counter.getNum();
System.out.println("标准文件的总个数为:"+filenum);
}
}
如果要进一步的提过效率,我们可以通过对文件进行判断,若是一个很大的文件,我们可以在启动线程去统计。
通过将任务进行分配,有时是可以提过效率,但是并不是把任务分配给线程,就一定能提过效率。像上面统计文件个数的情况,如果我只是简单的分配任务,其实并不能提高效率。而且像上面的情况是,线程什么时候运行结束了,并不知道。所以我们得优化线程的工作机制。
同步线程的影响
假设一下,如果有两个线程同时对一个变量进行操作,会有什么情况。因为同步线程之间的执行并不是同时的,也不一定是按照先后的顺序执行的,这取决与CPU和操作系统等因素。所以当两个或以上的线程同时操作同一块内存时,就有可能会产生影响。这种影响可能会导致一些不可预见的结果。
线程模型的优化
生产/消费者模型的应用,在统计每个盘的文件的时候,因为我们不知道哪个盘的统计已经完成了,所以我们得不停的去询问,也就是不断的去判断它们是否已经全部统计完了,这样做显然很不合理,那么我们能不能改进一下呢,当然是可以的。看到生产和消费是不是有些不理解,凭常识我们就能知道必须先生产出东西来,我才能进行消费,所以只要当我们知道生产这把东西生产出来就可以去买了。在“产品”
没有生产出来之前,我们就必须得在家里等到,知道有人通知我们产品已经生产出来了,我们才去买。这样在中生产/消费者模型有别转会成为等待/通知(wait()/notify())模型了。
下面是实现这种机制的具体代码
public class Filecount extends Thread{
private int num;//用来保存文件个数的属性
private String name;//文件的路径
private Counter count;
/**
* 重载构造方法,得到要统计的文件名
*/
public Filecount(String name,Counter count){
this.name=name;
this.count=count;
}
//获得统计后的文件个数
public int getNum(){
return num;
}
//重写线程的run方法
public void run(){
count(name);//统计文件个数的方法
synchronized(count){//将counter对象锁住
count.notify();// 统计完该盘的文件个数后,就通知它
}
}
/**
* 统计文件的方法
*/
public void count(String name){//统计文件个数的方法
public void count(String name){
// 根据文件路径创建文件对象
java.io.File file=new java.io.File(name);
// 如果表示的文件不存在
if(!file.exists()){
return;
}
// 将该文件夹下的所有文件装在一个文件数组中
java.io.File[] myfs=file.listFiles();
if(myfs==null){
return;
}
// 遍历数组
for(int i=0;i<myfs.length;i++){
java.io.File fs=myfs[i];
// 得到文件的地址
String s=fs.getAbsolutePath();
// 如果是一个标准文件
if(fs.isFile()){
//文件加一
num++;
//如果是文件夹
}else if(fs.isDirectory()){
//递归调用
try{//进行适当的休眠以免cpu被过度占用
Thread.sleep(20);
}catch(Exception ef){
ef.printStackTrace();
}
this.count(s);
}
}
}}
public class Counter extends Thread{
private int filenum=0;//用来保存文件个数
public static void main(String[] args) {
Counter count=new Counter();//创建一个统计所有文件个数的对象
count.start(); //启动线程去统计所有线程返回的结果
}
//重载run方法
public void run(){
Filecount E_counter=new Filecount("E:\\",this);//创建一个统计E盘的对象
E_counter.start();
Filecount F_counter=new Filecount("F:\\",this);//创建一个统计F盘的对象
F_counter.start();
synchronized(this){//将当前这个对象锁住
try {
this.wait();//等待通知第一个统计者的通知
this.wait();//等待通知第二个统计者的通知
} catch (InterruptedException e) {
e.printStackTrace();
}
}
filenum=E_counter.getNum()+F_counter.getNum();
System.out.println("标准文件的总个数为:"+filenum);
}
}
里面使用的wait()有阻塞的作用,用就是说如果等不到通知notify(),wait()就不会执行下去,就可阻塞在wait()里。