学习目标:
多线程安全问题的解决方式
学习内容:
方式一:同步代码块
方式二:Lock锁 -JDK5.0新特性
学习产出1:同步代码块
SafeTicketsWindow01.java
线程的安全问题Demo:
卖票过程中出现了重票和错票的情况 (以下多窗口售票demo存在多线程安全问题)
package com.java.thread.thread_secrity;
public class SafeTicketsWindow01 {
public static void main(String[] args) {
WindowThread ticketsThread02 = new WindowThread();
Thread t1 = new Thread(ticketsThread02);
Thread t2 = new Thread(ticketsThread02);
Thread t3 = new Thread(ticketsThread02);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class WindowThread implements Runnable {
private int ticketsNum = 25;
public void run() {
while (true) {
if (ticketsNum > 0) {
try {
//手动让线程进入阻塞,增大错票概率
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":\t票号:" + ticketsNum);
/*try {
//手动让线程进入阻塞,增大重票的概率
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
ticketsNum--;
} else {
break;
}
}
}
}
/* 错票分析:
当票数为 1 的时候,三个线程中有线程被阻塞没有执行票数 -1 的操作,这是其它线程就会通过 if 语句的判断,这样一来就会造成多卖了一张票,出现错票的情况。
极端情况为,当票数为 1 时,三个线程同时判断通过,进入阻塞,然后多执行两侧卖票操作。
*/
SafeTicketsWindow02.java
多线程安全问题的解决方式一:同步代码块
synchronized ( 同步监视器 ) { 需要被同步的代码 }
优点:同步的方式,解决了线程安全的问题
缺点:操作同步代码时,只能有一个线程参与,其他线程等待。相当于时一个单线程的过程,效率低。
package com.java.thread.thread_secrity;
public class SafeTicketsWindow02 {
public static void main(String[] args) {
WindowThread02 ticketsThread02 = new WindowThread02();
Thread t1 = new Thread(ticketsThread02);
Thread t2 = new Thread(ticketsThread02);
Thread t3 = new Thread(ticketsThread02);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class WindowThread02 implements Runnable {
private int ticketsNum = 100;
//由于,Runnable实现多线程,所有线程共用一个实现类的对象,所以三个线程都共用实现类中的这个Object类的对象。
Object obj = new Object();
//如果时继承Thread类实现多线程,那么需要使用到static Object obj = new Object();
public void run() {
//Object obj = new Object();
//如果Object对象在run()方法中创建,那么每个线程运行都会生成自己的Object类的对象,并不是三个线程的共享对象,所以并没有给加上锁。
while (true) {
synchronized (obj) {
if (ticketsNum > 0) {
try {
//手动让线程进入阻塞,增大安全性发生的概率
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":\t票号:" + ticketsNum + "\t剩余票数:" + --ticketsNum);
} else {
break;
}
}
}
}
}
学习产出2:Lock锁 -JDK5.0新特性
多线程安全问题的解决方式二:Lock锁 -JDK5.0新特性
JDK5.0 之后,可以通过实例化 ReentrantLock 对象,在所需要同步的语句前,调用 ReentrantLock 对象的 lock() 方法,实现同步锁,在同步语句结束时,调用 unlock() 方法结束同步锁
synchronized和lock的异同:(面试题)
1. Lcok是显式锁(需要手动开启和关闭锁),synchronized是隐式锁,除了作用域自动释放。
2. Lock只有代码块锁,synchronized有代码块锁和方法锁。
3. 使用Lcok锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的拓展性(提供更多的子类)
package com.java.thread.thread_secrity;
import java.util.concurrent.locks.ReentrantLock;
public class SafeLock {
public static void main(String[] args) {
SafeLockThread safeLockThread = new SafeLockThread();
Thread t1 = new Thread(safeLockThread);
Thread t2 = new Thread(safeLockThread);
Thread t3 = new Thread(safeLockThread);
t1.start();
t2.start();
t3.start();
}
}
class SafeLockThread implements Runnable{
private int tickets = 100;
// 重入锁
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (tickets > 0) {
try {
//在这里锁住,有点类似同步监视器
lock.lock();
if (tickets > 0) {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + ":\t票号:" + tickets + "\t剩余票数:" + --tickets);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//操作完成共享数据后在这里解锁
lock.unlock();
}
}
}
}
package com.java.thread.thread_secrity;
public class Window02 {
public static void main(String[] args) {
Window02Thread ticketsThread02 = new Window02Thread();
Thread t1 = new Thread(ticketsThread02);
Thread t2 = new Thread(ticketsThread02);
Thread t3 = new Thread(ticketsThread02);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window02Thread implements Runnable {
private int tiketsNum = 100;
@Override
public void run() {
while (tiketsNum > 0) {
show();
}
}
private synchronized void show() { //同步监视器:this
if (tiketsNum > 0) {
try {
//手动让线程进入阻塞,增大安全性发生的概率
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":\t票号:" + tiketsNum + "\t剩余票数:" + --tiketsNum);
}
}
}
package com.java.thread.thread_secrity;
/**
* 多线程安全问题的解决方式二:同步方法
*/
public class Window03 {
public static void main(String[] args) {
Window03Thread t1 = new Window03Thread();
Window03Thread t2 = new Window03Thread();
Window03Thread t3 = new Window03Thread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.setPriority(Thread.MIN_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
t3.start();
}
}
class Window03Thread extends Thread {
public static int ticketsNum = 100;
@Override
public void run() {
while (ticketsNum > 0) {
show();
}
}
public static synchronized void show() {//同步监视器:Winddoe03Thread.class 不加static话同步监视器为t1 t2 t3所以错误
if (ticketsNum > 0) {
try {
//手动让线程进入阻塞,增大安全性发生的概率
Thread.sleep(150);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":\t票号:" + ticketsNum + "\t剩余票数:" + --ticketsNum);
}
}
}