スレッドを使えばpaintImmediatelyを使わずに動かすことができます。動いている間もボタンが使えますから、複数の●を同時に動かせますし、green, blue ボタンも生きていますからこのようにいつでもランダムな円を描き加えることができます。

プログラムは決められた順番にしたがって作業をしていきます。この一連の作業の流れをスレッドといいます。
イベントを待ってボタンを押されたらactionPerformed()を実行するというのも一つのスレッドになっています。javaのイベントのスレッドをイベントディスパッチスレッド(EDT)と言います。
このイベントディスパッチスレッド内で時間のかかる仕事を入れてしまうと次のイベントが起こっても対応できません。そこで、時間のかかる仕事をEDTとは別のスレッドでやらせようという作戦です。
今回は円を描いてそれをゆっくり動かす仕事を別のスレッドにさせて、EDTはボタンのクリックを監視する作業にもどります。
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.image.*;
public class MoveDisk2 extends JFrame implements ActionListener{
JButton rbtn;
JButton gbtn;
JButton bbtn;
MyPanel mypnl;
JPanel btnpnl;
BufferedImage buffimg;
Graphics bfg;
//constructor
public MoveDisk2() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
setTitle("MoveDisk");
mypnl = new MyPanel();
rbtn = new JButton("start");
gbtn = new JButton("green");
bbtn = new JButton("blue");
btnpnl = new JPanel();
btnpnl.setLayout(new GridLayout(1,3,0,0));
btnpnl.add(rbtn);
btnpnl.add(gbtn);
btnpnl.add(bbtn);
setLayout(new BorderLayout());
add(mypnl, BorderLayout.CENTER);
add(btnpnl,BorderLayout.SOUTH);
rbtn.addActionListener(this);
gbtn.addActionListener(this);
bbtn.addActionListener(this);
pack();
setVisible(true);
//BufferedImageを作るのはpaintComponentに移した
}
//イベント処理
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == rbtn) {
//mypnl.moveDisk(); //●を描くメソッド
Thread thread = new moveDisk(mypnl,buffimg);
thread.start();
//mypnl.repaint();
}
if (e.getSource() == gbtn) {
mypnl.drawRdm('g');
mypnl.repaint();
}
if (e.getSource() == bbtn) {
mypnl.drawRdm('b');
mypnl.repaint();
}
}
public static void main(String[] args){
MoveDisk2 myframe = new MoveDisk2();
}
//内部クラス
public class MyPanel extends JPanel{
//BufferedImage buffimg;
//Graphics bfg;
Color bc= new Color(255,255,191); //背景の色
Color c = new Color(0,0,0); //楕円の色
boolean firsttime = true;
public MyPanel(){
setBackground(new Color(255,255,191));
setPreferredSize(new Dimension(400,400));
//panel側で大きさを指定する
}
@Override
public void paintComponent(Graphics myg){
super.paintComponent(myg);
if (firsttime){ //1回目の描画でBufferedImageをつくる
buffimg = new BufferedImage(
getSize().width,
getSize().height,
BufferedImage.TYPE_INT_RGB);
bfg = buffimg.createGraphics();
bfg.setColor(bc);
bfg.fillRect(0, 0, getSize().width, getSize().height);
firsttime = false; //次回はやらない
}
myg.drawImage(buffimg, 0, 0
,getSize().width, getSize().height,this);
//getSize().widthはMyPanelのインスタンスの幅
}
//円を動かすメソッド→下記内部クラスに
//ランダムに円を描くメソッド
public void drawRdm(char rgb) {
for(int i=0; 10>i; i++){
nextColor(rgb);
bfg.setColor(c);
int x = (int)(400*Math.random());
int y = (int)(400*Math.random());
int h = (int)(50*Math.random()+5);
bfg.fillOval(x-h/2,y-h/2,h,h);
}
}
//色を変化させるメソッド
public void nextColor(char rgb){
int r=0;
int g=0;
int b=0;
if (rgb=='r'){
r = (int)(r + 256*Math.random());
}
if (rgb=='g'){
g = (int)(g + 256*Math.random());
}
if (rgb=='b'){
b = (int)(b + 256*Math.random());
}
c = new Color(r,g,b);
}
}// end of class MyPanel
//円を動かすクラス
class moveDisk extends Thread{
MyPanel pnl;
BufferedImage image;
int x = 100;
int y ;
int d = 10;
int dx = 10;
//constructor
public moveDisk(MyPanel pn, BufferedImage im) {
this.pnl = pn;
this.image = im;
y = (int)(image.getHeight()*Math.random());
}
@Override
public void run() {
Graphics thg = image.createGraphics();
thg.setColor(Color.red);
thg.drawString(getName(),x-90,y);
while ( image.getWidth() > x ){
thg.fillOval(x-d/2,y-d/2,d,d);
pnl.repaint();
x += dx;
//100ms(0.1秒)停止
try {
Thread.sleep(100);
}
catch(InterruptedException ex) {
System.err.println(ex);
}
}//end of while
}//end of run
}//end of class moveDisk
}
強調部分が主要な変更です。イベント処理(actionPerformed)で、mypnl.moveDisk()の代わりにスレッドの作成(new moveDisk())と実行(start)をしています。
new moveDisk(mypnl,buffimg);でbuffimgを使っているので、BufferedImage の宣言を MyPanelの中でなくMoveDisk2の最初でおこなうようにしました。
一番大きな変更はmypnl.moveDisk()メソッドを独立したクラスにしたことです。そのために最後に持っていっています。
実行すると次の様になります。

別スレッドで実行されることを確認しなさい。