学习笔记之JavaSE(24)--多线程4

今天学习的内容是同步和死锁


一、同步

通过之前的学习我们知道,多个线程可以共享同一个JVM进程的资源。但你永远都不知道一个线程何时在运行,这就有可能出现问题:当多个线程同时访问共享资源时,就有可能造成线程之间的冲突,或者称为线程安全问题。实际上,当一个线程在执行操作共享数据的多条代码时,其它线程也执行了操作共享数据的代码,这时就会产生线程安全问题。首先来看下面的例子:

public class TicketsDemo {

	public static void main(String[] args){
		Runnable mission = new Ticket();
		Thread t1 = new Thread(mission,"1");
		Thread t2 = new Thread(mission,"2");
		Thread t3 = new Thread(mission,"3");
		Thread t4 = new Thread(mission,"4");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

class Ticket implements Runnable{
	private int number=10;
	
	public void run(){
		sell();
	}
	
	public void sell(){
		while(true){
			if(number>0){
				try{
					Thread.sleep(200);
				}catch (InterruptedException e){
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"号窗口卖出一张票,剩余票数:"+(--number));
			}
		}
	}
}


程序运行结果:



那么如何解决线程安全问题呢?答案就是Java同步机制:将操作共享资源的代码“锁起来”,在当前线程执行完这段代码之前,其他想要执行这段代码的线程都会进入阻塞状态。可以使用synchronized关键字将操作共享数据的代码封装为一个同步代码块,或者封装成一个用synchronized关键字修饰的同步方法。下面使用同步代码块解决上述问题:

public class TicketsDemo {

	public static void main(String[] args){
		Runnable mission = new Ticket();
		Thread t1 = new Thread(mission,"1");
		Thread t2 = new Thread(mission,"2");
		Thread t3 = new Thread(mission,"3");
		Thread t4 = new Thread(mission,"4");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

class Ticket implements Runnable{
	private int number=10;
	
	public void run(){
		sell();
	}
	
	public void sell(){
		while(true){
			//同步代码块
			synchronized (this) {
				if(number>0){
					try{
						Thread.sleep(200);
					}catch (InterruptedException e){
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"号窗口卖出一张票,剩余票数:"+(--number));
				}
			}
		}
	}
}

程序运行结果:



OK!问题解决了!总结一下同步的知识点:

  • 同步代码块的格式:synchronized(对象){需要同步的代码}
  • 同步方法的格式: synchronized  返回值类型 方法名(){}
  • 同步代码块和同步方法中的代码称为同步代码
  • 同步的原理:所有对象都自动含有一把锁和一把钥匙,当线程A访问某一对象的任意同步代码时,会将钥匙拿走,并将该对象的所有同步代码都锁上。此时其它线程没有钥匙,无法访问此对象的同步代码,只能进入阻塞状态,等待线程A执行完毕
  • 同步代码块的对象参数指定了使用哪个对象的锁,通常操作谁的资源用谁的锁(比如Servlet中的this.getServletContext(),操作ServletContext对象的资源,就用ServletContext的锁
  • 同步方法一定使用this的锁,所以同步方法可以视作对象参数为this的同步代码块的简写
  • 实际开发中通常使用同步代码块(由于不一定使用this的锁)
  • 静态方法也可以被同步化,使用的是类的字节码对象的锁,字节码对象可以通过this.getClass()或类名.class获取
  • 同步的弊端:线程在执行同步代码之前要查询锁和钥匙的状态,造成性能上的损耗;强制线程排队执行同步代码,违背了并发的原则;可能会造成死锁
下面再看一个使用同步方法解决并发性问题的示例:
public class Test40 {

	public static void main(String[] args){
		Runnable r = new MyRunnable();
		Thread t1 = new Thread(r,"甲");
		Thread t2 = new Thread(r,"乙");
		t1.start();
		t2.start();
	}
}
 class Bank{
	 private int sum;
	 
	 public synchronized void add(int num){//同步方法
		 sum = sum + num;
		 try{
			 Thread.sleep(200);
		 } catch(InterruptedException e){
			 e.printStackTrace();
		 }
		 System.out.println("用户"+Thread.currentThread().getName()+"存完钱之后,银行金库总数为:"+sum);
	 }
 }
 
 class MyRunnable implements Runnable{
	 private Bank b = new Bank();
	 
	 public void run(){
		 for(int i=0; i<3; i++){
			 b.add(100);
		 }
	 }
 }


上述实例都是多个线程执行相同任务时出现的线程安全问题,其实只要多个线程都执行操作共享数据的代码,即使它们的任务不同,也会产生线程安全问题。比如多个用户的请求(每个请求独占一条线程)都操作ServletContext的属性,也会发生线程安全问题,解决这种问题的方法同样是同步,注意要将每个线程操作共享数据的代码都同步化,此方法才奏效

如下面的程序示例:
public class ThreadCommunicationTest{
	public static void main(String[] args){
		Resource r = new Resource();//资源
		Input in = new Input(r);//任务1
		Output out = new Output(r);//任务2
		Thread t1 = new Thread(in,"线程1");//线程1
		Thread t2 = new Thread(out,"线程2");//线程2
		t1.start();
		t2.start();
	}
}

//资源
class Resource{
	private String name;
	private String sex;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	
}

//输入
class Input implements Runnable{
	private Resource r;
	
	public Input(){}
	public Input(Resource r){
		this.r = r;
	}
	
	public void run(){
		int x = 0;
		while(true){
			synchronized (r) {//所有操作共享数据的代码都要同步
				if(x==0){
					r.setName("k");
					r.setSex("男");
				}else{
					r.setName("q");
					r.setSex("女");
				}
			}
			x = (x+1)%2;
		} 
	}
}

//输出
class Output implements Runnable{
	private Resource r ;
	
	public Output(){}
	public Output(Resource r){
		this.r = r;
	}
	
	public void run(){
		while(true){
			synchronized (r) {//所有操作共享数据的代码都要同步
				System.out.println(r.getName()+":"+r.getSex());
			}
		}
	}
}


二、死锁

如果在程序中存在两个线程和两把锁,并且使用同步嵌套,就可能引发死锁问题。死锁问题实质上就是两个线程都想拿到各自所持有的钥匙,僵持不下。死锁问题无法解决,所以要尽量避免同步嵌套的使用。面试重点:设计一个死锁程序:
public class DeadLockTest {

	public static void main(String[] args){
		DeadLock d1 = new DeadLock(true);
		DeadLock d2 = new DeadLock(false);
		//两个线程
		Thread t1 = new Thread(d1,"1");
		Thread t2 = new Thread(d2,"2");
		t1.start();
		t2.start();
	}
}

class DeadLock implements Runnable{
	private boolean flag;
	
	public DeadLock(){}
	public DeadLock(boolean flag){
		this.flag = flag;
	}
	
	public void run(){
		if(flag){
			while(true){//为了确保发生死锁现象
				synchronized (MyLock.locka) {
					System.out.println("线程"+Thread.currentThread().getName()+"拿到了locka的钥匙");
					System.out.println("线程"+Thread.currentThread().getName()+"准备拿lockb的钥匙");
					synchronized (MyLock.lockb) {
						System.out.println("线程"+Thread.currentThread().getName()+"拿到了lockb的钥匙");
					}
				}
			}
		}else{
			while(true){
				synchronized (MyLock.lockb) {
					System.out.println("线程"+Thread.currentThread().getName()+"拿到了lockb的钥匙");
					System.out.println("线程"+Thread.currentThread().getName()+"准备拿locka的钥匙");
					synchronized (MyLock.locka) {
						System.out.println("线程"+Thread.currentThread().getName()+"拿到了locka的钥匙");
					}
				}
			}
		}
	}
}

class MyLock{
	//两个锁
	public static final Object locka = new Object();
	public static final Object lockb = new Object();

}

程序运行结果:


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值