1. 线程不安全问题
问题描述:在12306上抢票时,如果同一张票分配给了好几个人,或者票数没了却还在售该票。取钱时同时操作同一个账户。这些都是线程不安全问题
//抢票问题
public class UnsafeTest01copy {
public static void main(String[] args) {
Web12306 web12306 = new Web12306();
new Thread(web12306,"Lily").start();
new Thread(web12306,"Mary").start();
new Thread(web12306,"Vicky").start();
}
}
class Web12306 implements Runnable{
private int ticketNums = 10;
private boolean flag = true;
@Override
public void run() {
while (flag){
test();
}
}
public void test(){
if(ticketNums<0){
flag=false;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+ticketNums--);
}
}
结果:如图所示,同一张票有的分给了三个人,并且还出现了负数的情况。这些情况都是不应该出现的问题。
2. 如何解决线程不安全问题
在生活中,我们总是会遇到这样的问题:同一个资源,多个人都想使用。最好的解决办法就是大家排队,前一个用完之后,后一个人再用。
所以当处理多线程问题时,多个线程访问同一个对象,我们就需要用到线程同步。线程同步就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完后,下一个线程再使用。这就是锁机制,当一个线程获得对象的排他锁时,独占资源,其他线程必须等待,使用后释放锁即可。
但是锁机制会有以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。
在Java中,synchronized是用来控制线程同步的,在多线程环境下,用synchronized修饰的代码段不被多个线程同时执行。synchronized包括两种方法:
- synchronized方法
作用的对象是调用这个方法的对象 - synchronized块
作用的对象是调用这个代码块的对象
synchronized(obj){},obj称之为同步监视器
同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器未锁,锁定并访问
修饰一个方法
修饰一个方法就是在方法的前面加入synchronized,例如,public synchronized void mehtod(int args){}
缺陷:若将一个大的方法声明为synchronized将会大大影响效率。
/**
* 线程安全:在并发时保证数据的准确性,效率尽可能高
* synchronized
* 1.同步方法
* 2.同步块
*/
public class SynTest01 {
public static void main(String[] args) {
SafeWeb12306 web = new SafeWeb12306();
new Thread(web,"Mary").start();
new Thread(web,"Lily").start();
new Thread(web,"Vicky").start();
}
}
class SafeWeb12306 implements Runnable{
private int ticketNums = 10;
private boolean flag = true;
@Override
public void run() {
while (flag){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
operate();
}
}
private synchronized void operate() {
if (ticketNums<0){
flag=false;
return;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+ticketNums--);
}
}
结果:
修饰一个对象
这是一个电影院购票的过程
public class HappyCinema {
public static void main(String[] args) {
Cinema c = new Cinema(2,"happy sxt");
new Thread(new Customer(c,2),"老高").start();
new Thread(new Customer(c,1),"老林").start();
}
}
//顾客(多个顾客)
class Customer implements Runnable{
Cinema cinema;//选择哪个影院
int seats;//选择哪个位置
public Customer(Cinema cinema, int seats) {
this.cinema = cinema;
this.seats = seats;
}
@Override
public void run() {
synchronized (cinema){
boolean flag = cinema.bookTickets(seats);
if(flag){
System.out.println("出票成功"+Thread.currentThread().getName()+"-<位置为>"+seats);
}else {
System.out.println("出票失败"+Thread.currentThread().getName()+"-<位置不够>");
}
}
}
}
//影院
class Cinema{
int available;//可用的位置
String name;//名称
public Cinema(int available, String name) {
this.available = available;
this.name = name;
}
//购票(是否购票成功)
public boolean bookTickets(int seats){
System.out.println("可用位置为:"+available);
if(seats>available){
return false;
};
available-=seats;
return true;
}
}
结果:
1.不加synchronized(可以看到不加synchronized的时候,没有锁定线程,导致当第一个线程运行的时候,第二个线程也可以访问)
2.加入synchronized