Java线程系列(四)多线程的优势和风险(线程安全问题)

本文探讨了多线程编程的优势,如提高系统吞吐率和资源利用率,并通过一个彩色线条的示例说明其工作原理。然而,多线程也可能导致数据一致性问题,如颜色突变。解决这类问题的关键在于使用原子操作和锁机制,以确保线程安全。文章通过一个Java程序展示了如何使用`synchronized`关键字修复线程安全问题。
摘要由CSDN通过智能技术生成

多线程的优势

多线程可以让一个进程有多个并发操作,及多个任务在同一个时间被执行,提高了系统的吞吐率,而且一个进程中的多个线程可以共享其所在进程所申请的资源,如内存空间,节约了对系统资源的利用率
下面用一个彩色线条的例子来体现多线程:

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应该是成百成百地增加,但实际情况却是:
在这里插入图片描述
给循环加锁之后:
在这里插入图片描述

总结

多线程虽好,可要注意安全问题噢~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值