第五课 锁

1 线程同步

  • 由于同一进程的多个线程共享同一片存储空间,会引起访问冲突问题。
/**
 * 使用锁模拟12306
 * 
 * @author Administrator
 *
 */
public class ThreadSynchronizedTest {
	public static void main(String[] args) {
		// test12306_SyMethod test = new test12306_SyMethod();
		// test12306_Norm test = new test12306_Norm();
		test12306_SyBlock test = new test12306_SyBlock();
		Thread t1 = new Thread(test, "t1");
		Thread t2 = new Thread(test, "t2");
		Thread t3 = new Thread(test, "t3");

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

}

/*
 * 普通抢购模拟,会出现一张票被多个线程抢到,或抢到-1号票问题
 */
class test12306_Norm implements Runnable {
	private int ticketNum = 20;
	private boolean flag = true;

	@Override
	public void run() {
		while (this.flag) {
			buyTicket();
		}
	}

	public void buyTicket() {
		try {
			Thread.sleep(500);
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (ticketNum <= 0) {
			this.flag = false;
			return;
		}
		System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNum + "号票");
		ticketNum--;
		
	}
}

/*
 * 同步方法
 */
class test12306_SyMethod implements Runnable {
	private int ticketNum = 20;
	private boolean flag = true;

	@Override
	public void run() {
		while (this.flag) {
			buyTicket();
		}
	}

	public synchronized void buyTicket() {
		try {
			Thread.sleep(500);
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (ticketNum <= 0) {
			this.flag = false;
			return;
		}
		System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNum + "号票");
		ticketNum--;
	}
}

/*
 * 同步块
 */
class test12306_SyBlock implements Runnable {
	private int ticketNum = 20;
	private boolean flag = true;

	@Override
	public void run() {
		while (this.flag) {
			buyTicket();
		}
	}

	public void buyTicket() {
		synchronized (ThreadSynchronizedTest.class) {
			try {
				Thread.sleep(500);
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (ticketNum <= 0) {
				this.flag = false;
				return;
			}
			System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNum + "号票");
			ticketNum--;
		}
	}
}

2 同步块

public void test() {
	synchronized (){
		// 线程体
	}
}
  • 同步块可以传入以下几种参数(传参不能为空)

    • 引用类型
    • this
    • 类名.class
  • 参数是this对象和非this对象的区别

参数相同点不同点
this对象都可以实现同步实现同步的同时,会阻塞其他线程获取这个对象的锁,影响执行效率
非this对象都可以实现同步代码块中程序与同步方法是异步的,不与其他锁this同步方法争夺this锁,可以提高执行效率

3 单例设计模式

  • 作用:确保一个类只有一个对象
  • 一般步骤
    • 构造器私有化,避免外部直接创建对象
    • 声明一个私有的静态变量
    • 创建一个对外的公共静态方法访问该变量,如果变量没有对象,创建该对象
  • 懒汉式:Double Checking,私有的静态变量初始化为null
class SingletonPatternSafe {
	// 声明一个私有的静态变量
	private static SingletonPatternSafe instance = null;

	// 构造器私有化,避免外部直接创建对象
	private SingletonPatternSafe() {

	}

	// 创建一个对外的公共静态方法,访问该变量,如果变量没有实例化,创建该对象

	// 最基本的变成同步块
	public static synchronized SingletonPatternSafe getInstance() {
		if (instance == null) {
			try {
				Thread.sleep(100);
			} catch (Exception e) {
				e.printStackTrace();
			}

			instance = new SingletonPatternSafe();
		}
		return instance;
	}

	/*
	 * 如果有ABC三个线程进行访问,则A线程访问时,BC线程需要等待
	 * 如果A线程已经创建了SingletonPatternSafe对象,则BC线程也依旧需要排队等待 效率低下
	 */
	public static SingletonPatternSafe getInstance01() {
		synchronized (SingletonPatternSafe.class) {
			if (instance == null) {
				try {
					Thread.sleep(100);
				} catch (Exception e) {
					e.printStackTrace();
				}

				instance = new SingletonPatternSafe();
			}
			return instance;
		}
	}

	/*
	 * 如果一个线程创建对象成功后,instance不再为空,会直接return 提升效率(Double Checking)
	 */
	public static SingletonPatternSafe getInstance02() {
		// 如果一个线程创建对象成功后,instance不再为空,会直接return
		if (instance == null) {
			synchronized (SingletonPatternSafe.class) {
				if (instance == null) {
					try {
						Thread.sleep(100);
					} catch (Exception e) {
						e.printStackTrace();
					}

					instance = new SingletonPatternSafe();
				}
			}
		}
		return instance;
	}
}
  • 饿汉式:私有的静态变量初始化为对应的对象
/*
 * 基础版本:当调用getInstance方法时,instance变量才应该被实例化
 * 但是基础版本的写法,在调用到SingletonPatternHungry这个类的时候,instance就会被实例化
 */
class SingletonPatternHungryBasic {
	// 声明私有的静态属性,同时创建该对象(懒汉式中不会创建)
	private static SingletonPatternHungryBasic instance = new SingletonPatternHungryBasic();

	// 构造器私有化,防止外部类对其进行实例化
	private SingletonPatternHungryBasic() {

	}

	// 获取对象的实例
	public static SingletonPatternHungryBasic getInstance() {
		return instance;
	}
}

/*
 * 进阶版本:将instance封装在一个内部类当中,只有调用getInstance方法时,instance才会被实例化
 * 单纯使用SingletonPatternHungry中的其他方法时,instance不会被实例化,延缓了加载时机,提高性能
 */
class SingletonPatternHungry {
	// 声明私有的静态属性,同时创建该对象(懒汉式中不会创建)
	private static class SingletonPatternHungryHolder {
		private static SingletonPatternHungry instance = new SingletonPatternHungry();
	}
	
	// 构造器私有化,防止外部类对其进行实例化
	private SingletonPatternHungry() {

	}

	// 获取对象的实例
	public static SingletonPatternHungry getInstance() {
		return SingletonPatternHungryHolder.instance;
	}
}

4 死锁——生产者消费者模式

4.1 模拟死锁现象

  • 过多的同步方法可能造成死锁
  • 多个线程访问同一份资源的时候会出现死锁现象
/**
 * 模拟死锁
 * 
 * @author Administrator
 *
 */
public class ThreadDeadlockTest {
	public static void main(String[] args) {
		Object goods = new Object();
		Object prize = new Object();
		// 两个线程公用一份资源
		Thread01 thread01 = new Thread01(prize, goods);
		Thread02 thread02 = new Thread02(prize, goods);
		thread01.start();
		thread02.start();
	}
}

class Thread01 extends Thread {
	private Object prize;
	private Object goods;

	public Thread01(Object prize, Object goods) {
		this.goods = goods;
		this.prize = prize;
	}

	@Override
	public void run() {
		while (true) {
			test();
		}
	}

	public void test() {
		// Thread01锁住了goods对象,Thread02锁住了prize对象,两者均不释放资源时产生死锁
		synchronized (goods) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (prize) {
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		System.out.println("一手交货");
	}
}

class Thread02 extends Thread {
	private Object prize;
	private Object goods;

	public Thread02(Object prize, Object goods) {
		this.goods = goods;
		this.prize = prize;
	}

	@Override
	public void run() {
		while (true) {
			test();
		}
	}

	public void test() {
		synchronized (prize) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (goods) {
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		System.out.println("一手交钱");
	}
}

4.2 生产者消费者模式(信号灯法)

  • 生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例
    • 生产者:生成一定量的数据放到缓冲区中,重复此过程
    • 消费者:在缓冲区中消耗这些数据
  • 关键问题:保证生产者不会再缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据
  • 解决方案:保证生产者在缓冲区满时休眠,等到消费者消耗掉缓冲区中的数据时再次唤醒生产者进行生产。同样的,缓冲区空时,消费者进入休眠状态,等待生产者向缓冲区中添加完数据后,唤醒消费者。
  • 通常的采用的方法有:信号灯法和管程法
/**
 * 模拟生产者
 * 
 * @author Administrator
 *
 */
public class Player extends Thread {
	private Movie m;

	public Player(Movie m) {
		this.m = m;
	}

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			if (0 == i % 2) {
				m.play("a" + i);
			} else {
				m.play("b" + i);
			}
		}
	}
}


/**
 * 模拟消费者
 * 
 * @author Administrator
 *
 */
public class Watcher extends Thread {
	private Movie m;

	public Watcher(Movie m) {
		this.m = m;
	}

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			m.watch();
		}
	}
}


/**
 * 共同数据部分
 * 生产者的产物和消费者的消费物
 * 
 * @author Administrator
 *
 */
public class Movie {
	private String name;
	// flag --> True 生产者开始生产,消费者停止消费
	// flag --> False 消费者开始消费,生产者停止生产
	private boolean flag = true;

	public synchronized void play(String name) {
		// flag是false的时候,不再生产,生产挂起
		if (!flag) {
			try {
				this.wait(); // wait方法会释放锁,而sleep方法是持锁
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		// 模拟生产过程
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.name = name;
		// 生产结束后,将flag置成true,准备唤醒等待的消费者线程
		this.flag = false;
		// 唤醒消费者线程
		this.notify();
		
	}

	public synchronized void watch() {
		// flag是true的时候,不再消费,消费挂起
		if (flag) {
			try {
				this.wait();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(this.name);
		this.flag = true;
		this.notify();
	}
}



/**
 * 最终测试
 * 
 * @author Administrator
 *
 */
public class App {
	public static void main(String[] args) {
		Movie m = new Movie();
		// 两个线程同时持有共同的m对象
		Player player = new Player(m);
		Watcher watcher = new Watcher(m);

		player.start();
		watcher.start();

	}
}

5 任务调度(Timer类)

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class ThreadScheduleTest {
	public static void main(String[] args) {
		Timer timer = new Timer();
		
		// schedule(线程体,开始时间,多长时间运行一次线程体)
		timer.schedule(new TimerTask() {
			@Override
			public void run() {
				System.out.println("aaa");
			}
		}, new Date(System.currentTimeMillis() + 5000), 1000);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值