先看有问题的源码
packageSell;public class SellTicket implementsRunnable {private int tickets = 100;private Object obj = newObject();
@Overridepublic voidrun() {while (true) {if (tickets > 0) {//通过sleep()方法来模拟出票时间
try{
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}
packageSell;public classText {public static voidmain(String[] args) {
SellTicket sellTicket= newSellTicket();
Thread s1= new Thread(sellTicket,"小王1");
Thread s2= new Thread(sellTicket,"小王2");
Thread s3= new Thread(sellTicket,"小王3");
s1.start();
s2.start();
s3.start();
}
}
运行结果如下
可以看到三个人卖同一张票,还有票数应该是从大到小顺序卖的,少了些票,这是为什么呢?
这是因为线程线程执行的随机性导致的
先看在测试类中创建了多个线程,用Runnable接口实现了多线程,重写了Run方法
线程要执行先要先有执行资格即启动线程,想要继续执行就必须要抢到CPU时间片,也就是使用权,才可以执行命令,如果是多线程,就可能出现不同线程抢到不同时间片的执行权而导致以上的这种卖票重复情况
while (true) {//假设t1线程抢到CPU的执行权,进行下一步
if (tickets > 0) {//通过sleep()方法来模拟出票时间
try{
Thread.sleep(100);//t1线程休息100毫秒,其他的线程有执行资格,会抢夺CPU的执行权//t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里的时候,t2线程休息100毫秒//t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒
} catch(InterruptedException e) {
e.printStackTrace();
}//假设线程按照顺序醒过来,//t1抢到CPU的执行权,在控制台输出:小王1正在出售第100张票
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");//t2抢到CPU的执行权,在控制台输出:小王2正在出售第100张票//t3抢到CPU的执行权,在控制台输出:小王3正在出售第100张票
tickets--;//如果这三个线程还是按照顺序来,这里就执行了3次--的操作,也就是三个窗口拿到了同一张票,总票数100-3张出售的票,剩余97张票,同理后面出线重复票也是这样的
}
}
如何解决此类安全问题 ?
安全问题出现必定有三个条件
是多线程环境
有共享数据
有多条语句操作共享数据
同理解决问题也是从这三个条件入手,多线程环境和有共享数据,无法入手,再看源码中,也就只能从第三个条件入手
既然都是执行同一串代码,我们只需要将此刻只让一条线程来执行,让其他的线程执行不了,也就把它锁起来,等到执行完解开锁,让下个有执行权的线程进入在锁住。
Java提供三种方法用来解决此类安全问题
同步代码块
synchronized(任意对象) {
多条语句操作共享数据的代码
}
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
解决代码示例
@Overridepublic voidrun() {while (true) {synchronized(obj) {//t1进来后,就会把这段代码给锁起来
if (tickets > 0) {try{
Thread.sleep(100);//t1休息100毫秒
} catch(InterruptedException e) {
e.printStackTrace();
}//小王1正在出售第100张票
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--; //tickets = 99;
}
}//t1出来了,这段代码的锁就被释放了
}
}
同步方法
同步方法的格式
同步方法:就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
同步方法的锁对象是什么呢?
this
静态同步方法
同步静态方法:就是把synchronized关键字加到静态方法上
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}
同步静态方法的锁对象是什么呢?
类名.class
解决代码示例
@Overridepublic voidrun() {while (true) {
sellTicket();
}
}//静态同步方法
private static synchronized voidsellTicket() {if (tickets > 0) {try{
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "正在出售第" + tickets + "张票");
tickets--;
}
}
Lock锁
JDK5以后提供了一个新的锁对象Lock,清晰的表达如何加锁和释放锁
ReentrantLock构造方法
方法名说明
ReentrantLock()
创建一个ReentrantLock的实例
加锁解锁方法
方法名说明
void lock()
获得锁
void unlock()
释放锁
@Overridepublic voidrun() {while (true) {try{
lock.lock();if (tickets > 0) {try{
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "正在出售第" + tickets + "张票");
tickets--;
}
}finally{
lock.unlock();
}
}
}
原文:https://www.cnblogs.com/521521cm/p/14504958.html