多线程的优势
多线程可以让一个进程有多个并发操作,及多个任务在同一个时间被执行,提高了系统的吞吐率,而且一个进程中的多个线程可以共享其所在进程所申请的资源,如内存空间,节约了对系统资源的利用率。
下面用一个彩色线条的例子来体现多线程:
UI.java
import java.awt.Graphics;
import javax.swing.JFrame;
public class UI {
public static void main(String[] args) {
UI myui=new UI();
myui.Initl();
}
void Initl() {
//初始化界面
JFrame myjf=new JFrame();
myjf.setTitle("多线程示例");
myjf.setSize(800,600);
myjf.setDefaultCloseOperation(3);
myjf.setLocationRelativeTo(null);
myjf.setVisible(true);
Graphics g=myjf.getGraphics();
//创建监听器对象,并给窗体加上鼠标监听器
Mouse_Lis listener=new Mouse_Lis();
myjf.addMouseListener(listener);
//创建线程类对象,把鼠标点击后获取的矩形对象传给线程类
Thread_Rec rectangle=new Thread_Rec(g);
rectangle.reclist=listener.reclist;
//创建线程并启动
Thread thr1=new Thread(rectangle);
thr1.start();
}
}
Rectangle.java
import java.awt.Color;
import java.awt.Graphics;
public class Rectangle {
/*
* x,y:矩形的坐标
* speedx,speedy:移动的速度
* size:大小
* color:矩形的颜色
*/
int x,y,speedx,speedy,size;
Color color;
//构造方法
public Rectangle(int x,int y,int speedx,int speedy,int size,Color color) {
// TODO Auto-generated constructor stub
this.x=x;
this.y=y;
this.speedx=speedx;
this.speedy=speedy;
this.size=size;
this.color=color;
}
//小矩形绘制
void Draw(Graphics g) {
g.setColor(color);
g.fillRect(x, y, size, size);
}
//小矩形移动
void Move() {
if(x<0||x>1000) {
speedx=-speedx;
}else if(y<0||y>800) {
speedy=-speedy;
}
x+=speedx;
y+=speedy;
}
}
Mouse_Lis.java
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Random;
public class Mouse_Lis extends MouseAdapter{
/*
* rec:用来存储矩形的队列
* x,y:鼠标获取的矩形的坐标
* color:小球的颜色
*/
ArrayList<Rectangle> reclist=new ArrayList();
@Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
int x=e.getX();
int y=e.getY();
Random ran=new Random();
Color color=new Color(ran.nextInt(Integer.MAX_VALUE));
Rectangle rectan=new Rectangle(x,y,1,1,30,color);
reclist.add(rectan);//入队列
}
}
Thread_Rec.java
import java.awt.Graphics;
import java.util.ArrayList;
public class Thread_Rec implements Runnable{
ArrayList<Rectangle> reclist;
Graphics g;
public Thread_Rec(Graphics g) {
this.g=g;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
for(int i=0;i<reclist.size();i++) {
Rectangle rect=reclist.get(i);
//调用矩形类的方法画图
rect.Draw(g);
rect.Move();
}
try {
Thread.sleep (15);
} catch (InterruptedException e) {
e.printStackTrace ();
}
}
}
}
这是一个线程,可如果我们,在界面类做如下改进,启动多个线程的话:
Thread thr2=new Thread(rectangle);
thr2.start();
Thread thr3=new Thread(rectangle);
thr3.start();
绘制速度会明显加快。
单线程就好比你踢了一次球,而多线程相当于你多次踢了同一个球,很明显该球会变得更快。当然这并不是线性的叠加,讲线程问题时会提到。
线程问题的发现
运行单线程时效果如下:
运行多线程时效果如下:
仔细观察就会发现单线程虽然速度慢,但它的线条笔直,而且颜色非常纯正,而多线程虽然速度快,但是会有颜色突变,为什么呢?
线程问题的解释
多个线程在共享数据时,很可能产生数据一致性的问题,如读取过期的数据,某些线程所做的更新被其他线程所做的更新更改等。例如上述例子:因为多个线程共有一个画笔g,在一个线程还未绘制完矩形时画笔的颜色已被另一个线程所更改。
线程问题的解决
这种情况在真正的大型系统中是十分危险的。在介绍解决办法之前先了解几个名词:
原子操作
原子操作是指单一不可分割的操作。例如:int x=2;虽然是一条语句,但是实际操作时却分为两步:(1)声明变量x;(2)给变量赋值为2。第一步和第二步之间是可以被其他线程打断的,故不是原子操作。
锁:如果不想某段代码在运行时被打断,可以通过对该段代码上锁来把它变成 “原子操作” 。
解决:使用关键字synchronized给代码上锁来实现操作的原子性,其本质是通过该关键字所包含的临界区(Critical Section)的排他性保证在任何一个时刻只有一个线程能执行临界区的代码。
举例说明 代码改进
在线程类的run方法中给代码加锁:
public void run() {
// TODO Auto-generated method stub
while(true) {
for(int i=0;i<reclist.size();i++) {
Rectangle rect=reclist.get(i);
//加锁
synchronized (g) {
rect.Draw(g);
rect.Move();
}
}
try {
Thread.sleep (15);
} catch (InterruptedException e) {
e.printStackTrace ();
}
}
}
程序运行效果如下:
速度依旧很快,也没有颜色突变的情况了。
再举例 (计数器)
E.java
public class E implements Runnable{
int count=0;
public static void main(String[] agrs) {
E e=new E();
Thread th=new Thread(e);
th.start();
Thread th2=new Thread(e);
th2.start();
Thread th3=new Thread(e);
th3.start();
}
@Override
public void run() {
while(true) {
for(int i=0;i<100;i++) {
count++;
try {
Thread.sleep (15);
} catch (InterruptedException e) {
e.printStackTrace ();
}
}
System.out.println("count="+count);
}
}
}
程序运行时按理来说count应该是成百成百地增加,但实际情况却是:
给循环加锁之后:
总结
多线程虽好,可要注意安全问题噢~