1、卖票案例中出现的线程安全问题
例子:创建三个窗口卖票、总票数为100张、使用实现Runnable接口的方式
1、问题:卖票过程中,出现了重票、错票-->出现了线程安全问题
2、问题出现的原因:当某个线程操作车票的过程中,尚未完成时,其他线程参与进来、也操作车票。
3、如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才能开始
操作ticket。这种情况即使线程a阻塞,也不能被改变。可以通过同步代码块以及同步方法来处理。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this充当同步监视器。考虑使用当前类充当
2、在Java中,我们通过同步机制(同步代码块),来解决线程的安全问题
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:1、操作共享数据的代码,即为需要被同步的代码
2、共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
3、同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程共用一个锁
1.1、同步代码块解决实现Runnable接口的线程安全问题,锁可以使用this关键字代替
class Window2 implements Runnable{
private int ticket = 100;
Object object = new Object();
@Override
public void run() {
while(true) {
//也可以用this来代替锁,因为this代表w1这个对象
synchronized(this){
// synchronized (object) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 w1 = new Window2();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
t2.start();
t3.start();
}
}
1.2、同步代码块解决继承Thread类时出现线程安全问题。锁不能使用this关键字代替
// 在继承Thread类创建多线程的方式中,慎用this充当同步监视器。考虑使用当前类充当
class Window3 extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
public void run(){
while(true){
//正确得锁的写法
synchronized (obj){
//正确得锁的写法2,可以让当前类这个对象充当锁
//synchronized(Window3.class){
//错误的锁的写法:this代表着t1,t2,t3三个对象
//synchronized(this)
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 w1 = new Window3();
Window3 w2 = new Window3();
Window3 w3 = new Window3();
w1.setName("线程1");
w2.setName("线程2");
w3.setName("线程3");
w1.start();
w2.start();
w3.start();
}
}
3、同步方法来解决线程的安全问题
- 1、同步方法任然设计到同步监视器,只是不需要我们县式的声明。
- 2、非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身。
3.1、使用同步方法实现Runnable来解决线程安全问题
class Window4 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true) {
show();
}
}
private synchronized void show(){//同步监视器:this
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 w4 = new Window4();
Thread t1 = new Thread(w4);
Thread t2 = new Thread(w4);
Thread t3 = new Thread(w4);
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
t2.start();
t3.start();
}
}
3.2、使用同步方法继承Thread类来解决线程安全问题
class Window5 extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
public void run(){
while(true){
show();
}
}
public static synchronized void show(){//同步监视器:Window5.class
// public synchronized void show(){同步监视器:t1,t2,t3 这种写法是错误的
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
ticket--;
}
}
}
public class WindowTest5 {
public static void main(String[] args) {
Window5 w1 = new Window5();
Window5 w2 = new Window5();
Window5 w3 = new Window5();
w1.setName("线程1");
w2.setName("线程2");
w3.setName("线程3");
w1.start();
w2.start();
w3.start();
}
}