JUC学习笔记(一):简介和锁的回顾

JUC

  • JUC其实就是指这几个util工具包

 

 

原来我们使用的:

Thread:是一个普通的线程类。

Runnable:没有返回值,效率相比Callable比较低

 

 

 

线程和进程 和重要概念回顾

  • 进程:一个程序

    • 一个进程可以包含多个线程,至少包含1个。

  • Java默认有2个线程:

    • main

    • GC

1.Java 实际上是不能开启线程的,是通过本地方法调用本地系统接口去开启的:

//Thread.start方法    
public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
​
        boolean started = false;
        try {
            //调用start0方法
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
​
            }
        }
    }
​
//start0是一个本地方法,调用的是底层的C++,Java无法直接操作硬件
    private native void start0();
​

  


2.并发、并行

  • 并发:模拟出多条线程,以时间片为单位快速的切换正在的任务。

  • 并行:真正的多线程,1个处理器可以开启一个真正的线程

        //获取CPU的核数
        System.out.println(Runtime.getRuntime().availableProcessors());

 

  • 并发编程的本质:充分利用CPU的资源。

线程的状态

   public enum State {
        //新生
        NEW,
​
        //运行
        RUNNABLE,
​
        //阻塞
        BLOCKED,
​
        //等待,一直等待
        WAITING,
​
        //超时等待,超时后继续运行
        TIMED_WAITING,
​
        //终止
        TERMINATED;
    }

 

wait/sleep的区别

区别waitsleep
来源的类 Object Thread
会释放锁 不会释放锁
使用范围 必须在同步代码块中 可以在任何地方
捕获异常 不需要捕获异常(不是中断异常) 必须要捕获异常

 

传统的Synchronize锁

package com.rzp.demo01;
​
import java.util.TreeMap;
​
​
//基本的卖票例子
/**
 * 公司中多线程开发
 * 线程就是一个单独的资源类,没有任何的附属操作:
 * 意思就是不会去继承Runnable接口,只有纯粹的方法和属性
 */
public class Test1 {
    public static void main(String[] args) {
        //并发:多线程操作同一个资源类,把资源类丢入线程
        Ticket ticket = new Ticket();
        //使用Lamdba表达式
​
​
        new Thread(()->{
            for (int i = 0; i < 60; i++) {
                ticket.sale();
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
       },"A").start();
​
        new Thread(()->{
            ticket.sale();
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
​
        new Thread(()->{
            ticket.sale();
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"C").start();
​
    }
}
​
//资源类
class Ticket{
    private int number = 50;
​
    public synchronized void sale(){
        if (number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余:"+number);
        }
    }
}
​

 

Lock锁

Lock锁就是java.util.concurrent.Lock这个接口。

Lock锁有三个实现类:

  • ReentrantLock:可重入锁(常用)

  • ReadLock:读锁

  • WriteLock:写锁

ReentrantLock

公平锁与非公平锁

  • ReentrantLock有两个构造方法:

    • NonfairSync :非公平锁,默认构造方法,这个锁可以插队。

    • FairSync :公平锁,这个锁不能插队,必须先进先出。

    public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
​

 

 

使用

package com.rzp.demo01;
​
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
public class Demo02 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
​
        new Thread(()->{for (int i = 0; i < 60; i++) ticket.sale();},"A").start();
        new Thread(()->{for (int i = 0; i < 60; i++) ticket.sale();},"B").start();
        new Thread(()->{for (int i = 0; i < 60; i++) ticket.sale();},"C").start();
    }
}
​
/**
 * lock加锁的方法:
 * 1. new ReentrantLock
 * 2.lock.lock() 加锁
 * 3.finally lock.unlock() 解锁
 * try代码块中的代码就会加锁了
 */
class Ticket2{
    private int number = 50;
​
    Lock lock =  new ReentrantLock();
    public void sale(){
        //加锁
        lock.lock();
        try{
            //业务代码
            if (number>0){
                System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余:"+number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock(); //解锁
        }
​
    }
}

 


与Synchronized锁的区别

区别SynchronizedLock
定义 Java的关键字
是否获取到锁 无法判断 可以判断
释放锁 自动释放 手动释放
线程阻塞 其他线程会一直等待(blocked) 其他线程不一定等待(waiting)
可重入锁 可重入锁 可重入锁
中断 不可以中断 可以中断
公平 非公平锁 可以设置是否公平
适用范围 少量的代码同步问题 适合锁大量的同步代码

 

锁的应用:生产者消费者问题

Synchronized版解决

package com.rzp.pc;
​
/**
 *
 * 线程之间的通信问题:生产者和消费者问题!
 * 线程交替执行,A B 同时操作1个变量
 * 原来是使用等待和唤醒来相互通信的
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}
​
//等待,业务,通知
class Data{
    private int number = 0;
    public synchronized void increment() throws InterruptedException {
        if (number!=0){
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"="+number);
        //通知其他线程,+1完成
        this.notifyAll();
    }
​
    public synchronized void decrement() throws InterruptedException {
        if (number == 0){
            //等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"="+number);
        //通知其他线程,-1完毕
        this.notifyAll();
    }
}

 

  • 问题:

    • 这个解决方案,如果有超过2个的线程时,就还是会出现并发问题。

  • 原因:

    假如有两个线程同时增加,因为使用的是if判断。

    • 当前程A被唤醒的时候,就会直接执行number++,线程B也是如此,就会同时加了上去。(应该被唤醒后再判断一下,才能保证不会重复加)

    • 这种应该只有1个线程被唤醒,但是实际唤醒了多个的情况,被称作虚假唤醒

  • 解决

    这时候应该把if改成while

    • 因为while被唤醒后,会再次判断是否需要wait,就能避免这个问题。

        while (number == 0){
            //等待
            this.wait();
        }

 

JUC版解决

  • 和synchronized对比,也是使用类似的方法。只是名称不一样了,比如:

  • 在synchronized版本中,我们使用的是Object下的wait和notify方法。
  • 在JUC版本中,我们用的是Lock(对应synchronized),JUC包下的Condition类的await和unlock方法。

 

package com.rzp.pc;
​
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
​
//等待,业务,通知
class Data2 {
    private int number = 0;
    Lock lock = new ReentrantLock(); //使用lock替代了synchronized
    Condition condition = lock.newCondition(); //使用Condition取代了Object的Monitor(wait/notify/notifyAll方法)
​
​
    public void increment() throws InterruptedException {
        lock.lock();
        //ctrl+alt+t生成try
        try {
            while (number != 0) {
                //等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=" + number);
            //通知其他线程,+1完成
            condition.signalAll();
​
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
​
    }
​
​
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0) {
                //等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=" + number);
            //通知其他线程,-1完毕
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

 

  • 到这里JUC就可以实现相同的功能了,而JUC版比原来更强的地方在于,JUC可以精确唤醒线程。

指定唤醒

package com.rzp.pc;
​
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
//A执行完调用B,B执行完调用C,C执行完调用A
public class C {
    public static void main(String[] args) {
        Data3 data3 = new Data3();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) data3.printA();
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) data3.printB();
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) data3.printC();
        }, "C").start();
    }
}
​
class Data3 {
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
​
    private int flag = 1;
​
​
    public void printA() {
        lock.lock();
        try {
            while (flag != 1) {
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "===>AAAAA");
            //唤醒condition2
            flag = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
​
    public void printB() {
        lock.lock();
        try {
            while (flag != 2) {
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "===>BBBBBB");
            //唤醒condition3
            flag = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
​
    public void printC() {
        lock.lock();
        try {
            while (flag != 3) {
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "===>CCCC");
            //唤醒condition1
            flag = 1;
            condition1.signal();
​
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

 

8锁

  • 问题:上面生产者和消费者中,锁的到底是什么东西?

  • 8锁就是关于锁的8个问题

总结1:synchronized在方法中锁的是调用的对象

package com.rzp.pc;
​
import java.util.concurrent.TimeUnit;
​
/**
 * 8锁就是关于锁的8个问题
 * 1.sendSms 不增加延迟4秒情况下,以下代码谁先打印:
 *      答案:发短信 打电话
 *
 * 2.sendSms如果增加延迟4秒情况下,谁先打印:
 *      答案:发短信 打电话
 */
//原因:
/**
 * synchronized 锁在方法上,在运行时,锁的是调用这个方法的对象
 * 两个方法调用的是同一个对象,因此发短信先拿到锁,所以一定是发短信先打印
 *
 */
public class Lock8 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendSms();
        }).start();
​
        //休眠1秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        new Thread(()->{
            phone.call();
        }).start();
    }
}
​
​
class Phone{
    public synchronized void sendSms(){
        //休眠4
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        System.out.println("发短信");
    }
​
    public synchronized void call(){
        System.out.println("打电话");
    }
}

 

总结2:同一个对象,没加锁的方法不需要取得锁,总是可以直接运行

package com.rzp.lock8q;
​
import java.util.concurrent.TimeUnit;
​
/**
 * 3.增加普通方法,先输出的是谁?
 * hello
 * 因为普通方法没加锁
 */
public class Test2 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sendSms();
        }).start();
​
        //休眠1秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        new Thread(()->{
            phone.hello();
        }).start();
    }
}
​
​
class Phone2{
    public synchronized void sendSms(){
        //休眠4
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        System.out.println("发短信");
    }
​
    public synchronized void call(){
        System.out.println("打电话");
    }
​
    public void hello(){
        System.out.println("hello");
    }
}

 

package com.rzp.lock8q;
​
import java.util.concurrent.TimeUnit;
​
/**
 * 4.两个对象的时候,就是各自执行,因为是两把锁
 */
public class Test2 {
    public static void main(String[] args) {
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();
        new Thread(()->{
            phone1.sendSms();
        }).start();
​
        //休眠1秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        new Thread(()->{
            phone2.call();
        }).start();
    }
}
​
​
class Phone2{
    public synchronized void sendSms(){
        //休眠4
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        System.out.println("发短信");
    }
​
    public synchronized void call(){
        System.out.println("打电话");
    }
​
    public void hello(){
        System.out.println("hello");
    }
}

 

总结3:对于静态方法,synchronized锁的是类的class对象。

package com.rzp.lock8q;
​
import java.util.concurrent.TimeUnit;
​
/**
 * 5.增加两个静态同步方法
 */
public class Test2 {
    public static void main(String[] args) {
        //我们是直接用类名调用static方法的,而synchronized锁的是对象,这时候锁的是什么对象呢?
        // static 关键字:类加载的时候就加载了,加载在Phone2的class对象中。
        //这种情况下,synchronized锁的实际上是class对象
        new Thread(()->{
            Phone2.sendSms();
        }).start();
​
        //休眠1秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        new Thread(()->{
            Phone2.call();
        }).start();
    }
}
​
​
class Phone2{
    public static synchronized void sendSms(){
        //休眠4
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        System.out.println("发短信");
    }
​
    public static synchronized void call(){
        System.out.println("打电话");
    }
​
}

 

总结4.如果一个锁的是static,另一个锁的是普通方法,普通方法锁的是new出来的对象

  • 这时候锁的不是一个对象,按时间顺序执行

package com.rzp.lock8q;
​
import java.util.concurrent.TimeUnit;
​
public class Test3 {
    public static void main(String[] args) {
        Phone4 phone4 = new Phone4();
        new Thread(() -> {
            phone4.sendSms();
        }).start();
​
        //休眠1秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        new Thread(() -> {
            phone4.call();
        }).start();
    }
}
​
​
class Phone4 {
    public static synchronized void sendSms() {
        //休眠4
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        System.out.println("发短信");
    }
​
    public synchronized void call() {
        System.out.println("打电话");
    }
​
}

 

 

posted @ 2020-05-23 21:28  renzhongpei  阅读(355)  评论(0编辑  收藏  举报