java线程安全问题

当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题,但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。

1 同一个资源问题

1、局部变量不能共享

public class SaleTicketDemo1 {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
       
        w1.start();
        w2.start();
        w3.start();
    }
}
class Window extends Thread{
    public void run(){
        int total = 100;
        while(total>0) {
            System.out.println(getName() + "卖出一张票,剩余:" + --total);
        }
    }
}

2、不同对象的实例变量不共享

public class SaleTicketDemo2 {
	public static void main(String[] args) {
		TicketSaleThread t1 = new TicketSaleThread();
		TicketSaleThread t2 = new TicketSaleThread();
		TicketSaleThread t3 = new TicketSaleThread();
		
		t1.start();
		t2.start();
		t3.start();
	}
}
class TicketSaleThread extends Thread{
	private int total = 10;
	public void run(){
		while(total>0) {
			System.out.println(getName() + "卖出一张票,剩余:" + --total);
		}
	}
}

3、静态变量是共享的共享

public class SaleTicketDemo3 {
	public static void main(String[] args) {
		TicketThread t1 = new TicketThread();
		TicketThread t2 = new TicketThread();
		TicketThread t3 = new TicketThread();
		
		t1.start();
		t2.start();
		t3.start();
	}
}
class TicketThread extends Thread{
	private static int total = 10;
	public void run(){
		while(total>0) {
			try {
				Thread.sleep(10);//加入这个,使得问题暴露的更明显
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(getName() + "卖出一张票,剩余:" + --total);
		}
	}
}

 共享,但是有线程安全问题

4 同一个对象的实例变量共享

public class SaleTicketDemo3 {
	public static void main(String[] args) {
		TicketSaleRunnable tr = new TicketSaleRunnable();
		Thread t1 = new Thread(tr,"窗口一");
		Thread t2 = new Thread(tr,"窗口一");
		Thread t3 = new Thread(tr,"窗口一");
		
		t1.start();
		t2.start();
		t3.start();
	}
}
class TicketSaleRunnable implements Runnable{
	private int total = 10;
	public void run(){
		while(total>0) {
			try {
				Thread.sleep(10);//加入这个,使得问题暴露的更明显
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total);
		}
	}
}

但是存在0票和-1票情况

2 尝试解决线程安全问题

要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制 (synchronized)来解决。

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

同步方法:synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着。

public synchronized void method(){
    可能会产生线程安全问题的代码
}

同步代码块:synchronized 关键字可以用于某个区块前面,表示只对这个区块的资源实行互斥访问。 格式:

3 锁对象选择

同步锁对象:

  • 锁对象可以是任意类型。

  • 多个线程对象 要使用同一把锁。

1 同步方法的锁对象问题

(1)静态方法:当前类的Class对象

(2)非静态方法:this

public class SaleTicketSafeDemo1 {
	public static void main(String[] args) {
		// 2、创建资源对象
		Ticket ticket = new Ticket();

		// 3、启动多个线程操作资源类的对象
		Thread t1 = new Thread("窗口一") {
			public void run() {
				while (true) {
					try {
						Thread.sleep(10);// 加入这个,使得问题暴露的更明显
						ticket.sale();
					} catch (Exception e) {
						e.printStackTrace();
						break;
					}
				}
			}
		};
		Thread t2 = new Thread("窗口二") {
			public void run() {
				while (true) {
					try {
						Thread.sleep(10);// 加入这个,使得问题暴露的更明显
						ticket.sale();
					} catch (Exception e) {
						e.printStackTrace();
						break;
					}
				}
			}
		};
		Thread t3 = new Thread(new Runnable() {
			public void run() {
				while (true) {
					try {
						Thread.sleep(10);// 加入这个,使得问题暴露的更明显
						ticket.sale();
					} catch (Exception e) {
						e.printStackTrace();
						break;
					}
				}
			}
		}, "窗口三");

		t1.start();
		t2.start();
		t3.start();
	}
}

// 1、编写资源类
class Ticket {
	private int total = 10;

	//非静态方法隐含的锁对象就是this
	public synchronized void sale() {
		if (total > 0) {
			System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total);
		} else {
			throw new RuntimeException(Thread.currentThread().getName() + "发现没有票了");
		}
	}

	public int getTotal() {
		return total;
	}
}

2、同步代码块的锁对象

同步锁对象:

  • 锁对象可以是任意类型。

  • 多个线程对象 要使用同一把锁。

  • 习惯上先考虑this,但是要注意是否同一个this

    public class SaleTicketSafeDemo1 {
    	public static void main(String[] args) {
    		// 2、创建资源对象
    		Ticket ticket = new Ticket();
    
    		// 3、启动多个线程操作资源类的对象
    		Thread t1 = new Thread("窗口一") {
    			public void run() {
    				while (true) {
    					try {
    						Thread.sleep(10);// 加入这个,使得问题暴露的更明显
    						ticket.sale();
    					} catch (Exception e) {
    						e.printStackTrace();
    						break;
    					}
    				}
    			}
    		};
    		Thread t2 = new Thread("窗口二") {
    			public void run() {
    				while (true) {
    					try {
    						Thread.sleep(10);// 加入这个,使得问题暴露的更明显
    						ticket.sale();
    					} catch (Exception e) {
    						e.printStackTrace();
    						break;
    					}
    				}
    			}
    		};
    		Thread t3 = new Thread(new Runnable() {
    			public void run() {
    				while (true) {
    					try {
    						Thread.sleep(10);// 加入这个,使得问题暴露的更明显
    						ticket.sale();
    					} catch (Exception e) {
    						e.printStackTrace();
    						break;
    					}
    				}
    			}
    		}, "窗口三");
    
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    
    // 1、编写资源类
    class Ticket {
    	private int total = 10;
    
    	public void sale() {
    		synchronized (this) {
    			if (total > 0) {
    				System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total);
    			} else {
    				throw new RuntimeException(Thread.currentThread().getName() + "发现没有票了");
    			}
    		}
    	}
    
    	public int getTotal() {
    		return total;
    	}
    }
    

    4 锁的范围问题

    锁的范围太小:不能解决安全问题

    锁的范围太大:因为一旦某个线程抢到锁,其他线程就只能等待,所以范围太大,效率会降低,不能合理利用CPU资源。

  • 锁范围太小示例

    public class SaleTicketSafeDemo2 {
    	public static void main(String[] args) {
    		TicketRunnable tr = new TicketRunnable();
    		Thread t1 = new Thread(tr,"窗口一");
    		Thread t2 = new Thread(tr,"窗口二");
    		Thread t3 = new Thread(tr,"窗口三");
    		
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    
    class TicketRunnable implements Runnable {
    	private int ticket = 10;
    
    	@Override
    	public void run() {
    		while(ticket > 0){
    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			synchronized (this) {
    				//if (ticket > 0) {//条件没有锁进去
    					System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
    				//}
    			}
    		}
    	}
    
    }

    锁范围太大示例

    public class SaleTicketSafeDemo3 {
    	public static void main(String[] args) {
    		TicketRunnableDemo tr = new TicketRunnableDemo();
    		Thread t1 = new Thread(tr,"窗口一");
    		Thread t2 = new Thread(tr,"窗口二");
    		Thread t3 = new Thread(tr,"窗口三");
    		
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    
    class TicketRunnableDemo implements Runnable {
    	private int ticket = 10;
    
    	@Override
    	public synchronized void run() {
    		while(ticket > 0){
    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
    		}
    	}
    
    }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_43555873

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值