Java高级多线程笔记

简介: Java高级多线程笔记

Java高级多线程笔记


引言:

本文主要分享了多线程中的线程池概念(原理以及咋获取线程池)、Callable接口、Future接口、线程的同步以及异步、ReentrantLock(重入锁)、ReentrantRaedWriteLock(读写锁)

  • Java多线程超详细笔记:分享了多线程的实现方式、7种状态、线程安全的两种解决方案以及死锁、生产者消费者问题;
  • Java中线程安全的集合:分享了Collection体系集合下除了Vector以外的线程安全集合,包括:Collection中的安全工具方法、CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap、Queue接口

@[toc]

1. 线程池

池就是为了提供复用性,给出一个池实现复用;比如打客服电话,如果该客服占线需要等待,直到你之前的所有排在该客服的客户都咨询完你才可以咨询;客服这就相当于一个池,每天会接不同的电话而不是一个;

1.1 线程池引入

现有问题:

  • 线程是宝贵的内存资源、单个线程约占1MB空间,过分分配易造成内存溢出。

  • 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销、造成程序性能下降。

线程池:

  • 线程是容器,可设定线程分配的数量上限也就是同一时间并发执行的线程的个数。

  • 将预先创建的线程对象存入池中,并实现重用线程池中的线程对象。

  • 可以避免频繁的创建和销毁。

1.2 线程池原理

将任务提交给线程池,由线程池分配线程、运行任务、并在当前任务结束后复用线程。

在这里插入图片描述

如:有三个线程池,四个等待对象;先执行三个,当这三个中的一个执行完毕后空出线程池位置,第四个开始执行。

1.3 获取线程池

常用的线程池接口和类:在java.util.concurrent;包内。

  • Executor:线程池的顶级接口,执行已经提交的Runnable对象。

  • ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码。

  • Executors工厂类:通过此类可以获得一个线程池

  • 通过newFixedThreadPool(int nThreads):获取固定数量的线程池。参数:指定线程池中线程的数量。

  • 通过newCachedThreadPool():获得动态数量的线程池,如不够则创建新的,没有上限。

public class TestThreadPool {
   
   
    public static void main(String[] args) {
   
   
    //线程池(引用)   ---->Executors工具类(工厂类)
    ExecutorService es =  Executors.newFixedThreadPool(3);//手动限定线程池里的线程数量3。
    //    ExecutorService es = Executors.newCachedThreadPool();//动态数量的线程池
        //1.创建任务对象
        MyTask task = new MyTask();
        //2.将任务提交到线程池,由线程池调度、执行
        es.submit(task);//Runnable类型的对象
        es.submit(task);
        es.submit(task);
        es.submit(task);
        es.submit(task);
    }
}
//线程任务
class MyTask implements Runnable{
   
   
    public void run() {
   
   
        for(int i = 1;i<=5;i++) {
   
   
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
运行结果:
pool-1-thread-1:1
pool-1-thread-1:2
pool-1-thread-1:3
pool-1-thread-3:1
pool-1-thread-3:2
pool-1-thread-3:3
pool-1-thread-3:4
pool-1-thread-3:5
pool-1-thread-2:1
pool-1-thread-2:2
pool-1-thread-3:1
pool-1-thread-1:4
pool-1-thread-1:5
pool-1-thread-1:1
pool-1-thread-1:2
pool-1-thread-1:3
pool-1-thread-1:4
pool-1-thread-1:5
pool-1-thread-3:2
pool-1-thread-3:3
pool-1-thread-3:4
pool-1-thread-2:3
pool-1-thread-2:4
pool-1-thread-2:5
pool-1-thread-3:5
  • 以上程序是获取固定数量的线程池,由运行结果可以看出线程3最先执行完,之后有开始从1执行,实现了复用;
  • 获取动态数量的线程池可以自动添加线程池,对内存要求较高;

2. Callable接口

          JDK5之后加入,与Runnable接口类似都是为了变成任务被线程执行的对象,实现之后代表一个线程任务。Callable具有泛型返回值、可以声明异常,Runnable不行,没有返回值且无法抛出有返回值的异常。

Public interface Callable< V >{//V:value

Public V call() throws Exception;//计算结果,如无法计算则抛出

}

public class TestCallable {
   
   
    public static void main(String[] args) {
   
   
        ExecutorService es =  Executors.newFixedThreadPool(3);

        MyTask task = new MyTask();

        es.submit(task);
    }
}
class MyTask implements Callable<Integer>{
   
   

    @Override
    public Integer call() throws Exception {
   
   
        for(int i = 1;i<=10;i++) {
   
   
            if(i ==5) {
   
   
                Thread.sleep(3000);
            }
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
        return null;
    }
}

3. Future接口

异步接收ExecutorServices.submit():所返回的状态结果,当中包含了call()返回值。

  • V get():以阻塞形式等待Future中的异步处理结果(call()的返回值)

需求:

使用两个线程,分别计算1~50,51~100的和,进行相加

public class TestFuture {
   
   
    public static void main(String[] args) throws InterruptedException, ExecutionException {
   
   
        ExecutorService es =  Executors.newFixedThreadPool(2);
        MyCall mc = new MyCall();
        MyCall2 mc2 = new MyCall2();
        //通过submit执行提交的任务,Future接受返回的结果
        Future<Integer> result = es.submit(mc);
        Future<Integer> result2 = es.submit(mc2);
        //通过Future的get方法,获得线程执行完毕后的结果
        Integer value =  result.get();
        Integer value2 = result2.get();

        System.out.println(value+value2);
    }
}
//计算1~50的和
class MyCall implements Callable<Integer>{
   
   
    @Override
    public Integer call() throws Exception {
   
   
        Integer sum = 0;
        for(int i = 1;i<=50;i++) {
   
   
            sum = sum + i;
        }
        return sum;
    }
}
//计算51~100的和
class MyCall2 implements Callable<Integer>{
   
   
    @Override
    public Integer call() throws Exception {
   
   
        Integer sum = 0;
        for(int i =51;i<=100;i++) {
   
   
            sum = sum + i;
        }
        return sum;
    }
}

4. 线程的同步异步

4.1 线程同步

形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。

主线程调用m1()方法,优先执行完m1()方法回到主线程调用m2()方法,优先执行完m2()方法回到主线程调用m3()方法,m3方法执行完毕后回到主线程结束

  • 单条执行路径

4.2 线程异步

形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行;

主线程开始执行,告知m1之后回到主线程,m1也开始执行,各执行各的,二者共同竞争时间片;

  • 多条执行路径

5. Lock接口

JDK5提供了一个比synchronized更广泛的锁定操作(接口:Lock),可以清晰的看到在哪里加了锁;

  • public void lock():加锁
  • boolean tryLock()//尝试获取锁(成功返回true失败false,不会阻塞)仅在空闲时候才获取锁
  • public void unlock():试图释放锁

重入锁

也称递归锁,最多支持同一个线程发起的2147483648个递归锁,超过此限制就会抛出error错误;

Lock无法实例化只能借助子实现类:ReentrantLock可重入锁在 java.util.concurrent.locks包下 ;

public class TestLocks {
   
   
    public static void main(String[] args) {
   
   
        Test obj = new Test();//加锁
        Thread t1 = new Thread(new MyTask(obj));
        Thread t2 = new Thread(new MyTask2(obj));
        t1.start();
        t2.start();
    }
}
class Test{
   
   
    //创建Lock锁对象
    Lock lock = new ReentrantLock();
    public void method() {
   
   
        System.out.println(Thread.currentThread().getName()+"进入到上锁的方法里");
        try {
   
   
            //显示的写上 在此处获得锁
            lock.lock();
            try{
   
   
                Thread.sleep(3000);
            }catch (InterruptedException e) {
   
   
                e.printStackTrace();
        }
            System.out.println(Thread.currentThread().getName()+"退出了上锁的方法里");
        }finally {
   
   
            //显示的写上,此处释放锁
            lock.unlock();
        }
    }
}
class MyTask implements Runnable{
   
   
    Test obj;//共享资源对象
    public MyTask(Test obj) {
   
   
        this.obj = obj;
    }
    public void run() {
   
   
        obj.method();
    }
}
class MyTask2 implements Runnable{
   
   
    Test obj;//共享资源对象
    public MyTask2(Test obj) {
   
   
        this.obj = obj;
    }
    public void run() {
   
   
        obj.method();
    }
}
运行结果:
Thread-0进入到上锁的方法里
Thread-1进入到上锁的方法里
Thread-0退出了上锁的方法里
Thread-1退出了上锁的方法里
  • 谁先退出谁先执行,如果没有lock.unlock();则不会释放锁;

注:

  • 当使用Lock时,需要明确的写上锁和释放锁
  • 为了避免拿到锁的线程在运行时出现异常,导致程序终止没有释放锁;应用try{}finally{}保证最后会释放锁。
  • 设置正确的出口,使得每把锁都释放,否则会出现(StackOverflowError内存溢出错误)

6. 读写锁

ReentrantRaedWriteLock:

  • 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
  • 支持多次分配读锁,使多个读操作可以并发执行。

读锁:ReadLock

写锁:WriteLock

  • 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率;
  • isShutdown():如果程序关闭返回true

  • isTerminated():如果关闭后所有任务都完成,返回true

  • shutdown():启动一次顺序关闭,执行以前提交的任务,不接受新任务

public class TestReadWriteLock {
   
   
    public static void main(String[] args) {
   
   
        Student stu = new Student();//共享资源对象
        ExecutorService es = Executors.newFixedThreadPool(20);//20个线程池
        WriteTask write = new WriteTask(stu);//写线程任务
        ReadTask read = new ReadTask(stu);//读线程任务
        long start = System.currentTimeMillis();//开始时间  毫秒值
        es.submit(write);
        es.submit(write);

        for(int i =1;i<=18;i++) {
   
   
            es.submit(read);
        }
         es.shutdown();//停止线程池,但不停止已经提交的任务
        while(true) {
   
   
            System.out.println("结束了吗?");
            if(es.isTerminated() == true) {
   
   //线程池里的任务都执行完毕了
                break;
            }
        }

        long end = System.currentTimeMillis();//结束时间
        System.out.println(end - start);
    }
}
//写
class WriteTask implements Callable{
   
   
    Student stu;
    public WriteTask(Student stu) {
   
   
        this.stu = stu;
    }
    @Override
    public Object call() throws Exception {
   
   
        stu.setAge(100);
        return null;
    }
}
//读
class ReadTask implements Callable{
   
   
    Student stu;
    public ReadTask(Student stu) {
   
   
        this.stu = stu;
    }
    @Override
    public Object call() throws Exception {
   
   
        stu.getAge();
        return null;
    }
}
class Student{
   
   
    private int age;
     //Lock lock = new ReentrantLock();//重入锁
    ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock();//有两把锁
    ReadLock read = rrwl.readLock();//读锁
    WriteLock write = rrwl.writeLock();//写锁
    //赋值---写操作
    public void setAge(int age) {
   
   
        write.lock();
        try {
   
    
            try {
   
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
   
                e.printStackTrace();
            }
            this.age = age;
        }finally {
   
   
            write.unlock();
        }
    }
    //取值---读操作
    public int getAge() {
   
   
        read.lock();
        try {
   
   
            try {
   
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
   
                e.printStackTrace();
            }
            return this.age;
        }finally {
   
   
            read.unlock();
        }
    }
}
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
结束了吗?
3013
  • 使用Lock写2秒,读18秒
  • 使用ReentrantRaedWriteLock,2秒写1秒读,共三秒如上,因为读操作时是异步的

互斥规则:

写-写:互斥、阻塞;

读-写:互斥、读阻塞写,写阻塞读;

读-读:不互斥,不阻塞;

目录
相关文章
|
4天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
23 9
|
7天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
4天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
7天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
21 3
|
6天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
7天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
17 1
|
7天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
8天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
33 1
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
41 1
C++ 多线程之初识多线程
|
22天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
16 3