多线程之 线程安全
Posted xiangshaui
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程之 线程安全相关的知识,希望对你有一定的参考价值。
一.线程安全出现原因:
原因: 原本不应该拆开的两个步骤中间,被其他线程插足。
解决方案:(java中的同步机制 [synchronized] 来解决),具体有下面三种
a. 同步代码块
b. 同步方法
c. Lock接口
创建:Lock lock = new ReentrantLock();
霸占锁:lock();
释放锁:unlock();
代码演示线程安全问题:
public class Ticket implements Runnable{ //定义一个成员变量,表示还有多少张票 int num = 100; //要在run方法中定义卖票的任务 @Override public void run() { //因为要一直卖票,所以使用死循环 while (true) { //如果还有票,那么就卖票 if(num > 0) { try { Thread.sleep(20); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "正在卖票:" + num); num--; } } } }
public class Demo01TicketTest { public static void main(String[] args) { //创建三个线程,执行这个卖票任务 Ticket t = new Ticket(); //等同于创建了3个窗口,每个窗口都执行一样的任务:售票 new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } }
上面代码Demo01TicketTest,运行后可在控制台查看:
1.出现了票 0 ,-1
2.还有售出重复的票
这就是多个线程会出现的线程安全问题,而在现实中这是不允许的
二.解决线程安全的同步代码块方法
同步代码块: synchronized关键字可以用于方法中的某个区块中,表示支队这个区块的资源实行互斥访问
格式: synchronized( 同步锁 ){
需要同步操作的代码
}
同步锁: 对象的同步锁知识一个概念,就是个标记,(就好像狮子占地盘,撒了尿这就是我的地盘)
1. 锁对象,可以是任意类型
2. 多个线程对象,要使用同一把锁
注意: 一个同步锁最多被一个线程锁拥有,谁拿到锁就可以进入代码块,其他的线程只能外面等待,等待同步锁被上一个线程释放后,再进行抢占cpu
使用此方法解决,上面多线程售票安全问题
public class Ticket implements Runnable{ int num = 100; //定义一个对象,这个对象表示锁对象 //锁对象没有特殊的含义,只是做一个标记 //多个线程必须使用同一个锁对象,否则不能保证线程安全。 Object lock = new Object(); @Override public void run() { while (true) { //当线程执行到synchronized这句代码的时候,会看一下这个上面还有没有锁 //如果同步代码块上面还有锁,那么线程就获取到这个锁, 然后进入到同步代码块中。 //如果同步代码块上面没有锁, 那么这个线程就一直在这个位置等着获取锁, 如果锁一直不来,那么就一直等着. synchronized (lock) { if(num > 0) { try { Thread.sleep(20); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "正在卖票:" + num); num--; } } //当一个线程离开同步代码块,那么他会释放锁。 //之后其他线程就可以去竞争这个锁,谁抢到了,那么谁就去执行这个同步代码块。 } } }
三.解决线程安全的同步方法方法
同步方法: 使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外面等着
格式: public synchronized void method(){
可能会产生线程安全问题的代码
}
注意: 同步方法中也有同步锁
对于非static方法,同步锁就是this,
对于static方法,同步锁是类名.class (我们使用当前方法所在类的字节码对象)
使用同步方法代码解决售票的线程安全问题:
package cn.itcast.demo02; @SuppressWarnings("all") public class Ticket implements Runnable{ int num = 100; @Override public void run() { while (true) { sell(); } } //同步方法,表示对整个方法体都加上了同步代码块 public synchronized void sell() { if(num > 0) { try { Thread.sleep(20); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "正在卖票:" + num); num--; } } }
四.解决线程安全的Lock接口方法
Lock锁: lock机制提供了比synchronized代码块和synchronized方法更加广泛的操作
Lock,更加强大,更加体现面向对象
Lock锁也成为同步锁,将加锁和释放所拆分两个方法,如下:
public void lock(): 加同步锁
public void unlock(): 释放同步锁
使用该方法解决多线程售票安全问题
public class Ticket4 implements Runnable{ int num = 100; //创建Lock对象 Lock lock = new ReentrantLock(); @Override public void run() { while (true) { //调用lock对象的lock()方法,手动的获取锁 lock.lock(); if(num > 0) { try { Thread.sleep(20); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "正在卖票:" + num); num--; } //调用lock对象的unlock方法,手动的释放锁 lock.unlock(); } } }
以上是关于多线程之 线程安全的主要内容,如果未能解决你的问题,请参考以下文章