1线程安全问题
如果有多个线程同时运行,而这些线程可能会同时运行这段代码,程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值
和预期的是一样的,就是线程安全的
!!!举个卖票的例子!!!
代码
public class RunnableImpl implements Runnable {
// 定义一个多个线程共享票源
private int ticket = 100;
/**
* 卖票
*/
@Override
public void run() {
while (true) {
// 先判断票是否存在
if (ticket > 0) {
// 提高出现问题的概率
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 存在
System.out.println(Thread.currentThread().getName() + "--->正在卖票" + ticket + "张票");
ticket--;
}
}
}
}
public class DemoTicket {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
// 创建三个线程共同卖票
new Thread(run,"窗口-1").start();
new Thread(run,"窗口-2").start();
new Thread(run,"窗口-3").start();
}
}
!!!出现了2类线程安全问题!!!
问题1——分析出现负票原因
- Thread-2抢到cpu时间片,进入到run方法执行if之后,进入程序睡眠,放弃CPU执行权,Thread-1和Thread-0均是如此
- Thread-2线程醒来,执行ticket– 此时ticket=0,再一次循环 ticket不在大于0,线程Thread-2结束
- Thread-1线程醒来,此时ticket为0,进行ticket–,ticket为-1,再一次循环 ticket不大于0,线程Thread-1结束
- Thread-0线程醒来,此时ticket为-1,进行ticket–,ticket为-2,再一次循环 ticket不大于0,线程Thread-1结束
问题2——分析出现相同票原因
- 三个线程同时执行到了打印那句话,而此时ticket并没有开始自减,所以打印了重复的票
解决线程安全问题
- 保证让一个线程在访问共享资源时候,无论是否失去了CPU时间片,其他线程只能等待
- 等待当前线程拍完票,其他线程在进行卖票
2.线程同步
- Java种提供同步机制(Synchronized)来解决
- 线程1操作时,线程2和3等待,线程1操作结束,线程1和2和3在去竞争CPU时间片来执行线程
三种方式同步操作:
1.同步代码块
synchronized关键字可以用于方法种某个区块中,表示只对这个区块的资源实行互斥访问
对象同步锁只是一个概念,想象在对象上标记一个锁,锁对象可以是任意类型,多个线程对象要使用同一把锁
synchronized(同步锁){
需要同步的操作
}
!!!上代码!!!
public class RunnableImpl implements Runnable {
// 定义一个多个线程共享票源
private int ticket = 30;
// 创建锁对象
Object obj = new Object();
/**
* 卖票
*/
@Override
public void run() {
while (true) {
// 创建同步代码块
synchronized (obj) {
// 先判断票是否存在
if (ticket > 0) {
// 存在
System.out.println(Thread.currentThread().getName() + "--->正在卖票" + ticket + "张票");
ticket--;
}
}
}
}
}
public class DemoTicket {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
// 创建三个线程共同卖票
new Thread(run, "窗口-1").start();
new Thread(run, "窗口-2").start();
new Thread(run, "窗口-3").start();
}
}
!!!执行结果!!!
同步技术原理:
使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器
三个线程一起抢夺CPU执行权,谁抢到了谁执行run方法进行减票
窗口-1抢到了执行权,执行run方法,遇到synchronized代码块,并检查代码块是否有锁对象发现有,获取锁对象并进入到同步中执行
窗口-2抢到了执行权,执行run方法,遇到synchronized代码块,并检查代码块是否有锁对象发现没有,线程进入阻塞会一直等待窗口-1执行完同步中的代码并归还对象锁时,窗口-2才能获取对象锁并进入同步中执行
总结: 同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步方法,程序中频繁判断锁,获取锁,释放锁,效率降低
2.同步方法
使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法时,其他线程在方法外等待
public synchronized void method(){
// 可能会产生线程安全问题的代码
}
同步锁是谁?
对于非static方法,同步锁就是this
对于static方法,我们使用当前方法所在的类字节码对象(类名.class)
public class RunnableImpl implements Runnable {
// 定义一个多个线程共享票源
private int ticket = 100;
/**
* 卖票
*/
@Override
public void run() {
while (true) {
updateTicket();
}
}
// 同步方法
public synchronized void updateTicket() {
// 先判断票是否存在
if (ticket > 0) {
// 存在
System.out.println(Thread.currentThread().getName() + "--->正在卖票" + ticket + "张票");
ticket--;
}
}
}
public class DemoTicket {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
// 创建三个线程共同卖票
new Thread(run, "窗口-1").start();
new Thread(run, "窗口-2").start();
new Thread(run, "窗口-3").start();
}
}
!!!执行结果!!!
结果: 结果依旧不变
3.锁机制(Lock锁)
java.util.concurrent.locks.Lock
Java提供了比同步代码块和同步方法更广泛的锁定操作
Lock锁称为同步锁,加锁与释放锁方法如下
- public void lock(); 加同步锁
- public void unlock(); 释放同步锁
Lock接口提供了多个实现
!!!上代码!!!
public class RunnableImpl implements Runnable {
// 定义一个多个线程共享票源
private int ticket = 100;
// Lock接口获取锁对象 多态
Lock lock = new ReentrantLock();
/**
* 卖票
*/
@Override
public void run() {
while (true) {
// 获取锁
lock.lock();
try {
updateTicket();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 无论业务方法是否发生异常 都释放锁
lock.unlock();
}
}
}
/**
* 使用Lock 调用业务方法
*/
public void updateTicket() throws Exception {
// 先判断票是否存在
if (ticket > 0) {
// 存在
System.out.println(Thread.currentThread().getName() + "--->正在卖票" + ticket + "张票");
ticket--;
}
}
}
!!!查看结果!!!
!!!结果依然正确!!!