Java从入门到删库跑路——多线程的同步学习(1)

一.为什么需要同步

经常作为的demo的卖票程序中,极有可能碰到一种意外,就是同一张票号被打印两次或多次,也可能出现打印出的票号为0或是负数的情况。

if (tickets>0)
System.out.println(Thread.currentThread().getName()+"出售票"+tickets--);

假设tickets的值为1的时候,线程1刚执行完if (tickets>0) 这行代码,正准备执行下面的代码,就在这时,操作系统将CPU切换到了线程2上执行,此时tickets的值仍为1,线程2执行完上面两行代码,tickets 的值变为0后,CPU又切回到了线程1上执行,但此时线程1不会再执行if (tickets>0) 这行代码,因为先前已经比较过了,并且比较的结果为真,线程1将直接往下执行这行代码。

System.out.println(Thread.currentThread().getName()+"出售票"+tickets--);

但此刻tickets 的值已变为0,屏幕打印出来的将是0。

要想立即见到这种意外,可以在程序中调用Thread.sleep()静态方法来刻意造成线程间的这种切换。Thread.sleep()方法将迫使线程执行到该处后暂停执行,让出CPU给别的线程,在指定的时间(这里是毫秒)后,CPU回到刚才暂停的线程上执行。

package dataStructure;

public class ThreadDemo {

	public static void main(String[] args) {
		TestThread t =new TestThread();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		
	}
}
class TestThread implements Runnable{
	private int tickets =20 ;
	public void run() {
		while (true) {
			if (tickets>0) {
				try {
					Thread.sleep(100);
				} catch (Exception e) {
					// TODO: handle exception
				}
				System.out.println(Thread.currentThread().getName()+"出售票"+tickets--);
			}
			
		}
	}
}

在本程序中,故意实现线程执行完if (tickets>0)语句后,执行Thread.sleep(100),以让出CPU给别的线程。从运行结果可以看到,票号被打印出来了负数,这说明有同一张票被卖了几次的意外发生。造成这种意外的根本原因就是因为资源数据访问不同步引起的。


 

二.同步代码块

为了线程安全,就涉及到线程同步,

第一步就是要保证下面代码的原子性

if (tickets>0)
System.out.println(Thread.currentThread().getName()+"出售票"+tickets--);

 同步代码块定义语法如下:

...
synchronized (对象) {
						需要同步的代码;
					}
package dataStructure;

public class ThreadDemo {

	public static void main(String[] args) {
		TestThread t =new TestThread();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		
	}
}
class TestThread implements Runnable{
	private int tickets =20 ;
	public void run() {
		while (true) {
			synchronized (this) {
				if (tickets>0) {
					try {					
						Thread.sleep(100);
					} catch (Exception e) {
						// TODO: handle exception
					}
					System.out.println(Thread.currentThread().getName()+"出售票"+tickets--);
				}
			}
			
		}
	}
}

 本程序将这些需要具有原子性的代码放入synchronied语句内,形成了同步代码块。在同一时刻只能有一个线程可 以进入同步代码块内运行,只有当该线程离开同步代码块后,其他线程才能进入同步代码块内运行。

 

三.同步方法

package dataStructure;

public class ThreadDemo {

	public static void main(String[] args) {
		TestThread t =new TestThread();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		
	}
}
class TestThread implements Runnable{
	private int tickets =20 ;
	public void run() {
		while (true) {
			sale();
			
		}
	}
	
	public synchronized void sale() {

		if (tickets>0) {
			try {					
				Thread.sleep(100);
			} catch (Exception e) {
				// TODO: handle exception
			}
			System.out.println(Thread.currentThread().getName()+"出售票"+tickets--);
		}
	
		
	}
}

可见,编译运行后的结果同上面同步代码块方式的运行结果完全一样, 也就是说在方法定义前使用synchronized关键字也能够很好地实现线程间的同步。

在同一类中,使用synchronized关键字定义的若干方法,可以在多个线程之间同步。当有一个线程进入了有synchronized修饰的方法时,其他线程就不能进入同一个对象使用synchronized来修饰所有方法,直到第1个线程执行完它所进入的synchronized修饰的方法为止。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值