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.        //线程已经终止
目录
相关文章
|
9天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
4天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
5天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
23 4
|
5天前
|
消息中间件 供应链 Java
掌握Java多线程编程的艺术
【10月更文挑战第29天】 在当今软件开发领域,多线程编程已成为提升应用性能和响应速度的关键手段之一。本文旨在深入探讨Java多线程编程的核心技术、常见问题以及最佳实践,通过实际案例分析,帮助读者理解并掌握如何在Java应用中高效地使用多线程。不同于常规的技术总结,本文将结合作者多年的实践经验,以故事化的方式讲述多线程编程的魅力与挑战,旨在为读者提供一种全新的学习视角。
25 3
|
6天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
18 1
|
8天前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
22 2
|
10天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
17 3
|
10天前
|
缓存 安全 Java
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文将深入探讨Java中的多线程编程,包括其基本原理、实现方式以及常见问题。我们将从简单的线程创建开始,逐步深入了解线程的生命周期、同步机制、并发工具类等高级主题。通过实际案例和代码示例,帮助读者掌握多线程编程的核心概念和技术,提高程序的性能和可靠性。
10 2
|
11天前
|
Java
Java中的多线程编程:从基础到实践
本文深入探讨Java多线程编程,首先介绍多线程的基本概念和重要性,接着详细讲解如何在Java中创建和管理线程,最后通过实例演示多线程的实际应用。文章旨在帮助读者理解多线程的核心原理,掌握基本的多线程操作,并能够在实际项目中灵活运用多线程技术。
|
11天前
|
Java 开发者
Java中的多线程基础与应用
【10月更文挑战第24天】在Java的世界中,多线程是提高效率和实现并发处理的关键。本文将深入浅出地介绍如何在Java中创建和管理多线程,以及如何通过同步机制确保数据的安全性。我们将一起探索线程生命周期的奥秘,并通过实例学习如何优化多线程的性能。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开一扇通往高效编程的大门。
12 0