线程安全案例
多窗口售票
使用实现Runnable接口的方式实现售票
问题1:我们加入了循环和延迟模拟现实生活售票的场景后发现
1. 出现售卖重复的票号 【计算机中cpu的计算是具备原子性的】
2. 出现非法的票号 【随机性导致的,cpu小小的时间片,足以执行很多次】
上述的问题1实际上是属于线程安全的问题。
如何判断一个程序是否存在线程安全的问题呢?
三要素,缺一不可:
1、是否存在多线程环境? 是
2、是否存在共享数据/共享变量?是tickets
3、是否存在多条语句操作着共享数据/共享变量? 是
怎么解决?
方案1:同步代码块
synchronized(对象){需要同步的代码;} 这里对象要保证多个线程对象唯一的
同步方法:锁对象是this
同步静态方法:锁对象是当前类的class文件对象 类.class
方案2:lock锁
使用实现Runnable接口的方式实现售票
synchronized(对象){需要同步的代码;} 这里对象要保证多个线程对象唯一的
点击查看代码
class Window2 implements Runnable{
int tickets = 200;
Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){
if(tickets>0){ // 1
try {
// t1
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前 " + Thread.currentThread().getName()+" 正在出售第 "+(tickets--)+" 张票");
}
}
}
}
}
public class SellTicketsDemo2 {
public static void main(String[] args) {
Window2 window2 = new Window2();
Thread t1 = new Thread(window2, "窗口1");
Thread t2 = new Thread(window2, "窗口2");
Thread t3 = new Thread(window2, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
`同步方法的锁对象`
点击查看代码
class Window3 implements Runnable {
static int tickets = 200;
int i = 0;
@Override
public void run() {
while (true) {
if (i % 2 == 0) {
synchronized (Window3.class) {//同步代码块如果同步方法是一状态的话那么synchronized(this)
//如果同步方法是静态的那么则需要 synchronized (Window3.class)确保线程安全
if (tickets > 0) { // 1
try {
// t1
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
}
}
} else {
sell();//同步方法
}
i++;
}
}
public synchronized static void sell() {
if (tickets > 0) { // 1
try {
// t1
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
}
}
// public synchronized void sell() {
// if (tickets > 0) { // 1
// try {
// // t1
// Thread.sleep(20);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//
// System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
// }
// }
}
public class SellTicketsDemo3 {
public static void main(String[] args) {
Window3 window3 = new Window3();
Thread t1 = new Thread(window3, "窗口1");
Thread t2 = new Thread(window3, "窗口2");
Thread t3 = new Thread(window3, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
使用lock锁来解决线程安全的问题
点击查看代码
class Window4 implements Runnable {
int tickets = 200;
Object obj = new Object();
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 加锁
lock.lock();
if (tickets > 0) { // 1
try {
// t1
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
}
//释放锁
lock.unlock();
}
}
}
public class SellTicketsDemo4 {
public static void main(String[] args) {
Window4 window4 = new Window4();
Thread t1 = new Thread(window4, "窗口1");
Thread t2 = new Thread(window4, "窗口2");
Thread t3 = new Thread(window4, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
等待唤醒机制:
生产者
消费者
共享数据
测试类
等待唤醒机制:前提是要保证程序是线程安全的
生产者
点击查看代码
public class Product extends Thread{
Student s;
int i =0;
public Product(Student s) {
this.s = s;
}
@Override
public void run() {
// Student s = new Student();
while (true){
synchronized (s){
//作为生产者,在生产数据之前,应该先检查一下数据有没有被消费
//如果没有被消费,就等待消费者消费
if(s.flag){
//等待 锁对象调用方法等待
try {
s.wait(); // 程序走到这一步,发生阻塞,直到锁对象再次在程序中被调用了notify()方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(i%2==0){
s.setName("李刚");
s.setAge(18);
}else {
s.setName("钱志强");
s.setAge(10);
}
// 生产完数据后,通知消费者来消费数据
// 由锁对象来通知
s.notify();
s.setFlag(true);
i++;
}
}
}
}
消费者
点击查看代码
public class Consumer extends Thread{
Student s;
public Consumer(Student s) {
this.s = s;
}
@Override
public void run() {
// Student s = new Student();
while (true){
synchronized (s){
//消费者在消费数据之前,应该先看一看数据有没有产生【flag是否是true】
//若没有数据产生,等待生产者生产数据
if(!s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.getName()+"-"+s.getAge());
//消费者消费完数据后,通知生产者生产数据
s.notify();
s.setFlag(false);
}
}
}
}
共享数据
点击查看代码
public class Student {
private String name;
private int age;
boolean flag = false;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类
点击查看代码
public class WaitNotifyDemo1 {
public static void main(String[] args) {
Student s = new Student();
//创建生产者线程对象
Product product = new Product(s);
//创建消费者线程对象
Consumer consumer = new Consumer(s);
product.start();
consumer.start();
}
}