多线程同步(线程安全,同步方法)

多线程同步

java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程

没有完成操作之前,被其他线程的调用, 从而保证了该变量的唯一性和准确性。

线程安全

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。

不出现数据不一致或者数据污染。

线程不安全

就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据


1、下面例子是线程不安全的例子

public class Example11 {
	public static void main(String[] args) {
		SaleThread saleThread = new SaleThread(); // 创建Ticket1 对象
		// 创建并开启四个线程
		new Thread(saleThread, "线程一").start();
		new Thread(saleThread, "线程二").start();
		new Thread(saleThread, "线程三").start();
		new Thread(saleThread, "线程四").start();
	   }
}
// 定义Ticket1类实现Runnable接口
class SaleThread implements Runnable {
	private int tickets = 10; // 10张票
	public void run() {
		while (tickets > 0) {
			try {
				Thread.sleep(10); // 经过此处的线程休眠10毫秒
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "---卖出的票"
					+ tickets--);
		}
	}
}

结果:

线程三---卖出的票10
线程二---卖出的票9
线程四---卖出的票9
线程一---卖出的票8
线程二---卖出的票7
线程三---卖出的票6
线程一---卖出的票5
线程四---卖出的票5
线程三---卖出的票4
线程二---卖出的票3
线程一---卖出的票2
线程四---卖出的票2
线程二---卖出的票0
线程三---卖出的票1
线程四---卖出的票-1
线程一---卖出的票-1

由结果看出不安全线程造成的结果是同一资源可能被多次使用,与现实不符。造成上面的原因:①如9被读2次,②输出的数有负数。

①假设线程一此时出售9号票,在售票之前通过sleep()方法让线程休眠,因为调用sleep()函数不会释放资源,所以当线程一睡眠时,9号票还在它手里,这时线程三开始买票,

因为9号票还没卖出去,线程三就卖了9号票,当线程一醒了之后,又把手里的9号票卖了出去,所以就卖了2次。

②假设线程一此时出售1号票,对票号进行判断后,进入while循环,在售票之前通过sleep()方法让线程休眠,这时线程二进行售票,由于此时票号仍未1,因此线程二也会进入

循环,同理,四个线程都会进入while循环,休眠结束后,四个线程都会进行售票,这样就相当于将票减了四次。

二、实现同步

为了实现线程安全,即资源不被重复使用,Java提供了同步机制:①同步代码块,②同步方法(都用synchronized关键字来修饰)

①同步代码块

synchronized (lock) { 
	// 同步代码块
}
其中lock是一个锁对象,它是同步代码的关键。当线程执行同步代码块时,首先会检查锁对象的标志位。

lock默认情况下标志位为1,线程执行同步代码块时,lock变为0,别的线程就不能执行这段代码块了,当这个线程完事之后,lock变回1,别的线程可以调用这个代码了。将上面代码用synchronized后如下:

class Ticket1 implements Runnable {
	private int tickets = 10; // 定义变量tickets,并赋值10
	Object lock = new Object(); // 定义任意一个对象,用作同步代码块的锁
	public void run() {
		while (true) {
			synchronized (lock) { // 定义同步代码块
				try {
					Thread.sleep(10); // 经过的线程休眠10毫秒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (tickets > 0) {
					System.out.println(Thread.currentThread().getName()
							+ "---卖出的票" + tickets--);
				} else { // 如果 tickets小于0,跳出循环
					break;
				}
			}
		}
	}
}
public class Example2 {
	public static void main(String[] args) {
		Ticket1 ticket = new Ticket1(); // 创建Ticket1对象
		// 创建并开启四个线程
		new Thread(ticket, "线程一").start();
		new Thread(ticket, "线程二").start();
		new Thread(ticket, "线程三").start();
		new Thread(ticket, "线程四").start();
	}
}

结果:

线程一---卖出的票10
线程一---卖出的票9
线程一---卖出的票8
线程一---卖出的票7
线程一---卖出的票6
线程一---卖出的票5
线程一---卖出的票4
线程一---卖出的票3
线程一---卖出的票2
线程一---卖出的票1

由结果知道实现了线程安全,资源不被多次使用。


②同步方法

被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程会发生阻塞,直到当前线程访问完毕后,其他线程才有机会访问。如下例

class Ticket1 implements Runnable {
	private int tickets = 10;
	public void run() {
		while (true) {
			saleTicket(); // 调用售票方法
			if (tickets <= 0) { 
				break;
			}
		}
	}
    // 定义一个同步方法saleTicket()
	private synchronized void saleTicket() {
		if (tickets > 0) {
			try {
				Thread.sleep(10); // 经过的线程休眠10毫秒
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "---卖出的票"
					+ tickets--);
		}
	}
}
public class Example {
	public static void main(String[] args) {
		Ticket1 ticket = new Ticket1(); // 创建Ticket1对象
         // 创建并开启四个线程
		new Thread(ticket,"线程一").start();
		new Thread(ticket,"线程二").start();
		new Thread(ticket,"线程三").start();
		new Thread(ticket,"线程四").start();
	}
}
结果:

线程一---卖出的票10
线程一---卖出的票9
线程一---卖出的票8
线程一---卖出的票7
线程一---卖出的票6
线程一---卖出的票5
线程一---卖出的票4
线程一---卖出的票3
线程一---卖出的票2
线程一---卖出的票1

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值