Java多线程与并发

这篇具有很好参考价值的文章主要介绍了Java多线程与并发。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

序号 地址
1 计算机网络核心
2 数据库相关
3 Redis
4 Linux相关
5 JVM的内容
6 GC相关的
7 Java多线程与并发
8 Java多线程与并发-原理
9 Java常用类库与技巧
10 Java框架-Spring


1、JDK版本的选择

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

2、进程和线程的区别

进程和线程的由来

Java多线程与并发

3、进程与线程的区别

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

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

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

Java多线程与并发

Java多线程与并发

总结:

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

4、Java进程和线程的关系

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

获取当前线程名称:

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

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

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

Java多线程与并发

创建一个新线程

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接口。

Java多线程与并发

也就是线程需要执行,需要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());
    }
}

执行结果:

Java多线程与并发

使用线程池方式:

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( )方法。

Java多线程与并发

10、Sleep和Wait的区别

基本的差别

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

最主要的本质区别

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

Java多线程与并发

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.");
    }
}

执行结果:文章来源地址https://www.toymoban.com/news/detail-497599.html

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.        //线程已经终止

到了这里,关于Java多线程与并发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 计算机网络-IP地址解析

    局域网 是指一个较小范围内的计算机网络,由多台计算机、服务器、打印机、网络存储设备和其他网络设备组成,通过局域网,这些设备可以方便地进行数据通信和互相访问共享资源。局域网使用一些标准的网络协议来连接和管理网络设备,如TCP/IP、以太网等。我们大家理解

    2024年02月01日
    浏览(41)
  • 计算机网络 01 IP地址

    01.IPV4和IPV4的表示方式(点分四组) 二进制表达 02.IPV6(十六进制表达) 计算理解:一个十六进制的数转化成为二进制 是 4位 128/4=32 ,一共用32个十六进制 简化书写IPV6 02. 03.IPV4转换成为IPV6 04.IPV6的低32位 05.在URL中使用IPV6 1. 2. 3.

    2024年04月09日
    浏览(67)
  • 计算机网络-NAT网络地址转换

    今天来回顾下之前所学的知识,将它们串联起来进行巩固。一开始了解了IP编址进行IP设置和划分网段;学习了二层以太网交换,了解了二层通信基础;学习了路由基础知识,大致了解到了路由是什么?静态路由和动态路由;然后学习了VLAN的知识,进行虚拟局域网的划分和V

    2024年01月17日
    浏览(27)
  • 计算机网络:MAC地址 & IP地址 & ARP协议

    如果两台主机通过一条链路通信,它们不需要使用地址就可以通信,因为连接在信道上的主机只有他们两个。换句话说, 使用点对点信道的数据链路层不需要使用地址 。 再来看使用共享信道的总线型局域网: 总线上的某台主机要给另一台主机发送帧,表示帧的信号通过总线

    2024年04月17日
    浏览(40)
  • 计算机网络——主机IP地址、子网掩码、广播地址、网络数、主机数计算方法

    目录 一、概念 1.1 主机IP地址 1.2 子网掩码 1.3 广播地址 1.4 子网划分 二、计算 2.1 已知IP地址和子网掩码,计算网络地址和主机地址: 2.2 已知IP地址和子网掩码,计算广播地址: 2.3 已知子网掩码,计算主机数: 2.4 已知子网掩码,计算会产生多少个子网: 2.5 已知子网掩码,

    2023年04月24日
    浏览(33)
  • 【计算机网络】IP 地址处理函数

    目录 1.struct sockaddr_in的结构 2.一般我们写的结构 3.常见的“点分十进制”  到  ” uint32_t 的转化接口 3.1. inet_aton  和  inet_ntoa (ipv4) 3.2.  inet_pton  和 inet_ntop (ipv4 和 ipv6) 3.3. inet_addr 和 inet_network 3.4. inet_addr 和 inet_network 和 inet_aton 区别   一般我们在程序中用的点分十进制

    2024年02月08日
    浏览(34)
  • IP地址的分配(计算机网络)

    目录 一、IP地址的分配 二、IP地址分配举例 三、划分CIDR地址块 将142.150.64.0/24划分为3个CIDR地址块,其中一个地址块的大小是另外两个地址块大小之和,请给出每个地址块的CIDR记法、对应的掩码及地址范围。 1、IP地址现在由因特网名字与号码分配机构 (Internet Corporation for

    2024年02月12日
    浏览(30)
  • 计算机网络中的网络地址转换

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 在计算机网络的广阔和互联的环境中,有一位幕后英雄在设备如何跨数字领域进行通信方面发挥着关键作用。您可能在科技圈子里听说过它的首字母缩略词,或者在路由器的设置中遇到过它——NAT 或网

    2024年01月24日
    浏览(28)
  • 计算机网络相关-ip地址,子网掩码与网络地址,广播地址

    计算机网络基础(进制转换和IP地址详解)_ip地址进制转换方法_码海小虾米_的博客-CSDN博客 总的来说,ip地址用点隔开,用4个0-255之间的十进制数表示,如192.168.65.98 有这样的题:告诉你ip地址和子网掩码位数,求网络地址和广播地址。 一般给出的信息是这样的:192.168.65.98

    2024年02月07日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包