【Java】线程同步:从多线程小球浅谈 synchronized 的使用

最近刚开始学习多线程,写小球碰撞过程中遇到了问题:

多个线程小球在运动时,画布上会出现残影:
在这里插入图片描述
画小球的主要方法是设置画笔为背景色 - 画小球 - 设置画笔随机颜色 - 画小球,再通过调用另一个 move 方法让它们动起来,这样就能起到一个擦除轨迹实现运动的效果。关键代码如下:

//擦除小球
	public void drawDelect(){
			g.setColor(Color.lightGray);	//设置背景色
			g.fillOval(x - r - 1, y - r - 1, r * 2 + 3, r * 2 + 3); 
	}
//画新小球
	public void drawNew(){
        g.setColor(new Color(green,red,blue));		//设置小球颜色
        g.fillOval(x - r / 2 - i / 2, y - r / 2 - i / 2, i * 2, i * 2);
	}

线程的 run 方法:

public void run(){
		System.out.println("Thread start");
		for (;;){
			this.drawDelect();	//擦除小球轨迹
			this.move();		//移动小球
			this.collide();		//判断碰撞
			this.drawNew();		//画小球
		}
	}

经过分析,找到了原因:

每一个线程都创建了一个小球,但是不同线程的小球都是共用一个画笔 g ,也就是同一时间只有一个线程能拿到这支画笔。而每个线程对小球处理时,是先设置画笔颜色,再调用画笔画小球。

这里就会出现一个问题,比如,线程1设置背景颜色后,正想调用画笔 g 擦除小球轨迹,结果画笔 g 被线程2抢走了,线程1只能等待线程2画完才能拿起画笔,而这段时间小球依然在移动,移动的轨迹就没有被擦除,于是就留下了“残影”。

解决方法有两个,一个是彻底改写程序,只用一个线程,就不会有这个问题了。这里不讨论这个方法。

另一个方法就是,让执行这一小段代码时,画笔不能被其他线程抢走。

这里就是这篇博客要说的,synchronized 的使用了。

画小球的方法这样写,就不会出现这个问题了:

	//擦除小球
	public void drawDelect(){
        //用 synchronized 声明同步块
		synchronized (g) {
			g.setColor(Color.lightGray);
			g.fillOval(x - r - 1, y - r - 1, r * 2 + 3, r * 2 + 3);
		}
	}
	//画新小球
	public void drawNew(){
		//用 synchronized 声明同步块
    	synchronized (g){
        	g.setColor(new Color(green,red,blue));
        	g.fillOval(x - r / 2 - i / 2, y - r / 2 - i / 2, i * 2, i * 2);
        }
	}

在这里,synchronized 就是一把锁,保证在同一时刻,只有一个线程可以拿到锁去执行某个方法或某个代码块(同步块)。这里的括号里的参数是对象锁,线程执行到这里必须要获得这个对象的锁才能执行下面的同步块

好了,进入正题。

synchronized 实现原理

Java专门提供了负责管理线程对象中同步方法访问的工具—同步模型监视器,其原理是为每个具有同步代码的对象准备唯一一把“锁”。在Java程序中,通过 wait()、notify() 及 notifyAll() 方法可以完成线程间的消息传递。当前线程调用 wait() 方法可以使该线程进入不可运行状态,其他线程调用 notify() 或 notifyAll() 方法可以唤醒该线程。更底层的原理就不便展开了(我也不是太懂

synchronized 的用法

上面讲到的是对方法里的一小段代码加锁,锁是括号里面的对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。即:

public void method(){
	synchronized(obj){
	//……
	}
}

还有就是对整个方法加锁,即将该方法声明为同步:

public class demo{}
	public synchronized void method(){
		//……
	}
}

当两个线程同时对一个对象的一个方法进行操作,只有一个线程能够抢到锁。因为一个对象只有一把锁,一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,就不能访问该对象的其他 synchronized 实例方法,但是可以访问非 synchronized 修饰的方法。

但是如果两个个线程实例化出两个对象,对对象的一个方法进行操作,因为两个线程作用于不同的对象,获得的是不同的锁,所以互相并不影响。

例如以下代码创建了一个局部对象obj,由于每个线程执行到 Object obj = new Object()时,都会产生一个 obj 对象,每个线程都可以获得创建的新的 obj 对象的锁,不会相互影响,因此这段程序不会起到同步作用。

public void method(){
	Object obj = new Object();	//创建局部 Object 类型对象 obj
	synchronized(obj){
	//……
	}
}

还有需要注意的是,synchronized 作用于静态方法时,两个线程实例化两个不同的对象,但是访问的方法是静态的,两个线程发生了互斥(即一个线程访问,另一个线程只能等着),因为静态方法是依附于类而不是对象的,当 synchronized 修饰静态方法时,锁是 class 对象。

如下面代码中 increase方法是静态方法,用 synchronized 修饰时,就算是两个线程实例化两个不同的对象来访问,因为此时锁的其实是 class 对象,所以结果和两个线程同时对一个对象来访问的效果一样。

public class synchronizedTest implements Runnable {
    //共享资源
    static int i =0;
    //synchronized 修饰静态方法
    public static synchronized void increase(){
        i++;
    }
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值