在java的线程中经常能看到关于线程安全与非线程安全的说法,以前我认为线程安全是必须的,但在我进行了关于进度条组件的编写时,发现线程安全并不是必需的。也许你会说在每次一个代码段执行完,要改变进度条时,调用一下进度条组件的刷新方法就行了。这样无疑使代码的执行时间大大增长了,代码的效率是我们必须重视的,而且1%的进度,在任务较多,代码执行时间长的情况下人眼是不能分辨的,而在任务比较少的情况下,整个进度条也许都是一闪而逝,更何况1%的进度。所以在这种不需要即时显示数据变化的情况下,线程不安全也是可以的,只有那些对数据的及时性非常看重的才需要注意线程安全。比如商品金额,你要即时的显示当前的金额,不能出现脏数据(无效数据,过时的数据),当金额变化时,其他线程会获取现在的金额数据进行处理,然而如果还没有处理完,金额又变了的话,就会出现脏数据。所以在处理中要禁止对金额数据的操作,即加锁。来保证线程安全。

   在Swing中已经有了进度条组件JProgressBar,我们通过Timer定时器,来定时调用JProgressBar的setValue()方法,来实现对进度条的更新,Timer可以添加ActionListener,到达时间后就会触发actionlistener。。通过添加监听addChangeListener()方法向JProgressBar实例中添加ChangeListener监听实例,当进度条change后就会触发ChangeListener。

  我一开始是想让进度条监听变量(for循环)来实现对进度条的更新,即在我的进度条类中添加一个静态的对线程可见的类变量(static volatile),当调用进度条类的addChangeLisrener ()方法方法后就会启动一个线程循环来根据静态变量的值来改变进度条显示。在主线程中改变进度条类的静态变量就可以了。但是这种办法相对于JProgressBar的实现方法来说,是没有效率的。因为我的进度类在添加监听后就会一直运行线程,一个死循环就不知浪费了不少内存,而如果使用JProgressBar的话只需在主线程使用循环调用setValue()即可,这是可控的线程,相较于进度条中添加监听后的线程,它的每一步都是有用的,而进度条中添加监听后的线程中当进度不变时,它执行的代码都是无用的,也许它运行的十次中只有一次是我们想要的,所以如非必要还是不要用线程来死循环。下面是我的代码:
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Window;
import java.awt.image.BufferedImage;

import javax.swing.JDialog;

public class MyProgressBar extends JDialog{    
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    private int width=400,height=80;
    private BufferedImage img;
    public static volatile double nowsize;
    private Color color=Color.green;
    //构造函数
    public MyProgressBar() {super();this.setSize(width,height+100);}
    public MyProgressBar(Frame f) {super(f);this.setSize(width,height+100);}
    public MyProgressBar(Window w) {super(w);this.setSize(width,height+100);}
    
    public void setWidth(int w) {this.width=w;}
    public int getWidth() {return this.width;}
    public void setHeight(int h) {this.height=h;}
    public int getHeight() {return this.height;}
    public void setImg(Image img) {
        this.img=new BufferedImage(img.getWidth(this),img.getHeight(this), BufferedImage.TYPE_INT_RGB);        
    }
    public BufferedImage getImg() {return this.img;}
    public void setColor(Color c) {this.color=c;}
    public Color getColor() {return this.color;}
    //重写paintComponent()方法,把外框画出来。因为
    @Override
    public void paint(Graphics g) {
        // TODO Auto-generated method stub
        super.paint(g);
        g.setColor(Color.pink);
        g.drawRect(10, 40, 380, 80);
    }
    
    public void addChangeListener() {
         Graphics g=this.getGraphics();
         new Thread(new Runnable() {            
            @Override
            public void run() {
                // TODO Auto-generated method stub
                 while( nowsize<100.0) {    
                     if(img==null) {
                           g.setColor(color);
                           g.fillRect(12, 42,(int)(nowsize*width)-4,height-4);
                       }else {
                           g.drawImage(img, 12, 42,(int)(nowsize*width)-4,height-4,null);
                       }
                   }
            }
        }).start();
        
    }
    
}

继承JDialog类是个错误,因为重写他的paint的方法是没有用的,一旦窗体变化,就会导致绘图失效(调用了它父类的paint()方法)。(绘图不能分开,重绘时将外框也要一起重画,否则外框不全。)


为了避免了使用循环来处理图形变化,在主类只要定时调用进度条的方法来设置进度条的进度(进度条的重绘放在这个方法中)即可,这样还不用静态变量了。所以不要用线程+死循环来监控变量执行代码,没有效率,要使用监听,特别是在自己编写的类中。如果只是要实现简单的进度条类,可以不用添加任何监听。完成的进度条类,继承的是JDialog,要改为继承JPanel.

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Window;
import java.awt.image.BufferedImage;

import javax.swing.JDialog;

public class MyProgressBar extends JDialog{    
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    private int width=400,height=80;
    private BufferedImage img;
    private double progress;
    private Color color=Color.green;
    private int x=11;
    private double old;
    //构造函数
    public MyProgressBar() {super();this.setSize(width+50,height+100);}
    public MyProgressBar(Frame f) {super(f);this.setSize(width+50,height+100);}
    public MyProgressBar(Window w) {super(w);this.setSize(width+50,height+100);}
    
    public void setWidth(int w) {this.width=w;}
    public int getWidth() {return this.width;}
    public void setHeight(int h) {this.height=h;}
    public int getHeight() {return this.height;}
    public void setImg(Image img) {
        this.img=new BufferedImage(img.getWidth(this),img.getHeight(this), BufferedImage.TYPE_INT_RGB);        
    }
    public BufferedImage getImg() {return this.img;}
    public void setColor(Color c) {this.color=c;}
    public Color getColor() {return this.color;}

/*
 * 重写paint()方法,把外框画出来。JDialog是顶级容器.
 * 但在调用它的setVisible()后却不会画出来因为setVisible()调用的是它父类的方法,所以重写的paint()方法不会被调用,只能自己调用,
 * 所以我重写setVisible(),让其在调用父类方法画出窗体后,画出我想要的框架。重写setVisible()方法后,框体缺了一部分???
 * @Override
 *  public void paint(Graphics g) {
 *      // TODO Auto-generated method stub
 *      super.paint(g);  
 *  }
 *  
 *
 * */
    public void setProgress(double d) {
        this.progress=d;  
        
        //g.setColor(Color.blue);
        
        //Math.round()返回最近的long整数,四舍五入,保留三位小数
        //d=Math.round(d*100)/100;
        //每次只画0.1%
        while(old<d) {
            //不加会导致进度条开头缺失一部分
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //向下转型
            Graphics2D g=(Graphics2D)this.getGraphics();
            //设置边框粗细
            g.setStroke(new BasicStroke(2.0f));
            g.setColor(Color.blue);
            g.drawRect(10,50,width+4,height+4);
            g.setColor(color);
            g.fillRect(x,52,(int)(0.01*width),height);
            old+=0.01;
            x=x+(int)(0.01*width);
            System.out.println(old+"\t"+x);
            
        }
    }
    public double getProgress() {return this.progress;}
    
}
简单的自制ProgressBar(可用版),缺点::进度条读条不够流畅。

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

public class MyProgressBar extends JPanel{    
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    private BufferedImage img;
    private double progress;
    private Color color=Color.green;
    private int x=2;
    private double old;
    //构造函数
    public MyProgressBar() {super();//this.setSize(width,height);
    }
    
    public void setImg(Image img) {
        this.img=new BufferedImage(img.getWidth(this),img.getHeight(this), BufferedImage.TYPE_INT_RGB);        
    }
    public BufferedImage getImg() {return this.img;}
    public void setColor(Color c) {this.color=c;}
    public Color getColor() {return this.color;}

    @Override
    public void paintComponent(Graphics g) {
         //TODO Auto-generated method stub
        super.paintComponent(g);
//        Math.round()返回最近的long整数,四舍五入,保留三位小数
//        d=Math.round(d*100)/100;
//        向下转型
        Graphics2D gra=(Graphics2D)g.create();
        gra.setStroke(new BasicStroke(2.0f));
        gra.setColor(Color.black);
        gra.drawRect(1,1,getWidth()-2,getHeight()-2);                   
        gra.setColor(color);
        gra.fillRect(2,2,x-2,getHeight()-4);
        gra.dispose();           
    }
    
    public void setProgress(double d) {
        this.progress=d;                
        paintprogress();
    }
    public double getProgress() {return this.progress;}
    
    public void paintprogress() {
        /*
         * 直接使用继承自paint()方法的graphics只能在画完后显示,但调用的getGraphics()能正常绘画,
         * 画完后结果却会消失。所以执行完该方法后会调用repaint()方法来一次性绘制消失的图形。
         * */
        Graphics2D g=(Graphics2D)this.getGraphics();
        while(old<progress) {                                   
             g.setColor(color);
             g.fillRect(x,2,(int)(0.01*getWidth()),getHeight()-4);                                          
             old+=0.01;
             x+=(int)(0.01*getWidth());            
             try {
                Thread.sleep(50);
                   } catch (InterruptedException e) {
                 e.printStackTrace();
                }    
            }
         g.dispose();
        }
}