线程的安全问题
我们已经掌握了线程的开启方式,多线程的出现也会带来一系列的问题,看一个卖票的例子:我们模仿窗口售票的模式,同时开启3个窗口售卖总计100张门票:
public class Ticket implements Runnable {
int ticket = 100;
@Override
public void run() {
while(ticket>0){
System.out.println("第"+ticket+"张票已经售出");
ticket--;
}
}
public static void main(String[] args) {
Ticket t1 = new Ticket();
new Thread(t1).start();
new Thread(t1).start();
new Thread(t1).start();
}
}
截取一段运行结果如下:
仔细分析,发现好像哪里出了问题,没错,我们没有让买票的信息进行同步,这样的卖票方式显然是不合理的。那么如何避免这样的问题呢,下面介绍3种方便的解决方式:
1、同步代码块
格式:
synchronized(锁对象){
可能出现线程安全问题的代码
访问了共享数据的代码
}
注意:
1、锁对象可以是任意对象
2、必须保证多个线程使用的锁对象是同一个
3、锁对象的作用:把同步代码块锁住,只让一个代码块在同步代码块中执行
使用:
1、创建一个锁对象,在run方法外面
2、创建同步代码块
尝试一下:
@Override
public void run() {
synchronized (obj) {
while (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第" + ticket + "张票已经售出");
ticket--;
}
}
}
我们再来查看一下结果:
这样我们就完成了一个正确的模拟窗口售卖的过程
2、同步方法
我们可以将访问了共享数据的代码抽出,放入一个方法中,在方法前添加synchronized
关键字,同步方法也会把方法代码内部锁住,锁对象是this
如果是静态的同步方法,锁对象不能是this,锁对象是本类的class属性,class文件对象(反射)
所以简单来说,同步方法的锁对象是this,而同步方法块需要自定义锁对象
我们把重写的run方法加上关键字synchronized
:
@Override
public synchronized void run() {
while (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第" + ticket + "张票已经售出");
ticket--;
}
}
使用这种方法同样也可以解决线程安全的问题
3、Lock
解决线程安全的第三个方法:使用Lock锁
java.util.concurrent.locks.Lock接口
Lock实现提供了比synchronized方法和语句更广泛的锁定操作
Lock接口中的方法:
void lock();获取锁
void unlock();释放锁
我们使用它的实现类来完成需求:java.util.concurrent.locks.ReentrantLock implments Lock
使用步骤:
1、在成员位置创建一个Lock的实现对象 ReentrantLock
2、在可能会出现安全问题的代码前调用Lock接口中的方法lcok获取锁
3、在代码后,调用unLock释放锁
来看实现:
public class Ticket implements Runnable {
int ticket = 100;
Lock l = new ReentrantLock();
@Override
public void run() {
l.lock();
while (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第" + ticket + "张票已经售出");
ticket--;
}
l.unlock();
}
}
使用Lock解决问题同样简单
生产者消费者问题
最后我们用所学的知识完成一个简单的生产者消费者问题:
public class shengchanzhehexiaofeizhe {
public static void main(String[] args) {
Object obj = new Object();
new Thread(){
@Override
public void run() {
int cnt =1;
while(true){
System.out.println("顾客"+cnt+"正在下单");
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("顾客"+cnt+"提交了订单");
synchronized (obj){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("顾客"+cnt+"得到了食物,离开了餐厅");
System.out.println("================================");
cnt++;
}
}
}.start();
new Thread(){
@Override
public void run() {
while (true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("商家完成了订单,交给顾客");
synchronized (obj){
obj.notify();
}
}
}
}.start();
}
}