Java多线程笔记

本文详细介绍了Java多线程的相关知识,包括线程与进程的区别、并发编程的三大特性、Java线程模型及线程的生命周期。讨论了线程的创建方式,如继承Thread类、实现Runnable接口和使用Callable/Future接口。还涵盖了线程同步、线程通信以及Java中wait()、notify()和notifyAll()的使用。此外,文章提到了线程的优先级、线程的生命周期状态以及如何控制线程执行顺序的方法,如join()和CountDownLatch。最后,简述了Java线程同步的重要性,展示了同步代码块和同步方法的使用。
摘要由CSDN通过智能技术生成

线程

多线程是同时有多个线程并发执行,同时完成多个任务,具有多个顺序执行流,且执行流之间互不干扰

Java语言对多线程有非常优秀的支持

线程和进程

在操作系统中,每一个独立的应用程序都是一个进程,当一个程序进入内存以后就会变成一个进程,
进程是操作系统进行资源分配和调度的独立单位。

进程与线程.png

进程的三个特征

  • 独立性
    • 进程用有自己独立的资源,独立的地址空间,其他进程不可以直接访问该进程的地址空间,除非该进程允许
  • 动态性
    • 程序只是一个静态的指令集合,只有当程序进入内存运行时,才会变成一个正在运行的动态的指令集和,进程具有自己的生命周期和各种不同的状态
  • 并发性
    • 多个进程可以在单个处理器上并发执行

并发编程的三个概念

  • 原子性

一个或多个操作要么全部执行完毕且执行过程不会被中断,要么就不执行

  • 可见性

当多个线程同时访问同一个资源时,一个线程如果修改了这个资源,那么其他的线程能够立即看到修改后的值

  • 有序性

程序执行的顺序按照代码编写的先后顺序

有序性之指令的重排序:

只要程序执行的最终结果与之顺序执行的结果相同,那么指令的执行顺序就可以不和指令的编写顺序不同。这个改变代码执行顺序的过程就叫做指令的重排序。

JVM可以根据处理器的特性(多核、多级缓存),适当的对指令的执行顺序进行重排序,使得机器的指令可以更贴合CPU的特征,从而可以最大限度的发挥机器的性能

Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为happens-before(先行发生原则) 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。

这就要再把有序性的概念扩展一下了。Java程序中天然的有序性可以总结为一句话:如果在本线程内观察,所有操作都是天然有序的。如果在一个线程中观察另一个线程,所有操作都是无序的。
所以JVM只能保证单线程中的有序性,如果在多线程中没有针对有序化的限制,就会出现问题。

并发与并行

  • 并发
    • 并发是在一块cpu上同时运行多个进程,cpu要在多个进程之间进行轮换。并发不是阵正的同时发生,而是在很快的速度进行轮换,由于速度很快,所以感知不强。
  • 并行
    • 并行是指多个事件在同一时刻同时进行,如今的多核处理器就支持并行处理,如果一个程序对并行进行优化的话,会极大的提升效率

并发与并行.jpg

线程是进程的而组成部分,线程是最小的处理单位,可以拥有自己的堆栈,计数器和局部变量,但不能拥有系统资源,多个线程之间共享其所在进程的系统资源。

多线程拓展了多进程的概念,使得同一个进程可以同时并处理多个任务。因此,线程也被称作轻量级进程。多线程和多进程是多任务的两种类型,主要区别如下:

  • 多进程之间的数据块是独立的,彼此互不影响,进程之间需要通过信号、管道等进行交互;
  • 多线程之间的数据块可以共享,一个进程中的各个线程可以共享程序段、数据段等资源。多线程比多进程更便于资源共享,同时Java提供的同步机制还可以解决线程之间数据完整性的问题

Java线程模型

  • 线程分为用户线程和内核线程;
  • 线程模型有多对一模型、一对一模型、多对多模型;
  • 操作系统一般只实现到一对一模型;
  • Java使用的是一对一线程模型,所以它的一个线程对应于一个内核线程,调度完全交给操作系统来处理;
  • Go语言使用的是多对多线程模型,这也是其高并发的原因,它的线程模型与Java中的ForkJoinPool非常类似;
  • python的gevent使用的是多对一线程模型;

一对一线程模型

  • 优点:
    • 实现简单
  • 缺点:
    • 对用户线程的大部分操作都会映射到内核线程上,引起用户态和内核态的频繁切换;
    • 内核为每个线程都映射调度实体,如果系统出现大量线程,会对系统性能有影响;

Java使用的就是一对一线程模型,所以在Java中启一个线程要谨慎。


Java线程模型提供了线程所需的功能支持,,基本的Java线程模型有Thread类,Runnable接口,Callable接口,Future接口等,这些线程模型都是面向对象的。

Thread类的常用方法

构造方法
Thread()
Thread(Runnable target)	//创建一个新线程
Thread(Runnable target, String name)	//创建一个新线程并命名
Thread(String name)


Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
Thread(ThreadGroup group, String name)

前四个构造方法最常用,后四个构造方法则会涉及到线程组。
总之,可以根据需要来选择适当的构造函数,从而指定:线程名字、Runnable对象、所属的线程组和栈大小。

Thread类的静态方法 (类方法)
activeCount(): 返回当前线执行的程所在的线程组中的活动线程的数目
currentThread(): 返回当前正在执行的线程
holdsLock(Object obj): 返回当前执行的线程是否持有指定对象的锁
interrupted(): 返回当前执行的线程是否已经被中断
sleep(long millis): 使当前执行的线程睡眠多少毫秒数
sleep(long millis, int nanos): 使当前执行的线程睡眠多少毫秒数+纳秒数
yield(): 使当前执行的线程自愿暂时放弃对处理器的使用权并允许其他线程执行
Thread类的非静态方法 (实例方法)
getId(): 返回该线程的id
getName(): 返回该线程的名字
getPriority(): 返回该线程的优先级
getState(): 返回该线程状态
interrupt(): 使该线程中断
isInterrupted(): 返回该线程是否被中断
isAlive(): 返回该线程是否处于活动状态
isDaemon(): 返回该线程是否是守护线程
join(): 等待该线程终止
join(long millis): 等待该线程终止,至多等待多少毫秒数
join(long millis, int nanos):  等待该线程终止,至多等待多少毫秒数+纳秒数
start(): 使该线程开始执行
toString(): 返回该线程的信息——名字、优先级和所属线程组
setDaemon(boolean on): 将该线程标记为守护线程或用户线程,如果不标记默认是非守护线程
setName(String name): 设置该线程的名字
setPriority(int newPriority): 改变该线程的优先级

线程类的创建

Java里创建线程常见的且比较简单方式主要有下面三种:

继承Thread类,并且重写run方法

实例:
TestThread.java

package com.company;

public class TestThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //继承Thread类时,直接使用this即可获取当前线程对象
            System.out.println(this.getName() + ":" + i);
        }
    }
}

Main.java

package com.company;

public class Main {
    static public void main(String[] args) {
        TestThread testThread = new TestThread();
        testThread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread() + ":" + i);
        }
    }
}

很显然,由于java不支持对类的多继承,所以如果继承了Thread类就没办法再继承其它父类了,所以更建议使用如下的第二种方式。

实现Runnab接口

Runnable接口中只有一个run()方法,一个类实现了Runnable接口方法后并不算是一个线程类,不能直接启动线程,必须通过Thread类的实例来创建并启动线程;
实例:
TestRunnable.java

package com.company;

public class TestRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //实现Runnable接口后就不能直接使用this获取当前线程对象了
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

RunnableMain.java

package com.company;

public class RunnableMain {
    public static void main(String[] args) {
        Thread thread = new Thread(new TestRunnable(),"线程1");
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread() + ":" + i);
        }
    }
}

使用Callable和Future接口

使用使用Callable和Future接口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值