线程安全
问题引入:
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过实现Runnable接口实现
TicketSalesRunnable
public class TicketSalesRunnable implements Runnable {
static int piao = 100;
@Override
public void run() {
while (true) {
if (piao > 0) {
System.out.println(Thread.currentThread().getName() + ":正在出售" + (piao--) + "张票");
}
}
}
}
/*
现在写的这个售票的程序,是存在线程安全问题的
实际中存在网络延迟的情况,我们模拟一下网络延迟
出现了问题:
1. 出现重复票:由于原子性所导致的。原子性,就是不可再分割性 piao-- 不是一个原子性的操作 读 改 写
2. 出现0票或者说负数票:这是由于线程的随机性所导致的。
出现线程安全问题:得符合以下条件
1.多线程环境
2.多个线程共享同一个变量
3.有多条语句在操作这个共享变量 也就是说多个线程操作的这个共享变量是不是一个原子性的操作。
piao-- 不是原子性操作 读 改 写
使用同步,来解决线程安全问题,也就是说把你认为有可能会出现线程安全问题的代码
使用同步代码块包裹起来
synchronized (锁对象){
//同步的代码
}
锁对象:任意一个Java对象,都可以充当这个锁,注意多个线程要共用同一把锁对象
* */
MyTest
public class MyTest {
public static void main(String[] args) {
TicketSalesRunnable salesRunnable = new TicketSalesRunnable();
Thread th1 = new Thread(salesRunnable,"窗口1");
Thread th2 = new Thread(salesRunnable, "窗口2");
Thread th3 = new Thread(salesRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
加入延迟
我们前面讲解过电影院售票程序,从表面上看不出什么问题,但是在真实生活中,
售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟
改实现接口方式的卖票程序,每次卖票延迟100毫秒
模拟延迟: Thread.sleep(50);
线程安全问题产生原因分析
首先想为什么出现问题?(也是我们判断是否有问题的标准)
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
如何解决多线程安全问题呢?
基本思想:让程序没有安全问题的环境。
怎么实现呢?
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
判断一个多线程应用程序是否有问题的标准:
a: 是否是多线程环境
b: 是否存在共享数据
c: 是否存在多条语句同时操作共享数据,对共享变量是不是原子性操作
我们现在这个程序是存在问题的,因为它满足上面的标准,那么我们只要将这个标准打乱,那么我们就可以解决这个问题.
而上面的标准中a , b是不能打乱的,因此我们只能对c做处理,关键是怎么处理? 如果我们把操作共享数据的多条语句看做
成一个整体,当一个线程执行这个整体的时候,其他的线程处于等待状态,也就说当一个线程执行这个整体的时候,其他线程
不能进行执行,那么怎么做到这个一点呢?
需要使用同步代码块:
格式:
synchronized(对象){//不能在括号了直接new 对象 new 了 就没效果
要被同步的代码 ;
}
这个同步代码块保证数据的安全性的一个主要因素就是这个对象
注意这个对象 要定义为静态成员变量 才能被所有线程共享
需要这个对象被所有的线程对象所共享
这个对象其实就是一把锁.
这个对象习惯叫做监视器
同步代码块的方式解决线程安全问题及解释以及同步的特点及好处和弊端
同步代码块的格式
格式:
synchronized(对象){ //同步代码代码块上的锁,是一个互斥锁。
死循环
需要同步的代码;
}
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能
案例演示:同步代码块的方式解决线程安全问题
同步代码块
public class TicketSalesRunnable implements Runnable {
static int piao = 100;
static Object obj = new Object();
@Override
public void run() {
while (true) {
//同步代码块
synchronized (obj) {
//哪个线程,进入了同步代码块,就会持有持有锁对象,其他线程在没有获得这个对象的时候,处于等待的。
if (piao > 0) {
try {
//模拟网络延迟
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在出售" + (piao--) + "张票");
}
}
//当出了同步代码块,该线程就会释放锁
}
}
}
同步方法
//同步方法:默认用的这个锁对象是this
public synchronized void maiPiao() {
System.out.println(this);
//哪个线程,进入了同步代码块,就会持有持有锁对象,其他线程在没有获得这个对象的时候,处于等待的。
if (piao > 0) {
try {
//模拟网络延迟
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在出售" + (piao--) + "张票");
}
}
}
静态同步方法
//静态同步方法:默认使用的锁对象,是当前类的字节码文件对象
public static synchronized void maiPiao() {
//哪个线程,进入了同步代码块,就会持有持有锁对象,其他线程在没有获得这个对象的时候,处于等待的。
if (piao > 0) {
try {
//模拟网络延迟
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在出售" + (piao--) + "张票");
}
}
}
synchronized关键字
在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,
因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识。
java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。
线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。
获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,
当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,
直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,
但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,
类锁是用于类的静态方法或者一个类的class对象上的。
我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,
所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,
它只是用来帮助我们理解锁定实例方法和静态方法的区别的.
JDK5之后的Lock锁的概述和使用
Lock锁的概述
虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock和ReentrantLock
void lock() 加锁
void unlock() 释放锁
Lock锁的使用
建议总是 立即实践,使用 lock 块来调用 try,在之前/之后的构造中,最典型的代码如下:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
死锁问题
死锁问题概述
如果出现了同步嵌套,就容易产生死锁问题
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态
死锁问题代码演示
定义一个接口
public interface LockUtils {
public static final Object objA= new Object();
public static final Object objB= new Object();
}
MyThread
public class MyThread extends Thread {
boolean flag;
public MyThread(boolean flag) {
this.flag = flag;
}
public MyThread() {
}
@Override
public void run() {
if (flag){
synchronized (LockUtils.objA){
System.out.println("true的时候,进来持有objA");
synchronized (LockUtils.objB){
System.out.println("true的时候,进来持有objB");
}//释放objB
}//释放objA
}else{
synchronized (LockUtils.objB){
System.out.println("false的时候,进来持有objB");
synchronized (LockUtils.objA){
System.out.println("false的时候,进来持有objA");
}//释放objA
}//释放objB
}
}
}
Test01
public class Test01 {
public static void main(String[] args) {
MyThread th1 = new MyThread(true);
MyThread th2 = new MyThread(false);
th1.start();
th2.start();
}
}