面试必备考点 Java 并发编程(1)

Java 并发编程的目的就是充分利用计算机的资源,把计算机的性能发挥到最大。

1、什么是高并发

并发 concurrency 和并行 parallelism 的区别

并发是指多个线程操作同一个资源,不是同时操作,而是交替操作,单核 CPU,只不过因为速度太快,看起来是同时执行(张三、李四,共用一口锅炒菜,交替执行)

并行才是真正的同时执行,多核 CPU,每个线程使用一个独立的 CPU 的资源来运行。(张三、李四,一人一口锅,一起炒菜)

并发编程是指使系统允许多个任务在重叠的时间段内执行的设计结构。

高并发我们设计的程序,可以支持海量任务的同时执行(任务的执行在时间段上有重叠的情况)

  • QPS:每秒响应的请求数,QPS 并不是并发数。
  • 吞吐量:单位时间内处理的请求数,QPS 和并发数决定。
  • 平均响应时间:系统对一个请求作出响应的平均时间,QPS = 并发数/平均响应时间
  • 并发用户数:系统可以承载的最大用户数量。

互联网项目架构中,如何提高系统的并发能力?

  • 垂直扩展
  • 水平扩展

垂直扩展

提升单机的处理能力

1、增强单机的硬件性能:增加 CPU 的核数,硬盘扩容,内存升级。

2、提升系统的架构性能:使用 Cache 来提高效率,异步请求增加单个服务的吞吐量,使用 NoSQL 来提升数据的访问性能。

水平扩展

集群、分布式都是水平扩展的方案

集群:多个人做同一件事情(3 个厨师同时炒菜)

分布式:把一件复杂的事情,拆分成几个简单的步骤,分别找不同的人去完成 (1 洗菜、2 切菜、3 炒菜)

1、站点层扩展:Nginx 方向代理,高并发系统,一个 Tomcat 带不起来,就找十个 Tomcat 去带

image.png

2、服务层扩展:通过 RPC 框架实现远程调用,Dubbo、Spring Boot/Spring Cloud,将业务逻辑拆分到不同的 RPC Client,各自完成不同的业务,如果某些业务的并发量很大,就增加新的 RPC Client,理论上实现无限高并发。

3、数据层扩展:一台数据库拆成多台,主从复制、读写分离、分表分库。

2、进程和线程

进程就是计算机正在运行的一个独立的应用程序,进程是一个动态的概念,必须是运行状态,如果一个应用程序没有启动,那就不是进程。

线程就是组成进程的基本单位,可以完成特定的功能,一个进程是由一个或多个线程组成。

进程和线程的区别在于运行时是否拥有独立的内存空间,每个进程所拥有的空间都是独立的,互不干扰,多个线程是共享内存空间的,但是每个线程的执行是相互独立。

线程必须依赖于进程才能执行,单独线程是无法执行的,由进程来控制多个线程的执行。

Java 默认有几个线程?

Java 默认有两个线程:Main 和 GC(垃圾回收)

Java 本身是无法启动线程的

image.png

Java 无法操作底层硬件,只能通过调用本地方法,C++ 编写的动态函数库,由 C++ 去操作底层启动线程,Java 只是间接调用。

Java 中实现多线程有几种方式?

继承 Thread

实现 Runnable

实现 Callable

Thread 是线程对象,Runnable 是任务

image.png

package com.janeroad.demo;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        FutureTask futureTask = new FutureTask(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        //获取Callable的返回值
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<String>{

    @Override
    public String call() throws Exception {
        System.out.println("callable");
        return "hello";
    }
}

image.png

Callable 与 Runnable 的区别:

  • Callable 的 call 方法有返回值,Runnable 的 run 方法没有返回值。
  • Callable 的 call 方法可以抛出异常,Runnable 的 run 方法不能抛出异常。
  • 在外部通过 FutureTask 的 get 方法异步获取执行结果,FutureTask 是一个可以控制的异步任务,是对 Runnable 实现的一种继承和扩展。

get 方法可能会产生阻塞,一般放在代码的最后。

Callable 有缓存。

JUC

java.util.concurrent,JDK 提供的并发编程工具包,是由一组类和接口组成,可以更好的支持高并发任务。

image.png

package com.janeroad.demo2;

public class Test {
    public static void main(String[] args) {
        Account account = new Account();
        new Thread(account,"A").start();
        new Thread(account,"B").start();
    }
}

/**
 * 统计程序的访问量
 */
class Account implements Runnable{

    private static int num;

    @Override
    public synchronized void run() {
        num++;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"是第"+num+"位访客");
    }
}

image.png

public class Test3 {
    public static void main(String[] args) {
        Account2 account2 = new Account2();
        //lambda表达式
        new Thread(()->{
            account2.count();
        },"A").start();

        new Thread(()->{
            account2.count();
        },"B").start();
    }
}

class Account2{
    private static int num;
    public synchronized void count(){
        num++;
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"是第"+num+"位访客");
    }
}

实际开发中不会直接使用 sleep 休眠,而是使用 JUC 的方式

TimeUnit.SECONDS.sleep(1)

底层调用的还是 Thread.sleep 方法

Thread.sleep(ms, ns);

让休眠时间更加准确,毫秒+纳秒

规则:

  • 当纳秒大于 500 时,毫秒就加 1,当纳秒小于 500 时,不改变毫秒的值
  • 当毫秒为 0 的时候,只要纳秒不为 0 ,就将毫秒设置为 1

###3、sleep 和 wait 的区别

1、来自于不同的类,sleep 是 Thread 类的方法,wait 是 Object 类的方法。

2、sleep 是让当前的线程对象暂停执行任务,操作的是线程对象。wait 是让正在访问当前对象的线程休眠,不是针对线程对象,而是针对线程对象要访问的资源。但是 wait 有一个前提,当前线程对象必须拥有该资源,所以 wait 方法只能在同步方法或者同步代码块,否则会抛出异常。

3、wait 释放锁,sleep 不释放锁

wait 的使用

package com.janeroad.demo3;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        A a = new A();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                a.test(i);
            }
        }).start();
    }
}

/**
 * 资源
 */
class A{
    public synchronized void test(int i){
        if(i == 5){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i+"------------------");
    }
}

如何解除阻塞?

1、指定 wait 的时间

this.wait(3000);

2、通过调用 notify 方法唤醒线程

package com.janeroad.demo3;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        A a = new A();
        //任务线程
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                a.test(i);
            }
        }).start();

        //唤醒线程
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(7);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            a.test2();
        }).start();
    }
}

/**
 * 资源
 */
class A{
    public synchronized void test(int i){
        if(i == 5){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i+"------------------");
    }

    public synchronized void test2(){
        this.notify();
    }
}

notify 随机唤醒一个 wait 线程

notifyAll 唤醒所有 wait 线程

4、synchronized 锁定的是谁?

如果 synchronized 修饰非静态方法(实例方法),则锁定的是方法的调用者,只需要判断有几个对象,如果是一个对象,那么一定需要排队(线程同步),如果是多个对象,不需要排队

package com.janeroad.demo4;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            data.func1();
        }).start();
        new Thread(()->{
            data.func2();
        }).start();
    }
}

class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized void func2(){
        System.out.println("2...");
    }
}
package com.janeroad.demo4;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data data1 = new Data();
        Data data2 = new Data();
        new Thread(()->{
            data1.func1();
        }).start();
        new Thread(()->{
            data2.func2();
        }).start();
    }
}

class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized void func2(){
        System.out.println("2...");
    }
}
package com.janeroad.demo4;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            data.func1();
        }).start();
        new Thread(()->{
            data.func3();
        }).start();
    }
}

class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized void func2(){
        System.out.println("2...");
    }

    public void func3(){
        System.out.println("3...");
    }
}

如果 synchronized 修饰的是静态方法,则锁定的是类,无论多少个对象调用,都会同步

package com.janeroad.demo5;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data2 data1 = new Data2();
        Data2 data2 = new Data2();
        new Thread(()->{
            data1.func1();
        }).start();
        new Thread(()->{
            data2.func2();
        }).start();
    }
}

class Data2{
    public synchronized static void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized static void func2(){
        System.out.println("2...");
    }
}

image.png

如果 synchronized 静态方法和实例方法同时存在,静态方法锁类,实例方法锁对象

package com.janeroad.demo5;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(()->{
            data.func1();
        }).start();
        new Thread(()->{
            data.func2();
        }).start();
    }
}

class Data2{
    public synchronized static void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized void func2(){
        System.out.println("2...");
    }
}

image.png

如果 synchronized 修饰的是代码块,则锁定的是传入的对象,能否实现线程同步,就看锁定的对象是否为同一个对象

package com.janeroad.demo6;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data3 data3 = new Data3();
        for (int i = 0; i < 5; i++) {
            Integer num = Integer.valueOf(128);
            new Thread(()->{
                data3.func(num);
            }).start();
        }
    }
}

/**
 * 1、输出 start
 * 2s
 * 2、输出 end
 */
class Data3{
    public void func(Integer num){
        synchronized (num){
            System.out.println(num+"start...");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

Integer 的值 -128 - 127 之间,会使用包装类常量池,如果超出这个范围,不会使用包装类常量池。

package com.janeroad.demo7;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        User user = new User();
        new Thread(()->{
            user.count();
        },"A").start();
        new Thread(()->{
            user.count();
        },"B").start();
    }
}

class User{
    private Integer num = 0;
    private Integer id = 0;
    public void count(){
//        synchronized (num){
        synchronized (id){
            num++;
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"是第"+num+"位访问");
        }
    }
}
# Java  多线程 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×