线程安全问题
线程安全问题:当多线程访问了共享数据就会产生线程安全问题。
如:总共有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释放锁。
}//写在这里的好处,无论程序是否异常,都需要释放掉锁对象
}
}
}
}