(1)多线程共享数据引发的问题
多线程实现网络购票,用户提交购票信息后:
①网站修改网站车票数据;
②显示出票反馈信息给用户;
网络购票案例
public class Site implements Runnable {
private int count = 10;// 记录剩余票数
private int num = 0;// 记录当前抢到第几张票
@Override
public void run() {
// 当剩余票数为0时结束;
while (true) {
if (count <= 0) {
break;
}
// 1.修改数据(剩余票数、抢到第几张票)
count--;
num++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 2.显示信息,反馈用户抢到第几张票
System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票!");
}
}
}
public class Test1 {
public static void main(String[] args) {
Site site = new Site();
Thread person1 = new Thread(site, "黄牛1");
Thread person2 = new Thread(site, "黄牛2");
Thread person3 = new Thread(site, "黄牛3");
person1.start();
person2.start();
person3.start();
}
}
测试结果:
发现的问题:
- 不是从第一张票开始;
- 存在多人抢到一张票的情况;
- 有些票号没有被抢到;
原因:
多个线程操作同一共享资源时,将引发数据不安全问题。
解决办法:
使用线程的同步
(2)两种线程的同步
同步方法
使用synchronized修饰的方法控制对类成员变量的访问:(synchronized就是为当前的线程声明一个锁)
public class Sale implements Runnable {
private int count = 10;// 记录剩余票数
private int num = 0;// 记录当前抢到第几张票
private boolean flag = false;// 初始票没有卖完
@Override
public void run() {
// 当剩余票数为0时结束;
while (!flag) {
sy();
}
}
public synchronized void sy() {
if (count <= 0) {
flag = true;
return;
}
// 1.修改数据(剩余票数、抢到第几张票)
count--;
num++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 2.显示信息,反馈用户抢到第几张票
System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票!");
}
}
public class Test {
public static void main(String[] args) {
Sale sale = new Sale();
Thread person1 = new Thread(sale, "黄牛1");
Thread person2 = new Thread(sale, "黄牛2");
Thread person3 = new Thread(sale, "黄牛3");
person1.start();
person2.start();
person3.start();
}
}
同步代码块
public class Sale2 implements Runnable {
private int count = 10;// 记录剩余票数
private int num = 0;// 记录当前抢到第几张票
@Override
public void run() {
// 当剩余票数为0时结束;
while (true) {
synchronized (this) {
if (count <= 0) {
break;
}
// 1.修改数据(剩余票数、抢到第几张票)
count--;
num++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 2.显示信息,反馈用户抢到第几张票
System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票!");
}
}
}
}
public class Test {
public static void main(String[] args) {
//Sale sale = new Sale();
Sale2 sale = new Sale2();
Thread person1 = new Thread(sale, "黄牛1");
Thread person2 = new Thread(sale, "黄牛2");
Thread person3 = new Thread(sale, "黄牛3");
person1.start();
person2.start();
person3.start();
}
}
关于同步代码块的注意事项
- 多个并发线程访问同一资源的同步代码块时:
- 同一时刻只能有一个线程键入synchronized(this)同步代码块;
- 当一个线程访问一个sychronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定;
- 当一个线程访问一个synchronized(this)同步代码块时,其它线程可以访问该资源的非synchronized(this)同步代码;
(3)线程安全类型
查看ArrayList类的add()方法定义
public boolean add(E e) {
ensureCapacitylnternal(size + 1);//集合扩容,确保能新增数据
elementData[size++] = e;//在新增位置存放数据
return true;
}
ArrayList类的add()方法为非同步方法,当多个线程向同一个ArrayList对象添加数据时,可能出现数据不一致问题。
关于线程安全类型与非线程安全类型:
为达到安全性和效率的平衡,可以根据实际场景来选择合适的类型。
常见类型对比: