线程安全
我们先来说说线程安全问题是什么?
线程安全问题其实就是多个线程同时访问一个资源时,会导致程序运行结果并不是想看到的结果。所以我们建议在没有使用解决方案的时候尽量只读不写
首先我们写了一个简单的多线程问题
package experiment;
class tre implements Runnable {
private int piao=100;
@Override
public void run() {
while(true){
if(piao>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖了第"+piao--+"张票");
}
}
}
}
public class B {
public static void main(String[] args) {
tre tr1=new tre();
tre tr2=new tre();
tre tr3=new tre();
Thread thread=new Thread(tr1,"窗口1");
Thread thread2=new Thread(tr1,"窗口2");
Thread thread3=new Thread(tr1,"窗口3");
thread.start();
thread2.start();
thread3.start();
}
}
结果如下:
为什么会出现这种问题?
我们来分析以下代码,看下面这一部分就可以了
@Override
public void run() {
while(true){
if(piao>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖了第"+piao--+"张票");
}
}
}
我们都知道CPU是抢占式调度资源的,也就是说CPU在微观下其实是在多个线程之间高速切换的,那么出现问题的原因也就是,当我们的一个线程执行到打印语句的时候还没有进行减减操作,CPU切换到了另外一个线程,另外一个线程也开始执行这一个代码块,但是这个时候我们还没有减,所以也就造成了这种原因。
出现-1这个问题也是同样的道理,当我们的其中两个线程都没有执行到减减操作的时候,我们的第三个线程已经过了if判断了,但是这个时候CPU又切换到了另外的线程执行减减操作,但是这个时候线程已经没有if判断了,所以也就造成了-1的情况
解决方案
如何去解决这种情况呢?
我们有三种方法可以去解决
- 同步代码块
- 同步方法
- Lock
同步代码块
同步锁
同步锁其实就是给需要同步的代码上了一把锁,每次只能有一个线程进入。锁只是一个概念。
我们可以将需要同步的代码放进synchronized代码块里面
语法格式:synchronized(任意一个对象){同步代码}
class tre implements Runnable {
private int piao=100;
@Override
public void run() {
while(true){
if(piao>0){
synchronized(tre.class){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖了第"+piao--+"张票");
}
}
}
}
}
这里也可以放this也就是实例化对象,但是需要注意的是
同一个类必须使用同一个锁,否则我们在实例化多个对象的时候就会出现多把锁从而造成线程安全问题
tre.class指的是类对象,这样不管我们实例化多少个对象都不会有上面的问题
同步方法
同步方法就是通过synchronized关键字去修饰一个方法
语法格式就是在方法的前面加上synchronized关键字
class tre implements Runnable {
private int piao = 100;
@Override
public synchronized void run() {
while (true) {
if (piao > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖了第" + piao-- + "张票");
}
}
}
}
Lock
Lock里面有几个方法我们先来看看
void lock() 获得锁。如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获取锁。
void lockInterruptibly() 获取锁,如果可用并立即返回。如果锁不可用,那么当前线程将被禁用以进行线程调度,并且处于休眠状态,和lock()方法不同的是在锁的获取中可以中断当前线程(相应中断)。
Condition newCondition() 获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁。
boolean tryLock() 只有在调用时才可以获得锁。如果可用,则获取锁定,并立即返回值为true;如果锁不可用,则此方法将立即返回值为false 。
boolean tryLock(long time, TimeUnit unit) 超时获取锁,当前线程在一下三种情况下会返回: 1. 当前线程在超时时间内获得了锁;2.当前线程在超时时间内被中断;3.超时时间结束,返回false.
void unlock() 释放锁。
知道了这些方法之后我们来看看怎么改进,
- Lock是一个接口,首先我们需要创建一个Lock接口的实现类ReentrantLock
- 然后我们要在需要同步的代码前面"获取锁"
- 然后再后面不用同步的代码后面"释放锁"
class tre implements Runnable {
private int piao = 100;
private Lock l =new ReentrantLock();
@Override
public void run() {
while (true) {
l.lock();
if (piao > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖了第" + piao-- + "张票");
}
l.unlock();
}
}
}
有任何不对的地方,欢迎提出改正!!感谢阅读