Java 线程安全问题
首先我们引出一个卖票问题,假设一个电影现剩余10张票要卖,这个任务有三个人要抢着做,我们对任务中的票数的操作如何才能实现安全访问。
- 首先看一下没加任何约束的情况。
package com.kkb.task4_5;
public class Test {
public static void main(String[] args) {
//线程不安全
Runnable runnable = new Ticket();
new Thread(runnable).start();//创建第一个线程并执行
new Thread(runnable).start();//创建第二个线程并执行
new Thread(runnable).start();//创建第三个线程并执行
}
static class Ticket implements Runnable{
private int count =10;//10张票
@Override
public void run() {
while (count > 0) {
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("票已出售成功!余票:" + count);
}
}
}
}
这时候的输出结果顺序比较乱,而且count值得改变也不合理。我们拿一种输出情况做解释。
这种情况下我们发现count的值还有-1和-2.我们在代码中是明确加入了while判断语句进行判断的。这种情况下问题出在哪里呢?
解释:当还有一张票剩下时,这时线程1,线程2,线程3同时进行了whlie判断,发现都能进入循环,这时线程1将count设置为了0,并进行了输出打印,二线程2在线程1改变count值的基础上又进行修改和输出,线程3在线程2修改的count值基础上也进行了修改和输出。这时我们发现不加任何约束是不安全的,我们如何能够实现安全的卖票呢?
1.解决方案1,同步代码块
格式:synchroized(锁对象){//任何对象都可以作为锁对象,但是同步的线程要共用一个锁对象
}
我们先创建一个suo类,里面无需声明任何属性。也可以直接Object s= new Object();创建一个
package com.kkb.task4_5;
public class Test {
public static void main(String[] args) {
//线程不安全
Runnable runnable = new Ticket();
new Thread(runnable).start();//创建第一个线程并执行
new Thread(runnable).start();//创建第二个线程并执行
new Thread(runnable).start();//创建第三个线程并执行
}
static class Ticket implements Runnable{
private int count =10;//10张票
suo s =new suo();
//Object object =new Object();
@Override
public void run() {
while (true) {
synchronized (s) {//将下面代码锁住,一次只能一个线程操作
if (count > 0) {
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "票已出售成功!余票:" + count);
}
}
}
}
}
}
输出结果
这时我们发现三个线程是排队进行售票操作的,如果一个线程抢到锁就会将代码块锁住,直到执行完操作后释放锁,再另一个进程抢到锁,以此类推,直到count值为0.
2.线程安全2-同步方法
就是将要锁的代码放到一个方法里面。
package com.kkb.task4_5;
public class Test {
public static void main(String[] args) {
//线程不安全
Runnable runnable = new Ticket();
new Thread(runnable).start();//创建第一个线程并执行
new Thread(runnable).start();//创建第二个线程并执行
new Thread(runnable).start();//创建第三个线程并执行
}
static class Ticket implements Runnable{
private int count =10;//10张票
//suo s =new suo();
//Object object =new Object();
@Override
public void run() {
while (true) {
Boolean falg = sale();
if (!falg){
break;
}
}
}
public synchronized Boolean sale() {
if (count > 0) {
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "票已出售成功!余票:" + count);
return true;
}
return false;
}
}
}
输出结果
注意:当锁的方法不是static修饰时,锁为对象this,就是上面创建的runnnable,否则为类.class字节码文件对象,就是Tecket.class字节码文件对象。当代码块和方法共用this锁对象时,代码块和方法区一定时间内(线程占用锁的时间)只能被这个拥有锁的线程访问。
3.线程安全3-显示锁Lock
-
同步代码块和方法区都是隐示锁
首先通过Lock l = new ReentrantLock();创建一把锁。
在需要同步执行的代码区进行上锁和解锁操作即可。
package com.kkb.task4_5;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockText {
public static void main(String[] args) {
Runnable runnable =new Ticket();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
static class Ticket implements Runnable{
private int count =10;
private Lock l = new ReentrantLock();
@Override
public void run() {
while (true){
l.lock();//锁上
if (count>0){
System.out.println("开始卖票---");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"售票成功!剩余的票数为:"+count);
}
l.unlock();//解锁
}
}
}
}
执行结果
显示锁Lock和隐示锁synchronized的区别:
: