Java多线程与并发

简介: Java多线程与并发

1、JDK版本的选择

选择JDK8、JDK11进行讲解的原因:Oracle长期支持

2、进程和线程的区别

进程和线程的由来

3、进程与线程的区别

进程是资源分配的最小单位,线程是cpu调度的最小单位.

  • 所有与进程相关的资源,都被记录在PCB(进程控制块)中。
  • 进程是抢占处理机的调度单位;线程属于某个进程,共享其资源。
  • 线程只由堆栈寄存器、程序计数器和TCB组成。

进程是资源分配的基本单位,所有与进程有关的资源都记录在进程控制块PCB中,以表示进程拥有这些资源或者正在使用它们,进程也是抢占处理机的调度单位,它拥有完整的虚拟内存地址空间,当进程发生调度时,不同的进程拥有不同的地址空间,而同一进程内的不同线程共享同一地址空间。与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其它线程共享进程的资源。

总结:

  • 线程不能看做独立应用,而进程可看做独立应用。
  • 进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径。
  • 线程没有独立的地址空间,多进程的程序比多线程程序健壮。
  • 进程的切换比线程的切换开销大。

4、Java进程和线程的关系

  • Java对操作系统提供的功能进行封装,包括进程和线程。
  • 运行一个程序会产生一个进程,进程包含至少一个线程。
  • 每个进程对应一个 JVM 实例,多个线程共享 JVM 里的堆。
  • Java采用单线程编程模型,程序会自动创建主线程。
  • 主线程可以创建子线程,原则上要后于子线程完成执行

获取当前线程名称:

System.out.println("Current Thread: "+Thread.currentThread().getName());

5、Thread中的start和run方法的区别

  • 调用 start()方法会创建一个新的子线程并启动
  • run( )方法只是 Thread的一个普通方法的调用

创建一个新线程

public class Test {
    private static void attack() {
        System.out.println("Current Thread is :" + Thread.currentThread().getName());
    }
    public static void main(String[] args) {
        Thread t = new Thread() {
            //            新线程run()需要执行的内容
            public void run() {
                attack();
            }
        };
        System.out.println("main Thread :" + Thread.currentThread().getName());
//        调用start方法,开启新线程
        t.start();
    }
}

结果:

main Thread :main
Fight
Current Thread is :Thread-0

5、Thread和Runnable的关系

Thread是一个类,实现了Runnable接口

我们可以打开其源码,发现Thread类实现了Runnable接口,进入Runnable接口发现其中只有一个抽象的run方法,也就说明了这个接口并不具备多线程的特性,是依赖Thread中的start的方法去创建一个子线程,再在子线程中调用一个thread实现好了的run方法去执行相应的业务逻辑.才能让类具备多线程的特性。

Thread

public class Thread implements Runnable {
    public synchronized void start() {
    }
}

Runnable

public interface Runnable{
    public abstract void run( );
}

Thread和 Runnable是什么关系

  • Thread是一个类,实现Runnable接口,里面含有start( )方法。
  • Runnable是一个接口,里面只有run( )方法。
  • Thread是实现了 Runnable接口的类,使得run支持多线程。
  • 因类的单一继承原则,推荐多使用 Runnable接口。

也就是线程需要执行,需要run( )方法,也就是新建线程的执行逻辑。还需要start( )方法,调用系统创建多线程的方法。也就是 start( )方法让系统创建一个新的线程执行run( )方法中逻辑。

extends Thread:内部有继承的run( )方法和start( )方法。可以自己执行。

implement Runnable:内部只有run( )方法。需要调用Thread中的start ( )才能执行。

6、创建多线程的方式

继承Thread方式(extends Thread)

自定义线程类:

public class MyThread extends Thread {
    /**
     * 利用继承中的特点 * 将线程名称传递 进行设置
     */
    public MyThread(String name) {
        super(name);
    }
    /**
     * 重写run方法 * 定义线程要执行的代码
     */
    public void run() {
        for (int i = 0; i < 20; i++) {
            //getName()方法 来自父亲
            System.out.println(getName() + i);
        }
    }
}

测试类:

public class Demo {
public static void main(String[] args) {
    System.out.println("这里是main线程");
        MyThread mt = new MyThread("小强");
            mt.start();//开启了一个新的线程
        for (int i = 0; i < 20; i++) {
            System.out.println("旺财:"+i);
}

实现Runnable方式(implements Runnable)

定义实现类:

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
        System.out.println(Thread.currentThread().getName()+" "+i);
}

定义测试类:

public class Demo {
    public static void main(String[] args) {
    //创建自定义类对象 线程任务对象
        MyRunnable mr = new MyRunnable();
    //创建线程对象
        Thread t = new Thread(mr, "小强");
            t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("旺财 " + i);
}

7、如何给run( )方法传参

  • 构造函数传参
  • 成员变量传参
  • 回调函数传参

8、如何实现处理线程的返回值

实现的方式

  • 主线程等待法
  • 使用Thread类的join()阻塞当前线程等待子线程处理完毕
  • 通过Callable接口:通过FutureTask或者线程池实现

8,1主线程等待

当主线程没有获取到值时,主线程等待子线程完成赋值。、

优点:实现简单,

缺点:

  • 自己需要实现循环等待逻辑,等待变量越多,代码异常臃肿。
  • 循环多久是不确定的。

子线程:

public class CycleWait implements Runnable {
    private String name;
    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        name = "we have date now";
    }
}

主线程:

//  主线程
public static void main(String[] args) throws Exception {
    CycleWait cw = new CycleWait();
    Thread t = new Thread(cw);
    t.start();
//  当辅助线程中name没有值时,主线程等待
    while (cw.name == null) {
            Thread.sleep(100);
    }
    System.out.println(cw.name);
}

8.2 使用Thread类的join()阻塞当前线程等待子线程处理完毕

  • 使用Thread里面的join( )方法,替代主线程等待法代码。
  • 优点:更精准控制,代码更简单。
  • 缺点:力不不够细,不能指定等待某个子线程完成再执行

替换为:t.join( )等待该线程终止。

//  主线程
public static void main(String[] args) throws Exception {
    CycleWait cw = new CycleWait();
    Thread t = new Thread(cw);
    t.start();
   当辅助线程中name没有值时,主线程等待
    t.join();
    System.out.println(cw.name);
}

8.3 通过Callable接口:通过FutureTask或者线程池实现

子线程:

import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        String value = "test";
        System.out.println("Ready to work");
        Thread.sleep(5000);
        System.out.println("task done");
        return value;
    }
}

使用FutureTask方式:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class FutureTaskDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task = new FutureTask<String>(new MyCallable());
        //启动线程
        new Thread(task).start();
//        线程等待
        if (!task.isDone()){
            System.out.println("wait");
        }
        System.out.println("task return="+task.get());
    }
}

执行结果:

使用线程池方式:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadPoolDemo {
    public static void main(String[] args) {
//        1、创建线程池
        ExecutorService pool = Executors.newCachedThreadPool();
//        2、获取返回的结果
        Future<String> result = pool.submit(new MyCallable());
//        3、线程等待
        if (!result.isDone()){
            System.out.println("wait ..");
        }
        try {
//        4、获取返回值
            System.out.println(result.get());
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
//         5、关闭线程池
            pool.shutdown();
        }
    }
}

9、线程状态

线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
TimedWaiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep.
Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

无限等待(Waiting)

没有设置 Timeout参数的 object.wait( )方法。
没有设置 Timeout参数的 Threadjoin( )方法。
LockSupport.park( )方法。

限期等待(Timed Waiting)

Thread.sleep( )方法。
设置了 Timeout参数的 Object.wait( )方法。
设置了 Timeout参数的 Thread.join( )方法。
LockSupport.parkNanps( )方法。
LockSupport parkUntil( )方法。

10、Sleep和Wait的区别

基本的差别

  • sleep是 Thread类的方法,wat是Object类中定义的方法
  • sleep( )方法可以在任何地方使用
  • wait( )方法只能在 synchronized方法或 synchronized块中使用

最主要的本质区别

  • Thread.sleep只会让出CPU,不会导致锁行为的改变。
  • Object.wait不仅让出CPU,还会释放已经占有的同步资源锁。

public class WaitSleepDemo {
    public static void main(String[] args) {
            //创建lock锁对象
        final Object lock = new Object();
            //启动线程A
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread A is waiting to get lock");
                synchronized (lock){
                    try {
                        System.out.println("thread A get lock");
                        Thread.sleep(20);
                        System.out.println("thread A do wait  method");
                        lock.wait(1000);
                        System.out.println("thread A is done");
                    } catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        //线程等待10s让线程A,执行到wait( );
            Thread.sleep(10);
        //启动线程B
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread B is waiting to get lock");
                synchronized (lock){
                    try {
                        System.out.println("thread B get lock");
                        System.out.println("thread B is sleeping 10 ms");
                        Thread.sleep(10);
                        System.out.println("thread B is done");
                    } catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

执行结果:

thread A is waiting to get lock
thread A get lock
thread B is waiting to get lock
thread A do wait  method
thread B get lock
thread B is sleeping 10 ms
thread B is done
thread A is done

11、notify和norifyAll的区别?

两个概念:

  • 锁池(EntryList)
  • 等待池(WaitSet)

锁池:

假设线程A已经拥有了某个对象(不是类)的锁,而其它线程B、C想要调用这个对象的某个 synchronized方法(或者块),由于B、C线程在进入对象的 synchronized方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程A所占用,此时B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池

等待池(Object.wait( )):

假设线程A调用了某个对象的wat()方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁。

notify和norifyAll的区别?

  • notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会。
  • notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会。

12、yield

public static native void yield()    暂停当前正在执行的线程对象,并执行其他线程。

概念

当调用 Thread.yield( )函数时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示。

public class YieldDemo {
    public static void main(String[] args) {
        Runnable yieldTask = new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 10; i++) {
                    System.out.println(Thread.currentThread().getName() + i);
                    if (i == 5) {
                        Thread.yield();
                    }
                }
            }
        };
        Thread t1 = new Thread(yieldTask, "A");
        Thread t2 = new Thread(yieldTask, "B");
        t1.start();
        t2.start();
    }
}

执行结果:

请求让出CPU执行的,不一定会被采纳,可能会让你继续执行(B1后还是B线程 的B2),也可能让出(B5后换成A线程的A1)。

B1
B2
B3
B4
B5
A1
B6
A2
A3
A4
A5
A6
A7
A8
A9
A10

13、如何中断线程

已经被抛弃的方法

  • 线程A通过调用stop( )方法停止线程B。
  • 通过调用 suspend( )和 resume( )方法

目前使用的方法

调用 interrupt0,通知线程应该中断了

  • ①如果线程处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个 InterruptedException异常。
  • ②如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。

需要被调用的线程配合中断。

  • ①在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
  • ②如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。

代码:

public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Runnable interruptTask = new Runnable() {
            @Override
            public void run() {
                int i = 0;
                try {
                    //在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程
                    while (!Thread.currentThread().isInterrupted()) {
                        Thread.sleep(100); // 休眠100ms
                        i++;
                        System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + ") loop " + i);
                    }
                } catch (InterruptedException e) {
                    //在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程。)
                    System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + ") catch InterruptedException.");
                }
            }
        };
    // 启动“线程t1”
        Thread t1 = new Thread(interruptTask, "t1");
        System.out.println(t1.getName() + " (" + t1.getState() + ") is new.");
        t1.start();                      
        System.out.println(t1.getName() + " (" + t1.getState() + ") is started.");
        // 主线程休眠300ms,然后主线程给t1发“中断”指令。
        Thread.sleep(300);
        t1.interrupt();
        System.out.println(t1.getName() + " (" + t1.getState() + ") is interrupted.");
        // 主线程休眠300ms,然后查看t1的状态。
        Thread.sleep(300);
        System.out.println(t1.getName() + " (" + t1.getState() + ") is interrupted now.");
    }
}

执行结果:

t1 (NEW) is new.        //线程被新创建。
t1 (RUNNABLE) is started.        线程执行,(调用start( )方法)
t1 (RUNNABLE) loop09 1        //线程正在执行
t1 (RUNNABLE) loop09 2        //线程正在执行
t1 (RUNNABLE) catch InterruptedException.    //线程正在阻塞,调用抛出异常
t1 (TIMED_WAITING) is interrupted.        //继续执行后面的逻辑
t1 (TERMINATED) is interrupted now.        //线程已经终止
目录
相关文章
|
1天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
3天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
3天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
3天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
19 3
|
3天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
39 2
|
11天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
43 6
|
24天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
20天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
20天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
43 3
|
21天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####