请问,Java如何使用多线程重绘?有哪几种方法,哪种比较好?

corn8888 2012-08-19 09:52:22
比如另开一个线程响应panel的重绘事件?
...全文
474 26 打赏 收藏 转发到动态 举报
写回复
用AI写文章
26 条回复
切换为时间正序
请发表友善的回复…
发表回复
最强面试者 2013-10-22
  • 打赏
  • 举报
回复
引用 10 楼 ldh911 的回复:
[Quote=引用 9 楼 的回复:] “绘图过程可以绘制到 BufferedImage 上” 还是需要在线程里执行绘图过程啊 [/Quote] 楼主听说过 双缓冲 之类的技术么?类似但是不完全一样。 如我1楼所说,你要能区分出 GUI主线程 和 其它线程。 GUI主线程负责控制容器的输出,这个是独占的。 而其它线程可以将所需绘制内容先绘制到各自的BufferedImage上,这期间,BufferedImage是由各自线程所独占的。我并没有说线程不能执行绘图,我只是强调GUI主线程的独占性。 最后GUI主线程定期将Buffered的内容合成输出到主容器上(约等于屏幕),这样GUI的工作量就很小了。
你好,你说的意思我懂了,但是我有个问题还是不明白:假如我最终输出到屏幕上的图像是由3部分组成(或者更多的部分组成的),比如说是个游戏,首先要绘制地图(创建一个线程将地图绘制在自己线程内的BufferedImage1),然后还有一个障碍物同样一个线程绘制到自己的BufferedImage2上,还有一个游戏人物角色也创建一个线程绘制到自己的BufferedImage3上,最后将BufferedImage1,BufferedImage2,BufferedImage,3按1、2、3的优先次序绘制到主线程的BufferedImage的上,然后再输出,但是问题是,线程的执行需要得到处理机,所以绘制操作会有迟延或者其他误差,然而我的游戏是动态更新画面的,下一帧绘制的会不会出现是上一刻游戏的地图和当前游戏中的障碍物和下一时刻游戏中人物的形态呢?
MiceRice 2012-11-06
  • 打赏
  • 举报
回复
楼主你也太有心了,我看到帖子更新,还以为是新的问题 =_=
C4FE4597464E1BAA 2012-11-06
  • 打赏
  • 举报
回复
谢那个贴代码的哥们儿。。最近就是很愁多线程绘图,找了好几天了,看您几个代码,胜过前几天的傻努力
引用 20 楼 ldh911 的回复:
有啊,工作线程绘制到BufferedImage上,主线程只负责输出。 一般来说都比较推荐这种: Java code12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364impor……
corn8888 2012-08-20
  • 打赏
  • 举报
回复
一个 C# 多线程重绘界面的例子

http://topic.csdn.net/u/20120805/01/58230777-2767-4337-A8F8-A3725E7DB4B1.html
corn8888 2012-08-20
  • 打赏
  • 举报
回复
楼上几位的方法,我看得不是很明白

我要在panel上绘图,假如重绘时调用paint(),我另开一个线程,在paint()中向线程发送对象及重绘事件,在线程中重绘,这样做有什么问题吗?
corn8888 2012-08-20
  • 打赏
  • 举报
回复
不多说了,感谢哥们
MiceRice 2012-08-20
  • 打赏
  • 举报
回复
在我有限的知识里,没有了。。。
scbb 2012-08-20
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 的回复:]

引用 9 楼 的回复:
“绘图过程可以绘制到 BufferedImage 上”
还是需要在线程里执行绘图过程啊


楼主听说过 双缓冲 之类的技术么?类似但是不完全一样。

如我1楼所说,你要能区分出 GUI主线程 和 其它线程。

GUI主线程负责控制容器的输出,这个是独占的。

而其它线程可以将所需绘制内容先绘制到各自的BufferedImage上,这期间,Buffe……
[/Quote]

对的,先把要再GUI显示的,绘制到BufferedImage,最后一次更新到界面。
如果界面对应的BufferedImage比较多,到可以用多线程来绘制BufferedImage。
但是主的GUI线程只有1个。
MiceRice 2012-08-20
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 的回复:]
“绘图过程可以绘制到 BufferedImage 上”
还是需要在线程里执行绘图过程啊
[/Quote]

楼主听说过 双缓冲 之类的技术么?类似但是不完全一样。

如我1楼所说,你要能区分出 GUI主线程 和 其它线程。

GUI主线程负责控制容器的输出,这个是独占的。

而其它线程可以将所需绘制内容先绘制到各自的BufferedImage上,这期间,BufferedImage是由各自线程所独占的。我并没有说线程不能执行绘图,我只是强调GUI主线程的独占性。

最后GUI主线程定期将Buffered的内容合成输出到主容器上(约等于屏幕),这样GUI的工作量就很小了。
corn8888 2012-08-20
  • 打赏
  • 举报
回复
楼上哥们太好了,花时间帮我解决问题,这年头时间多宝贵啊!

比较了两种方法,感觉第2种要慢一点,可能还要根据具体的应用来选择吧

请问还有其它的方法吗?
MiceRice 2012-08-20
  • 打赏
  • 举报
回复
有啊,工作线程绘制到BufferedImage上,主线程只负责输出。

一般来说都比较推荐这种:


import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.*;

import javax.swing.*;

public class BufferedImageGUI extends JFrame implements Runnable {

private volatile BufferedImage boardDrawing;

private volatile BufferedImage boardDisplaying;

/**
* 窗体构造函数
*/
public BufferedImageGUI() {
this.setSize(800, 600);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);

boardDrawing = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
boardDisplaying = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
}

public void paint(Graphics g) {
g.drawImage(boardDisplaying, 0, 0, null);
}

public void run() {
Random rand = new Random();
while (true) {
// 复杂的绘制过程
Graphics g = boardDrawing.getGraphics();
g.clearRect(0, 0, boardDrawing.getWidth(), boardDrawing.getHeight());
for (int i = 0; i < 500; i++) {
g.setColor(new Color(rand.nextInt()));
g.drawString(String.valueOf(rand.nextInt()), rand.nextInt(800), rand.nextInt(600));
}

// 切换前景与背景工作区
BufferedImage tmp = boardDisplaying;
boardDisplaying = boardDrawing;
boardDrawing = tmp;
this.repaint();

// Sleep
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

/**
* 启动主函数
*/
public static void main(String[] args) {
BufferedImageGUI wnd = new BufferedImageGUI();
wnd.setVisible(true);
new Thread(wnd).start();
}
}
corn8888 2012-08-20
  • 打赏
  • 举报
回复
请问还有没有其它的方法?
MiceRice 2012-08-20
  • 打赏
  • 举报
回复
[Quote=引用 17 楼 的回复:]
我的理解不知对不对?
for循环每次都新建一个线程,线程里有个while(true)循环会一直运行,直至程序退出
[/Quote]

理解正确。

其实你可以打开 任务管理器,然后在“查看”->“选择列”中增加 线程数。

然后多点几次按钮,可以看到线程暴涨。。。

注意:线程数越多,线程自身切换的代价越高,系统速度越慢。最后如果是几百个线程跟GUI线程抢占CPU资源的话,界面也会卡的。
corn8888 2012-08-20
  • 打赏
  • 举报
回复
非常感谢楼上的哥们!

哥们给的代码运行起来很精彩!


for (int i = 0; i < 50; i++) {
new DrawWorker(g).start();
}
我的理解不知对不对?
for循环每次都新建一个线程,线程里有个while(true)循环会一直运行,直至程序退出
建第1个线程,第1个线程开始运行,现在有1个线程在运行
建第2个线程,第2个线程开始运行,现在有2个线程在运行
建第3个线程,第3个线程开始运行,现在有3个线程在运行
建第4个线程,第4个线程开始运行,现在有4个线程在运行
建第5个线程,第5个线程开始运行,现在有5个线程在运行
........

MiceRice 2012-08-20
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 的回复:]
我不想用GUI主线程绘图,就想另开一个线程执行绘图操作,难到就没有办法了吗?
[/Quote]

可以,不要访问控件,仅操作Graphics。


import java.awt.*;
import java.awt.event.*;
import java.util.*;

import javax.swing.*;

public class MultiThreadDraw extends JFrame {

/**
* 窗体构造函数
*/
public MultiThreadDraw() {
this.setSize(800, 600);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
JButton btnStartThreads = new JButton("StartManyThread");
btnStartThreads.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Graphics g = ((JComponent) e.getSource()).getParent().getGraphics();
System.out.println(g.getClass());
for (int i = 0; i < 50; i++) {
new DrawWorker(g).start();
}
System.out.println("Thread started...");
}
});
this.getContentPane().add(btnStartThreads);
}

/**
* 启动主函数
*/
public static void main(String[] args) {
MultiThreadDraw wnd = new MultiThreadDraw();
wnd.setVisible(true);
}

private class DrawWorker extends Thread {
private Graphics g;

public DrawWorker(Graphics g) {
this.g = g;
}

public void run() {
Random rand = new Random();
while (true) {
g.setColor(new Color(rand.nextInt()));
g.drawString(String.valueOf(rand.nextInt()), rand.nextInt(800), rand.nextInt(600));
g.drawLine(rand.nextInt(800), rand.nextInt(600), rand.nextInt(800), rand.nextInt(600));
}
}
}
}


不过还是推荐用BufferedImage,不要全部都直接绘制容器的Graphics
corn8888 2012-08-20
  • 打赏
  • 举报
回复
“它原理上仍然是借助GUI主线程,如果所Invoke的doRun运算很复杂,仍然会阻塞GUI线程。”


我不想用GUI主线程绘图,就想另开一个线程执行绘图操作,难到就没有办法了吗?
MiceRice 2012-08-20
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 的回复:]
一个 C# 多线程重绘界面的例子
http://topic.csdn.net/u/20120805/01/58230777-2767-4337-A8F8-A3725E7DB4B1.html
[/Quote]

看过你的例子了,这个Java也支持。不过直接这么用,应该是不能解决你的问题的:

javax.swing.SwingUtilities.invokeLater(doRun);

它原理上仍然是借助GUI主线程,如果所Invoke的doRun运算很复杂,仍然会阻塞GUI线程。


public class TestInvokeLater extends JFrame {

/**
* 负责绘图
*/
private Runnable doBusyDraw = new Runnable() {
public void run() {
try {
Thread.sleep(5000); // 每次绘图执行花 5 秒
} catch (InterruptedException e) {
}
System.out.println("BusyDraw done.");
}
};

/**
* 负责定时刷新
*/
private Thread refresher = new Thread() {
public void run() {
try {
while (true) {
SwingUtilities.invokeLater(doBusyDraw);
Thread.sleep(1000); // 每秒执行 1 次绘图
}
} catch (InterruptedException e) {
}
}
};

public TestInvokeLater() {
this.setSize(800, 600);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);

JButton btnBusyDraw = new JButton("StartBusyDraw");
btnBusyDraw.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
refresher.start();
System.out.println("Thread started...");
}
});

this.add(btnBusyDraw);
}

/**
* 启动主函数
*/
public static void main(String[] args) {
TestInvokeLater wnd = new TestInvokeLater();
wnd.setVisible(true);
}
}
小傅哥 2012-08-19
  • 打赏
  • 举报
回复
同意楼上的
corn8888 2012-08-19
  • 打赏
  • 举报
回复
“绘图过程可以绘制到 BufferedImage 上”

还是需要在线程里执行绘图过程啊
MiceRice 2012-08-19
  • 打赏
  • 举报
回复
这个多线程重绘,是你创新的么?还是?

Java中,GUI线程只有一条,而且从并发安全性上而言,是不允许其它线程随意去访问Graphics的。

所以多线程GUI系统,都是只多线程处理各类数据,或者各自处理图形缓冲,而最终负责输出到屏幕的,只有GUI线程。


不知道楼主你的期望是什么?
加载更多回复(5)

62,621

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧