我们使用多线程进行卖票,代码如下:
class Ticket implements Runnable
{
private int num = 50;
public void run()
{
sale();
}
public void sale()
{
while(true)
{
if(num>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"..."+num--);
}
}
}
}
public class Demo
{
public static void main(String[] args)
{
Ticket st = new Ticket();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
Thread t3 = new Thread(st);
Thread t4 = new Thread(st);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果:
Thread-1...50
Thread-3...48
Thread-2...49
Thread-0...47
Thread-2...46
Thread-3...45
Thread-1...46
Thread-0...44
Thread-2...43
Thread-0...42
Thread-1...43
Thread-3...43
Thread-0...41
Thread-2...38
Thread-3...40
Thread-1...39
Thread-1...37
Thread-2...37
Thread-0...37
Thread-3...37
Thread-3...36
Thread-2...34
Thread-1...33
Thread-0...35
Thread-0...32
Thread-2...30
Thread-3...29
Thread-1...31
Thread-2...28
Thread-1...27
Thread-0...28
Thread-3...28
Thread-2...26
Thread-0...25
Thread-1...25
Thread-3...26
Thread-2...24
Thread-3...22
Thread-0...23
Thread-1...24
Thread-2...21
Thread-0...20
Thread-1...20
Thread-3...20
Thread-3...19
Thread-1...18
Thread-2...16
Thread-0...17
Thread-3...15
Thread-1...15
Thread-0...14
Thread-2...13
Thread-3...12
Thread-1...12
Thread-0...11
Thread-2...10
Thread-3...9
Thread-1...9
Thread-0...8
Thread-2...7
Thread-1...6
Thread-3...6
Thread-2...5
Thread-0...4
Thread-1...3
Thread-3...3
Thread-2...2
Thread-0...2
Thread-1...1
Thread-3...0
Thread-2...-1
Thread-0...-2
我们发现最后三个分别是0 -1 -2 票的号码怎么能为负数和零呢,明显出现了问题。
首先多线程运行就是通过CPU随机在这几个线程之间进行切换,每个线程可能在运行某个代码是被剥夺运行权利,这就可能引发
线程同步安全问题
分析sale函数:
public void sale()
{
while(true)
{
if(num>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"..."+num--);
}
}
}
假设当前num为20
当线程0进行if条件判断 20当然大于0 所以线程0进入了if语句中,但这时CPU剥夺线程0的运行资格,切换到了线程1,
线程1开始运行,此时num仍是20,线程1进行if判断 20大于0 满足if条件进入if语句,同样此时CPU剥夺了线程1的运行资格,
切换到了线程2.。。。
就这样所有的线程都进入了if语句中CPU切换到了线程0,线程0 1 2 3此时持有的num均为20
这样输出时可能会出现一张票会被多次购买或者出现负数票
这样的多线程是很不安全的,我们发现问题的根源就出在 当某个线程对共享资源进行处理时可能会被CPU暂停然后被其他线程
处理这个资源,这样容易引起混乱。
我们设想,如果当某个线程处理共享资源时,其他线程不能进入处理共享资源,这有当之前的线程处理完毕后,其他线程才能进入处理,好比是一把锁,线程拿到钥匙后才能进入,其他人没有钥匙无法进入,当有钥匙的人出来,将钥匙给其他人,其他人才能进入。
java为我们提供了同步代码块,在这个区间内只能有一个线程。
格式如下:
synchronized(锁对象){需要进行同步的代码块}
这样我们把之前的代码修改一下
class Ticket implements Runnable
{
private int num = 50;
Object obj = new Object(); //作为同步锁
public void run()
{
sale();
}
public void sale()
{
while(true)
{
synchronized(obj)
{
if(num>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"..."+num--);
}
}
}
}
}
public class Demo
{
public static void main(String[] args)
{
Ticket st = new Ticket();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
Thread t3 = new Thread(st);
Thread t4 = new Thread(st);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
Thread-0...50
Thread-3...49
Thread-3...48
Thread-3...47
Thread-3...46
Thread-3...45
Thread-3...44
Thread-3...43
Thread-3...42
Thread-3...41
Thread-3...40
Thread-3...39
Thread-3...38
Thread-3...37
Thread-3...36
Thread-3...35
Thread-3...34
Thread-3...33
Thread-3...32
Thread-3...31
Thread-3...30
Thread-3...29
Thread-3...28
Thread-3...27
Thread-3...26
Thread-3...25
Thread-3...24
Thread-3...23
Thread-3...22
Thread-3...21
Thread-3...20
Thread-3...19
Thread-3...18
Thread-3...17
Thread-3...16
Thread-3...15
Thread-3...14
Thread-3...13
Thread-3...12
Thread-3...11
Thread-3...10
Thread-3...9
Thread-3...8
Thread-3...7
Thread-3...6
Thread-3...5
Thread-3...4
Thread-3...3
Thread-3...2
Thread-3...1
除了同步代码块 还可以使用同步函数:
class Ticket implements Runnable
{
private int num = 50;
Object obj = new Object();
public void run()
{
//sale();
while(true)
{
sale2();
}
}
public synchronized void sale2() //同步函数
{
if(num>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"..."+num--);
}
}
public void sale() //使用同步代码块的函数
{
while(true)
{
synchronized(this)
{
if(num>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"..."+num--);
}
}
}
}
}
public class Demo
{
public static void main(String[] args)
{
Ticket st = new Ticket();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
Thread t3 = new Thread(st);
Thread t4 = new Thread(st);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
同步函数的同步锁就是调用他的当前对象,就是this
因此我们在同步代码块中也可以不用创建对象作为同步锁,可以直接使用this
除了同步函数还有静态同步函数,静态同步函数的同步锁是 字节码文件对象 可以通过 对象名.getClass()
或者 类名.class 产生这个字节码文件对象
单例模式下的同步问题
class Single //饿汉式 不存在安全问题
{
private static final Single s = new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}
class Single //懒汉式 存在安全问题
{
private static Single s = null;
private Single(){}
public static Single getInstance()
{
if(s==null)
s = new Single();
return s;
}
}
需要加上同步锁
class Single
{
private static Single s = null;
private Single(){}
public static synchronized Single getInstance()
{
if(s==null)
s = new Single();
return s;
}
}