Java多线程学习笔记_卖票案例分析

一、同步代码块

先贴代码:

public class MyRunnable implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if(ticket == 0){
                break;
            }else {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }    
                ticket--;
                System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
            }
        }

    }
}

public class Demo {
    public static void main(String[] args) {
        MyRunnable mr1 = new MyRunnable();

        Thread t1 = new Thread(mr1);
        Thread t2 = new Thread(mr1);
        Thread t3 = new Thread(mr1);

        t1.setName("#窗口一#");
        t2.setName("##窗口二##");
        t3.setName("###窗口三###");

        t1.start();
        t2.start();
        t3.start();
    }
}

此处代码与上篇博文(卖票案例实现)有所区别,为了引出需要讲解的内容。

不同之处在于,while循环的判断条件改为了true,增加了线程休眠;

问题:在如上代码的执行过程中,会出现重复票,负数票的情况。

原因就是,在休眠过程中,线程A的控制权给了其他线程(比方说是B),在这个过程中,线程B对共享数据ticket进行了修改,所以当A线程进行打印的时候,ticket已经不是当初那个ticket了,他变了。

而且很有可能在ticket置零的时候,还没进行判断(为零则终止循环),就被更改了数据值,导致死循环。

将if判断条件改为 <=0 , 则死循环不会出现,但是依旧存在重复票和负数票。

所以,如果在其他线程执行的时候,共享数据不允许被操作,那么这种情况就会得以解决。

这就需要用到 synchronized() 方法。将操作共享数据的多条代码锁起来,实现同步代码块。

synchronized(任意对象){
    操作共享数据的多条语句
}

默认情况下,锁是打开的,只要有一条线程去执行代码,所就会关闭。

当线程执行完出来了,锁才会自动打开。

同步的好处和弊端:

  • 好处:解决了多线程的数据安全问题。
  • 弊端:当线程很多时,所有的线程都会区判断同步上的锁,这很耗费资源,无形中会降低程序执行的效率。

二、锁对象唯一

上述卖票案例所用的实现方式是继承Runnable接口,线程之间使用的是同一个参数,所以锁也是共享的。

当每个线程都有自己的锁的时候,还是会出现重复票和负数票的问题,即线程变得不安全。(继承Thread类的方式实现多线程时)


public class MyThread extends Thread {
    private static int ticket = 100;
    private static Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized(obj){
                if(ticket <= 0){
                    break;
                }else {

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    ticket--;

                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
                }
            }
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("窗口一");
        t2.setName("窗口二");

        t1.start();
        t2.start();
    }
}

这是需要将MyThread类中的ticket变量和Object对象设置为静态,这样所有的MyThread类对象共享一个静态变量。

三、同步方法

同步方法的锁对象是:this

同步静态方法的锁对象是:类.class



public class MyRunnable implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {

        while (true) {
            if("窗口一".equals(Thread.currentThread().getName())){
                //同步方法
                boolean res = synchronizedMethod();
                if(res){
                    break;
                }
            }

            if("窗口二".equals(Thread.currentThread().getName())){
                //同步代码块
                synchronized(this){
                    if(ticket == 0){
                        break;
                    }else {

                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        ticket--;
                        System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
                    }
                }
            }
        }

    }

    private synchronized boolean synchronizedMethod() {
        if (ticket == 0) {
            return true;
        } else {

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            ticket--;
            System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
            return false;
        }
    }
}


public class Demo {
    public static void main(String[] args) {
        MyRunnable mr1 = new MyRunnable();

        Thread t1 = new Thread(mr1);
        Thread t2 = new Thread(mr1);

        t1.setName("窗口一");
        t2.setName("窗口二");

        t1.start();
        t2.start();

    }
}

注意:此代码实现多线程的方式是继承Runnable接口,两个线程的参数相同,执行的都是MyRunnable的对象mr1中的run()方法,所以锁对象this是相同的。

同步静态方法:

public class MyRunnable implements Runnable{
    //静态方法只能访问静态变量,所以要加上static进行修饰
    private static int ticket = 100;
    @Override
    public void run() {

        while (true) {
            if("窗口一".equals(Thread.currentThread().getName())){
                //同步方法
                boolean res = synchronizedMethod();
                if(res){
                    break;
                }
            }

            if("窗口二".equals(Thread.currentThread().getName())){
                //同步代码块
                //这里的对象需要进行修改
                synchronized(MyRunnable.class){
                    if(ticket == 0){
                        break;
                    }else {

                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        ticket--;
                        System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
                    }
                }
            }
        }

    }

    private static synchronized boolean synchronizedMethod() {
        if (ticket == 0) {
            return true;
        } else {

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            ticket--;
            System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
            return false;
        }
    }
}

四、Lock

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作Lock中提供了获得锁和释放锁的方法

  • void lock():获得锁
  • void unlock()∶释放锁

Lock是接口,不能直接实例化,这里采用它的实现类ReentrantLock来实例化。

import java.util.concurrent.locks.ReentrantLock;

public class MyThread extends Thread {
    private static int ticket = 100;
//    private static Object obj = new Object();

    ReentrantLock rlock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            
//            synchronized(obj){
            try {
                rlock.lock();
                if (ticket <= 0) {
                    break;
                } else {

                    Thread.sleep(100);
                    ticket--;

                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                rlock.unlock();
            }
//            }
        }
    }
}

五、死锁

死锁是因为锁的嵌套导致的,建议不要写锁的嵌套,这样可以避免死锁。

猜你喜欢

转载自blog.csdn.net/qq_43191910/article/details/114987823