Java——JUC深入理解

文章目录

    • 1、八锁问题
    • 2、生产者消费者问题
    • 3、Condition演示
    • 4、Callable演示

本文涉及代码以上传到 https://github.com/xtxxtxxtx/JUC_JVM

1、八锁问题

锁也是我们在编写多线程程序时候不可避免的一个话题,那么问题来了,如何确定要使用的锁。何时上锁何时释放锁,并且加上static修饰之后运行结果会有什么区别。

这只是一个简短的demo示例:

public class Lock8Demo {
    public static void main(String[] args) throws InterruptedException{
        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(() -> {
            try {
                //phone.sendSMS();
                //phone.sayHello();
                phone2.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();
    }
}

class Phone{

    public static synchronized void sendEmail() throws Exception{
        TimeUnit.SECONDS.sleep(4);
        System.out.println("sendEmail");
    }

    public static synchronized void sendSMS() throws Exception{
        TimeUnit.SECONDS.sleep(4);
        System.out.println("sendSMS");
    }

    public static synchronized void sayHello() throws Exception{
        TimeUnit.SECONDS.sleep(4);
        System.out.println("sayHello");
    }
}


/**
 *  8 lock
 *1 标准访问,请问先打印邮件还是短信
 *2 暂停4秒钟在邮件方法,请问先打印邮件还是短信
 *3 新增普通sayHello方法,请问先打印邮件还是hello
 *4 两部手机,请问先打印邮件还是短信
 *5 两个静态同步方法,同一部手机,请问先打印邮件还是短信
 *6 两个静态同步方法,2部手机,请问先打印邮件还是短信
 *7 1个静态同步方法,1个普通同步方法,同一部手机,请问先打印邮件还是短信
 *8 1个静态同步方法,1个普通同步方法,2部手机,请问先打印邮件还是短信
 *
 *  1、一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了其他的线程只能等待,换句话说
 *  同一个时刻内只能有唯一的线程去访问这些synchronized方法
 *  2、锁的当前对象是this,被锁定后,其他的线程都不能进入到当前对象的其他的synchronized方法
 *  3、加个普通方法后发现和同步锁无关,换成两个对象后不是同一把锁了情况就发生了变化;换成静态同步方法之后情况又发生变化,所有非静态同步方法用的都是同一把锁——实例对象本身
 *  4、synchronized实现同步的基础:Java中的每一个对象都可以作为锁,
 *      具体表现有下面三种表现形式:
 *          (1)、对于普通方法,锁时当前实例对象
 *          (2)、对于同步方法块,锁是synchronized括号里配置的对象
 *          (3)、对于静态同步方法,锁是当前类的class对象
 *
 *  5、当一个线程试图访问同步代码块时,首先必须得到锁,退出或抛出异常时必须释放锁
 *     就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态方法必须等待获取锁的方法释放锁之后才能获取锁
 *     可是别的实例对象的非静态同步方法因为跟实例对象的非静态同步方法用的是不同的锁
 *     所以无需等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们的锁
 *
 *  6、静态同步方法用的是同一把锁——类对象Class本身
 *      这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法是不会有竞争条件的
 *      但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁
 *      不管是同一个实例对象的静态同步方法之间还是不同的实例对象的静态同步方法之间,只要他们是同一个类的实例对象
 */

2、生产者消费者问题

生产者消费者也是我们经常遇到的问题,这个时候线程之间就要进行通信的交互了,这个时候我们就要考虑好如何对线程进行判断。
这个时候存在一个问题:如果使用if进行判断的话就会出现虚假唤醒的问题,所以在这种场合不适用if而是应该使用while。

/**
 * 题目:有两个线程,操作初始值为零的一个变量
 *      实现一个线程对变量加一、一个线程对变量减一;交替十轮
 *
 */
public class producerConsumer {
    public static void main(String[] args){

        AirCondition airCondition = new AirCondition();

        new Thread(() ->{
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(200);
                    airCondition.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(() ->{
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(300);
                    airCondition.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(400);
                    airCondition.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(500);
                    airCondition.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}

/**
 * 资源类
 */
class AirCondition{

    private int number = 0;

    private Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    public void increment(){
        lock.lock();
        try {
            while (number != 0){
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void decrement(){
        lock.lock();
        try {
            while (number == 0){
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

3、Condition演示

接下来要实现一个需求:多个线程之间要按照顺序调用。
这个时候我们需要对一把锁配多份备用钥匙并且在唤醒的时候要精确唤醒。

class ShareData{

    private int number = 1;
    private Lock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void print5(){
        lock.lock();
        try {
            //判断
            while (number != 1){
                c1.await();
            }
            //处理业务
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            //通知
            number = 2;
            c2.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void print10(){
        lock.lock();
        try {
            while (number != 2){
                c2.await();
            }
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            number = 3;
            c3.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void pring15(){
        lock.lock();
        try {
            while (number != 3){
                c3.await();
            }
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            number = 1;
            c1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

4、Callable演示

我们编写多线程代码时往往就是实现Runnable然后实现内部的run方法,但是这种方法显得比较老了,现在Callable用的比较多。

public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask(new myThread());

        new Thread(futureTask, "A").start();

        Integer result = futureTask.get();
        System.out.println(result);
    }
}

class myThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("come in call method");
        return 520;
    }
}

你可能感兴趣的:(Java)