JUC

JUC

1、什么是JUC

java.util.concurrent包是在并发编程中使用的工具类,有以下三个包:
JUC

2、线程和进程

进程/线程是什么?

进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
线程:通常在一个进程中可以包含多个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程可以利用进程所有拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于进程比线程小,基本上不拥有系统资源,故对它的调度所付出的开销就会小很多,能更高效地提高系统多个程序间并发执行的程度。
JUC

并发/并行是什么?

并发和并行是两个非常容易混淆的概念。它们都可以表示两个或多个任务一起执行,但是偏重点有点不同。并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的。并发是逻辑上的同时发生(simulatneous),而并行是物理上的同时发生。然而并行的偏重点在于“同时执行”

严格意义上来说,并行的多个任务是真实的同时执行,而对于并发来说,这个过程只是交替的,一会运行任务一,一会儿又运行任务二,系统会不停地在两者间转换。但对于外部观察者来说,即使多个任务是串行并发的,也会造成是多个任务并行执行的错觉。

实际上,如果系统内只有一个CPU,而现在而使用多线程或者多线程任务,那么真实环境中这些任务不可能真实并行的,毕竟一个CPU一次只能执行一条指令,这种情况下多线程或者多线程任务就是并发的,而不是并行,操作系统会不停的切换任务。真正的并发也只能够出现在拥有多个CPU的系统中(多核CPU)。

并发的动机:在计算能力恒定的情况下处理更多的任务,就像我们的大脑,计算能力相对恒定,要在一天中处理更多的问题,我们就必须具备多任务的能力,现实工作中有很多事情可能会中断你的当前任务,处理这种多任务的能力就是你的并发能力。

并发的动机:用更多的CPU核心更快的完成任务。就像一个团队,一个脑袋不够用了,一个团队来一起处理一个任务。

例子:
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。(不一定是同时的)
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理技能。

线程的状态
Java的线程有6种状态:可以分析源码

public enum State {
       //线程刚创建
        NEW,

       //在JVM中正在运行的线程
        RUNNABLE,

       //线程处于阻塞状态,等待监视锁,可以重新进行同步代码块中执行
        BLOCKED,

     	//等待状态
        WAITING,

       	//调用sleep() join() wait()方法可能导致线程处于等待状态
        TIMED_WAITING,

        //线程执行完毕,已经退出
        TERMINATED;
    }

JUC

wait/sleep的区别

1、来自不同的类
这两个方法来自不同的类分别是,sleep来自Thread类,wait来自Object类。

sleep是Thread的静态方法,谁调用的谁去睡觉,即使在a线程中调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码调用sleep。
2、有没有释放锁(释放资源)
最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

sleep是线程被调用时,占着cpu去睡觉,其他线程不能占用cpu,os认为该线程正在工作,不会让出系统资源,wait是进入等待池等待,让出系统资源,其他线程可以占用cpu。

sleep(100L)是占用cpu,线程休眠100毫秒,其他线程不能再占用cpu资源,wait(100L)是进入等待池等待,交出cpu等系统资源供其他线程使用,在这100毫秒中,该线程可以被其他线程notify,但不同的是其他在等待池中不被notify不会出来,但这个线程在等待100毫秒后会自动进入就绪队列等待系统分配资源,换句话说,sleep(100)在100毫秒户肯定会运行,但wait在100毫秒后还要等待os调用分配资源,所以wait100的停止运行时间是不确定的,但至少是100毫秒。

就是说sleep有时间限制的就像闹钟一样到时候就叫了,而wait是无限期的除非用户主动notify。
3、使用范围不同
wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

	synchronized(x){
		//或者wait()
		x.notify()
	}

4.是否需要捕获异常
sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

3、Lock锁

传统的 synchronized

/*
 *题目:三个售票员卖出30张票
 *多线程编程的企业级套路:
 *1.在高内聚低耦合的前提下,线程 操作(对外暴露的调用方法)资源类
*/
public class SaleTicketTest1 {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 1; i < 40; i++) {
                    ticket.saleTicket();
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"A").start();

        new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 1; i < 40; i++) {
                    ticket.saleTicket();
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"B").start();

        new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 1; i < 40; i++) {
                    ticket.saleTicket();
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"C").start();
    }
}

class Ticket{//资源类
    private int number = 30;

    public synchronized void saleTicket(){
        if (number > 0){
            System.out.println(Thread.currentThread().getName()+"卖出第"+(number--)+"票,还剩下:"+number);
        }
    }
}

使用juc.locks包下的类操作Lock锁+Lambda表达式

JUC

public class SaleTicketTest2 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(()->{
            for (int i = 1; i < 40; i++) {
                ticket.saleTicket();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 1; i < 40; i++) {
                ticket.saleTicket();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 1; i < 40; i++) {
                ticket.saleTicket();
            }
        },"A").start();
    }
}
class Ticket{//资源类
    private Lock lock = new ReentrantLock();

    private int number = 30;

    public void saleTicket(){
        lock.lock();
        try{
            if(number>0){
                System.out.println(Thread.currentThread().getName()+"卖出第"+(number--)+"票,还剩下:"+number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

synchronized和lock区别

1、首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2、synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3、synchronized会自动释放锁(a线程执行完同步代码会释放锁;b线程执行过程中发生异常释放锁),Lock需要在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4、用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5、synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6、Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
JUC

4、生产者和消费者

线程间的通信,线程之间要协调和调度

生产者和消费者synchronized版

/**
 *题目:现在两个线程,可以操作初始值为0的一个变量
 *	实现一个线程对变量+1,一个线程对该变量-1
 *	实现交替10次
 *诀窍:
 *	1、高内聚低耦合的前提下, 线程操作资源类
 *	2、判断、干活、通知
*/
public class B {
    public static void main(String[] args) {
        Data data = new Data();

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

        new Thread(()->{
            for (int i = 1; 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()+"\t"+number);
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        //判断该不该这个线程做
        if(number == 0){
            this.wait();
        }
        //干活
        number--;
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        //通知
        this.notifyAll();
    }
}

问题升级:防止虚假唤醒,4个线程。两个加,两个减

JUC

/**
 *题目:现在两个线程,可以操作初始值为0的一个变量
 *	实现一个线程对变量+1,一个线程对该变量-1
 *	实现交替10次
 *诀窍:
 *	1、高内聚低耦合的前提下, 线程操作资源类
 *	2、判断、干活、通知
 *	3、多线程交互中,必须要防止多线程的虚假唤醒,也即(判读不能用if,只能用while)
*/
public class B {
    public static void main(String[] args) {
        Data data = new Data();

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

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        
		new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

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

    }
}

class Data{//资源类
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
        //判断该不该这个线程做
        while(number != 0){
            this.wait();
        }
        //干活
        number++;
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        //判断该不该这个线程做
        while(number == 0){
            this.wait();
        }
        //干活
        number--;
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        //通知
        this.notifyAll();
    }
}

新版生产者和消费者写法

JUC
JUC
JUC
JUC
闲聊常见笔试题:手写单例模式、手写冒泡排序、手写生产者消费者

/**
 *题目:现在两个线程,可以操作初始值为0的一个变量
 *	实现一个线程对变量+1,一个线程对该变量-1
 *	实现交替10次
 *诀窍:
 *	1、高内聚低耦合的前提下, 线程操作资源类
 *	2、判断、干活、通知
 *	3、多线程交互中,必须要防止多线程的虚假唤醒,也即(判读不能用if,只能用while)
*/
public class B {
    public static void main(String[] args) {
        Data data = new Data();

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

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

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

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

    }
}

class Data{//资源类
    private int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void increment() throws InterruptedException {
        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() throws InterruptedException {
        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();
        }
    }
}

精确通知顺序访问

/**
 *题目:多线程之间按顺序调出,实现A->B->C
 * 		三个线程启动,要求如下:
 *  	AA 打印 5次,BB 打印10次,CC打印15次, 依次循环
*/
public class C {
    public static void main(String[] args) {
        Resources resources = new Resources();

        new Thread(()->{
            for (int i = 1; i <= 10 ; i++) {
                resources.print5();
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 1; i <= 10 ; i++) {
                resources.print10();
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 1; i <= 10 ; i++) {
                resources.print15();
            }
        },"CC").start();
    }
}
class Resources{//资源类
    private int number =1 ; //1A 2B 3C
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    public void print5(){
        lock.lock();
        try{
            //判断
            while(number !=1){
                condition1.await();
            }
            //干活
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName()+"\t"+i);
            }
            //通知,指定的干活!
            number = 2;
            condition2.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void print10(){
        lock.lock();
        try{
            //判断
            while(number != 2){
                condition2.await();
            }
            //干活
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName()+"\t"+i);
            }
            //通知,指定的干活!
            number = 3;
            condition3.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void print15(){
        lock.lock();
        try{
            //判断
            while(number !=3){
                condition3.await();
            }
            //干活
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName()+"\t"+i);
            }
            //通知,指定的干活!
            number = 1;
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

5、8锁的现象

1、标准访问,请问先打印邮件还是短信?

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

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

        Thread.sleep(200);

        new Thread(()->{
            try{
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"B").start();
    }
}
class Phone{
    public synchronized void sendEmail() throws Exception{
        System.out.println("sendEmail");
    }

    public synchronized void sendSMS() throws Exception{
        System.out.println("sendSMS");
    }
}

结论:被synchronized修饰的方法,锁的对象是方法的调用者。因为两个方法的调用者是同一个,所以来两个方法用的是同一个锁,先调用方法的先执行。
2、邮件方法暂停4秒钟,请问先打印邮件还是短信?

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

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

        Thread.sleep(200);

        new Thread(()->{
            try{
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"B").start();
    }
}
class Phone{
    public synchronized void sendEmail() throws Exception{
        try{
            TimeUnit.SECONDS.sleep(4);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    public synchronized void sendSMS() throws Exception{
        System.out.println("sendSMS");
    }
}

结论:被synchronized修饰的方法,锁的对象时方法的调用者。因为两个方法的调用者是同一个,所以两个方法用的是同一个锁,先调用方法的先执行,第二个方法只有在第一方法执行完释放锁之后才能执行。
3、新增一个普通方法hello()没有同步,请问先打印邮件还是hello?

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

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

        Thread.sleep(200);

        new Thread(()->{
            try{
                //phone.sendSMS();
                phone.helle();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"B").start();
    }
}
class Phone{
    public synchronized void sendEmail() throws Exception{
        try{
            TimeUnit.SECONDS.sleep(4);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    public synchronized void sendSMS() throws Exception{
        System.out.println("sendSMS");
    }

    public void helle(){
        System.out.println("Hello");
    }
}

结论:新增的方法没有被synchronized修饰,不是同步方法,不受锁的影响,所以不需要等待。其他线程共用了一把锁,所以还需要等待。
4、两部手机、请问先打印邮件还是短信?

public class Lock8 {
    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();

        Thread.sleep(200);

        new Thread(()->{
            try{
                //phone.helle();
                phone2.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"B").start();
    }
}
class Phone{
    public synchronized void sendEmail() throws Exception{
        try{
            TimeUnit.SECONDS.sleep(4);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    public synchronized void sendSMS() throws Exception{
        System.out.println("sendSMS");
    }
}

结论:被synchronized修饰的方法,锁的对象是方法的调用者。因为用了两个对象调用各自的方法,所以两个方法的调用者不是同一个,所以两个方法用的不是同一个锁,后调用的方法不需要等待先调用的方法。

5、两个静态同步方法,同一部手机,请问先打印邮件还是短信?

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

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

        Thread.sleep(200);

        new Thread(()->{
            try{
                //phone.helle();
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"B").start();
    }
}
class Phone{
    public static synchronized void sendEmail() throws Exception{
        try{
            TimeUnit.SECONDS.sleep(4);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

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

结论:被synchronized和static修饰的方法,锁的对象是类的class对象。因为两个同步方法都被static修饰了,所以两个方法用的是同一个锁,后调用的方法需要等待先调用的方法。
6、两个静态同步方法,2部手机,请问先打印邮件还是短信?

public class Lock8 {
    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();

        Thread.sleep(200);

        new Thread(()->{
            try{
                //phone.helle();
                phone2.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"B").start();
    }
}
class Phone{
    public static synchronized void sendEmail() throws Exception{
        try{
            TimeUnit.SECONDS.sleep(4);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

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

结论:被synchronized和static修饰的方法,锁的对象时类的class对象。因为两个同步方法都被static修饰了,即便用了两个不同的对象调用方法,两个方法用的还是同一个锁,后调用的方法需要等待先调用的方法。
7、一个普通方法,一个静态方法,同一部手机,请问先打印邮件还是短信?

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

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

        Thread.sleep(200);

        new Thread(()->{
            try{
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"B").start();
    }
}
class Phone{
    public static synchronized void sendEmail() throws Exception{
        try{
            TimeUnit.SECONDS.sleep(4);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    public synchronized void sendSMS() throws Exception{
        System.out.println("sendSMS");
    }
}

结论:被synchronized和static修饰的方法,所得对象是类的class对象。仅仅被synchronized修饰的方法,锁的对象时方法的调用者。因为两个方法锁的对象不是同一个,所以两个方法用的不是同一个锁,后调用的方法不需要等待先调用的方法。
8、一个普通同步方法,一个静态方法,2部手机,请问先打印邮件还是短信?

public class Lock8 {
    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();

        Thread.sleep(200);

        new Thread(()->{
            try{
                phone2.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"B").start();
    }
}
class Phone{
    public static synchronized void sendEmail() throws Exception{
        try{
            TimeUnit.SECONDS.sleep(4);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    public synchronized void sendSMS() throws Exception{
        System.out.println("sendSMS");
    }
}

结论:被synchronized和static修饰的方法,锁的对象是类的class对象、仅仅被synchronized修饰的方法,锁的对象是方法的调用者。即便是同一个对象调用两个方法,锁的对象也不是同一个,所以两个方法用的不是同一个锁,后调用的方法不需要等待先调用的方法。

上一篇:Java JUC - 1 简介


下一篇:【JUC】并发编程的艺术笔记