线程安全问题、同步代码块、同步代码块的锁问题以及同步方法的应用和锁问题、Lock锁、死锁问题
线程安全问题、同步代码块、同步代码块的锁问题以及同步方法的应用和锁问题、Lock锁、死锁问题
线程安全问题
前面讲解过电影院售票程序,从表面上看不出什么问题,但是在真实生活中,
售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟
每次卖票延迟100毫秒,此时就有可能出现同票或者负票的情况
出现线程安全问题的前提条件:
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
同步代码块解决线程安全问题
格式:
synchronized(对象){ //同步代码代码块上的锁,是一个互斥锁。
死循环
需要同步的代码;
}
这个同步代码块保证数据的安全性的一个主要因素就是这个对象
注意这个对象 要定义为静态成员变量 才能被所有线程共享
需要这个对象被所有的线程对象所共享
这个对象其实就是一把锁.
这个对象习惯叫做监视器
public class CellRunnable implements Runnable{
//这个票让三个线程共享
static int piao=100;
//确保这个锁对象,只有一个,多个线程公用一把锁
static Object obj=new Object();
@Override
public void run() {
while (true) {
synchronized (obj){
if (piao >= 1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
}
}
//th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
}
}
}
public class MyTest {
public static void main(String[] args) {
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3= new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
同步的好处: 同步的出现解决了多线程的安全问题。
同步的弊端: 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。
线程进入同步代码块或同步方法的时候会自动获得该锁,在退出同步代码块或同步方法时会释放该锁。
获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,
当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,
直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
同步方法解决线程安全问题
同步方法使用的是对象锁
java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,
但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,
类锁是用于类的静态方法或者一个类的class对象上的。
我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,
所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,
它只是用来帮助我们理解锁定实例方法和静态方法的区别的.
public class CellRunnable implements Runnable{
//这个票让三个线程共享
static int piao=100;
//确保这个锁对象,只有一个,多个线程公用一把锁
static Object obj=new Object();
int i=1;
@Override
public void run() {
while (true) {
if (i%2==0){
synchronized (this){
//当th1这个线程进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面
if (piao >= 1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
}
}
}else{
maipiao();
}
i++;
}
}
//同步方法:我们可以把一个方法用synchronized这个关键字修饰,来封装一段代码,来解决线程安全问题
//同步方法:默认用的锁对象是this
private synchronized void maipiao() {
//System.out.println(this);
if (piao >= 1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
}
}
}
public class MyTest {
public static void main(String[] args) {
//创建了一次任务 100
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3= new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
静态同步方法解决线程安全问题
静态同步方法使用的是类锁
类的对象实例可以有很多个,但是每个类只有一个class对象,故可以保证锁只有一把
public class CellRunnable implements Runnable{
//这个票让三个线程共享
static int piao=100;
//确保这个锁对象,只有一个,多个线程公用一把锁
static Object obj=new Object();
int i=1;
@Override
public void run() {
while (true) {
//模拟一下真实的售票环境,有网络延迟。
if (i%2==0){
synchronized (CellRunnable.class){
//当th1这个线程进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面
if (piao >= 1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
}
}
}else{
maipiao();
}
i++;
}
}
//同步方法:我们可以把一个方法用synchronized这个关键字修饰,来封装一段代码,来解决线程安全问题
//静态同步方法:默认用的锁对对象,用的是当前类的字节码对象
private static synchronized void maipiao() {
//System.out.println(this);
if (piao >= 1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
}
}
}
public class MyTest {
public static void main(String[] args) {
//创建了一次任务 100
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3= new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
Lock锁
Lock锁的概述
虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock和ReentrantLock
static ReentrantLock lock=new ReentrantLock();
void lock() 加锁
void unlock() 释放
public class CellRunnable implements Runnable{
//这个票让三个线程共享
static int piao=100;
//确保这个锁对象,只有一个,多个线程公用一把锁
static ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true) {
//th1 th2 th3
//加锁
lock.lock();
try{
if (piao >= 1) {
try {
//模拟一下真实的售票环境,有网络延迟。
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
public class MyTest {
public static void main(String[] args) {
//创建了一次任务 100
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3= new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
死锁问题
如果出现了同步嵌套,就容易产生死锁问题
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态
public interface ObjectUtils {
//创建两个对象来充当两把锁
public static final Object objA=new Object();
public static final Object objB=new Object();
}
public class MyThread extends Thread{
boolean flag;
public MyThread(boolean flag) {
this.flag=flag;
}
@Override
public void run() {
if (flag){
synchronized (ObjectUtils.objA){
System.out.println("true 线程持有了objA锁,进来执行了AAAA");
synchronized (ObjectUtils.objB){
System.out.println("true 线程持有了objB锁,进来执行了BBB");
}
}
}else {
synchronized (ObjectUtils.objB){
System.out.println("false线程持有了objB锁,进来执行BBB");
synchronized (ObjectUtils.objA){
System.out.println("false 线程持有了objA锁,进来执行了AAA");
}
}
}
}
}
public class MyTest {
public static void main(String[] args) {
MyThread th1 = new MyThread(true);
MyThread th2 = new MyThread(false);
th1.start();
th2.start();
}
}
推荐阅读
-
荐 Java中多线程的使用(超级超级详细)线程安全+线程锁原理解析+保证线程安全的三种方式 (同步代码块+同步方法+lock锁) 5
-
关于多线程两种方法的思考 卖票问题 同步代码块加锁问题
-
Java~使用synchronized修饰静态方法带来的问题 与 同步synchronized代码块不使用String作为锁对象的原因
-
线程安全问题、同步代码块、同步代码块的锁问题以及同步方法的应用和锁问题、Lock锁、死锁问题
-
荐 Java中多线程的使用(超级超级详细)线程安全+线程锁原理解析+保证线程安全的三种方式 (同步代码块+同步方法+lock锁) 5
-
关于多线程两种方法的思考 卖票问题 同步代码块加锁问题
-
Java~使用synchronized修饰静态方法带来的问题 与 同步synchronized代码块不使用String作为锁对象的原因