java多线程学习(线程的同步机制)

线程安全问题

线程安全问题:当多线程访问了共享数据就会产生线程安全问题。
如:总共有5张票,电影院有三个窗口卖票,要让三个窗口共享票的数据。

public class SaleTicket implements Runnable{
	public static int ticket = 5;//一共有5张票
	@Override
	public void run() {//多线程执行的任务,卖票
		while(true) {//一直执行卖票的动作
			if(ticket>0) {//先判断是否有票
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("正在卖第"+ticket+"张票");
				ticket--;
			}
		}
	}
}
public class SaleTicketTest {
	public static void main(String[] args) {
	//创建一个实现类,传入三个线程,保证只有这5张票
		SaleTicket sti = new SaleTicket();
		Thread th1 = new Thread(sti);
		Thread th2 = new Thread(sti);
		Thread th3 = new Thread(sti);
		th1.start();
		th2.start();
		th3.start();
		//出现了线程安全问题,th1,th2,th3三个线程抢占CPU资源
	}
}
正在卖第5张票
正在卖第5张票
正在卖第5张票
正在卖第2张票
正在卖第2张票
正在卖第0张票
正在卖第-1张票//这种线程安全问题不能产生

我们可以让线程在访问共享数据的时候,其他线程只能等待,等待当前线程卖完票,其他线程在进行卖票。因此我们需要解决这个线程安全问题。

结局线程安全的三种方式

1:同步代码块
synchronized关键字可用于方法中某个区块中,表示对这个区块资源进行互斥访问。

synchronized(同步锁){
	//需要互斥访问的代码
}

注意:
1:同步代码块中的锁对象,可以使用任意对象。
2:但是必须保证多个线程对象使用的锁对象是同一个
3:锁对象的作用,把同步代码块锁住,只让一个线程在同步代码块中执行。

public class SaleTicket implements Runnable{
	public static int ticket = 5;
	Object obj = new Object();//创建一个锁对象
	@Override
	public void run() {
		while(true) {//一直执行卖票的动作
			synchronized (obj) {//多线程执行的任务,卖票用同步代码块进行包裹
				if(ticket>0) {//先判断是否有票
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println("正在卖第"+ticket+"张票");
					ticket--;
				}
			}
		}
	}
}
public class SaleTicketTest {
	public static void main(String[] args) {
	//创建一个实现类,传入三个线程,保证只有这5张票
		SaleTicket sti = new SaleTicket();
		Thread th1 = new Thread(sti);
		Thread th2 = new Thread(sti);
		Thread th3 = new Thread(sti);
		th1.start();
		th2.start();
		th3.start();
	}
}
正在卖第5张票
正在卖第4张票
正在卖第3张票
正在卖第2张票
正在卖第1张票//结解决了线程安全问题

原理:三个线程抢夺CPU执行权,谁抢到了就执行run()方法进行卖票。th1抢到了CPU执行权,执行run方法,遇到synchronized代码块,这时候th1会检查synchronized代码块是否有锁对象。发现有就会获取到锁对象,进入到同步执行。th2抢到了CPU执行权,执行run方法,遇到synchronized代码块,这时候th2会检查synchronized是否有锁对象,发现没有,th2进入到阻塞状态,一直等待th1归还锁对象。
程序频繁的判断锁,获取锁,释放锁,程序的效率会降低。

2:同步方法
使用步骤:
1:把访问了共享数据的代码抽取出来,放到一个方法中
2:在方法上添加synchronized修饰符

public class SaleTicket implements Runnable{
	public static int ticket = 5;
	Object obj = new Object();
	@Override
	public void run() {
		while(true) {//一直执行卖票的动作
			Sale();//调用执行同步的方法
		}
	}
	public synchronized void Sale() {
		if(ticket>0) {//先判断是否有票
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("正在卖第"+ticket+"张票");
			ticket--;
		}
	}
}
public class SaleTicketTest {
	public static void main(String[] args) {
	//创建一个实现类,传入三个线程,保证只有这5张票
		SaleTicket sti = new SaleTicket();
		Thread th1 = new Thread(sti);
		Thread th2 = new Thread(sti);
		Thread th3 = new Thread(sti);
		th1.start();
		th2.start();
		th3.start();
	}
}
正在卖第5张票
正在卖第4张票
正在卖第3张票
正在卖第2张票
正在卖第1张票//结解决了线程安全问题

原理:定义一个同步方法,同步方法也会把方法内部的锁对象锁住,只让一个线程执行,同步方法的锁对象是实现类对象,也就是this。如果给同步方法添加static修饰符,变为静态方法,静态方法的锁对象是本类的Class属性。

public class SaleTicket implements Runnable{
	public static int ticket = 5;
	Object obj = new Object();
	@Override
	public void run() {
		while(true) {//一直执行卖票的动作
			Sale();//调用执行同步的方法
		}
	}
	public static void Sale() {
		synchronized(SaleTicket.class) {//静态方法的锁对象是本类的class
			if(ticket>0) {//先判断是否有票
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("正在卖第"+ticket+"张票");
				ticket--;
			}
		}
	}
}
public class SaleTicketTest {
	public static void main(String[] args) {
	//创建一个实现类,传入三个线程,保证只有这5张票
		SaleTicket sti = new SaleTicket();
		Thread th1 = new Thread(sti);
		Thread th2 = new Thread(sti);
		Thread th3 = new Thread(sti);
		th1.start();
		th2.start();
		th3.start();
	}
}//也保证的线程安全。

3:锁机制
void Lock()获取锁。
void unlock()释放锁。
使用步骤:
1.在成员位置创建一个ReentrantLock对象。
2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁。
3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁。

public class LockThread implements Runnable{
	private int ticket = 5;
	Lock l = new ReentrantLock();//1.在成员位置创建一个ReentrantLock对象。
	@Override
	public void run() {
		while(true) {
			l.lock();//2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁。
			if(ticket>0) {//先判断是否有票
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("正在卖第"+ticket+"张票");
				ticket--;
			}
			l.unlock();//3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁。
		}
	}
}
public class SaleTicketTest {
	public static void main(String[] args) {
	//创建一个实现类,传入三个线程,保证只有这5张票
		LockThread sti = new LockThread();
		Thread th1 = new Thread(sti);
		Thread th2 = new Thread(sti);
		Thread th3 = new Thread(sti);
		th1.start();
		th2.start();
		th3.start();
	}
}
正在卖第5张票
正在卖第4张票
正在卖第3张票
正在卖第2张票
正在卖第1张票

优化下代码:

public class LockThread implements Runnable{
	private int ticket = 5;
	Lock l = new ReentrantLock();//1.在成员位置创建一个ReentrantLock对象。
	@Override
	public void run() {
		while(true) {
			l.lock();//2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁。
			if(ticket>0) {//先判断是否有票
				try {
					Thread.sleep(1000);
					System.out.println("正在卖第"+ticket+"张票");
					ticket--;
				} catch (InterruptedException e) {
					e.printStackTrace();
				}finally {
					l.unlock();//3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁。
				}//写在这里的好处,无论程序是否异常,都需要释放掉锁对象
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值