1、线程与进程

进程:

  • 进程是程序运行以及资源分配的基本单位,一个程序至少有一个进程。
  • 如下图所示:

线程:

  • 线程是CPU调度和分配的基本单位,一个进程至少有一个线程。
  • 同一个进程中的线程共享进程资源(减少切换,可提高效率),且可以并发执行。

2、并发和并行

并发:

  • 指的是同一时间间隔执行多个事件,强调的是一段时间内可以做多个不同的事。
  • 比如在一分钟内,先吃一口饭,再喝一口水,接着说一句话等等。

并行:

  • 指的是同一时刻执行多个事件,强调的是同一时刻可以做多个不同的事。
  • 比如在吃饭的同时,一边听歌,看电视。

3、线程的上下文切换

基本原理:

  • 一个CPU在任意时刻只能执行一个线程,如果有多个线程(超过CPU个数)存在,CPU将会采用时间片轮转的方式进行线程切换,即给每一个线程分配一个时间片,当一个线程的时间片用完的时候便会处于就绪状态,并让出CPU给其他线程使用,这就是一次上下文切换。
  • 上下文切换时是通过运行时数据区中的程序计数器来存储各个线程的运行状态的,以便于下次运行线程时可以接着上次指令继续运行(比如线程A做了一次计算准备返回数据时,切换到了线程B,然后又切回线程A,是直接返回数据,而不是再去计算一遍)。
  • 程序计数器指的是JVM中的一块内存区域,它可以看作是当前线程所执行字节码的行号指示器,通过它,Java可以知道每个线程执行到了哪一步指令(由此可以看出程序计数器是线程私有的)。
  • 一般而言,上下文切换是比较耗费CPU时间的一种操作。

4、线程的几种状态

基本原理:

  • 创建:指的是生成线程对象,此时并没有调用start方法。
  • 就绪:调用线程的start方法后,线程便进入了就绪状态,此时等待系统调度。
  • 运行:通过系统调度,开始运行线程中的run函数。
  • 等待:调用了Object.wait()会进入等待队列。一般需要等待其他线程做出一些特定的通知或者中断。
  • 超时等待:该状态与等待状态有一点区别就是它会自动返回,比如调用Threa.sleep(long),在超时之后,会自动返回进入就绪状态。
  • 阻塞:指的是去获取锁时(等待进入synchronized方法或块),发现同步锁被占用了,这时线程会被放入锁池,等待获取锁。
  • 死亡:运行完run方法,main方法(main方法指的是主线程)之后正常退出,也有可能出现异常导致死亡;死亡的线程不能重新启用,否则报错。

5、创建线程的几种方式

继承Thread类:

  • 创建一个类继承Thread,重写其中的run方法,即线程执行体。
  • 创建继承了Thread类的子类实例,即线程对象
  • 调用线程对象的start()方法启动线程。
  • 示例代码如下;
  1. public class ThreadTest {
  2. public static void main(String[] args){
  3. new TestThread().start();
  4. }
  5. }
  6.  
  7. class TestThread extends Thread{
  8. @Override
  9. public void run(){
  10. System.out.println("Test Thread");
  11. }
  12. }

实现Runnable接口:

  • 创建一个类实现Runnable接口,重写其中的run方法,即线程执行体。
  • 创建实现Runnable接口的类实例,接着将该实例对象作为target传入Thread类的构造器,此时创建的Thread类才是线程对象。
  • 调用线程对象的start()方法启动线程。
  • 示例代码如下;
  1. public class ThreadTest {
  2. public static void main(String[] args){
  3. new Thread(new TestRunnable()).start();
  4. }
  5. }
  6.  
  7. class TestRunnable implements Runnable{
  8. @Override
  9. public void run() {
  10. System.out.println("Test Runnable");
  11. }
  12. }

实现Callable类:

  • 创建一个类实现Callable接口,重写其中的call方法,即线程执行体,call方法存在返回值。
  • 创建实现Callable接口的类实例,将该实例对象作为callable传入FutureTask类的构造器,此FutureTask对象封装了该Callable对象的call()方法的返回值,可使用get方法获取。
  • 将FutureTask实例对象作为target传入Thread的构造器,此时创建的Thread类才是线程对象。
  • 调用线程对象的start()方法启动线程。
  • 示例代码如下;
  1. public class ThreadTest {
  2. public static void main(String[] args){
  3. FutureTask<Integer> task = new FutureTask(new TestCallable());
  4. new Thread(task).start();
  5. try {
  6. System.out.println(task.get());//get方法会得到call执行完之后的值
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. } catch (ExecutionException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. }
  14.  
  15. class TestCallable implements Callable<Integer> {
  16. @Override
  17. public Integer call() throws Exception {
  18. int num = 0;
  19. while(num < 5){
  20. num++;
  21. }
  22. return num;
  23. }
  24. }

6、sleep() 和 wait()

sleep():

  • sleep()方法是线程类Thread类的一个静态方法,如若是在synchronized中调用,sleep()方法不会释放锁。

wait():

  • wait()方法是线程类Object类的一个方法,如若是在synchronized中调用,wait()方法会释放锁。
  • 示例代码如下(从控制台输出的时间戳结果可以看出sleep没有释放锁,是阻塞进行的,而wait释放了锁):
  1. public class ThreadTest {
  2. protected static volatile Object lock = "lock";
  3.  
  4. public static void main(String[] args){
  5.  
  6. for(int i=0;i<5;i++){
  7. new Thread(() -> {
  8. synchronized(lock){
  9. System.out.println(Thread.currentThread().getName() + ":等待开始当前时间戳:" + System.currentTimeMillis());
  10. try {
  11. // lock.wait(1000);//让当前线程进入等待池
  12. Thread.sleep(1000);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }).start();
  18. }
  19. }
  20. }

7、关于wait() 与 notify(),notifyAll()

notify(),notifyAll():

  • notify()指的是随机唤醒一个wait线程进入对象锁池中竞争锁,而notifyAll()是唤醒所有的wait线程进入对象锁池中竞争锁。
  • 关于wait()与notify()方法示例代码如下:
  1. public class ThreadTest {
  2. protected static volatile Object lock = "lock";
  3.  
  4. public static void main(String[] args){
  5. new Thread(new TestRunnable(lock)).start();
  6. try {
  7. Thread.sleep(1000);
  8. System.out.println("sleep 1000ms");
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. new TestThread(lock).start();
  13.  
  14. }
  15. }
  16.  
  17. class TestRunnable implements Runnable{
  18. private Object lock;
  19. public TestRunnable(Object lock){
  20. this.lock = lock;
  21. }
  22. @Override
  23. public void run() {
  24. try {
  25. synchronized (lock){
  26. System.out.println("begin wait:" + System.currentTimeMillis());
  27. System.out.println("Release lock........");
  28. lock.wait();//让当前线程进入等待池
  29. System.out.println("end wait:" + System.currentTimeMillis());
  30. }
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. }
  36.  
  37. class TestThread extends Thread{
  38. private Object lock;
  39. public TestThread(Object lock){
  40. this.lock = lock;
  41. }
  42. @Override
  43. public void run() {
  44. synchronized (lock){
  45. System.out.println("begin notify:" + System.currentTimeMillis());
  46. System.out.println("do something........");
  47. lock.notify();//从等待池中随机唤醒一个wait线程进入锁池竞争锁
  48. // lock.notifyAll();//从等待池中随机唤醒所有wait线程进入锁池竞争锁
  49. System.out.println("end notify:" + System.currentTimeMillis());
  50. }
  51. }
  52. }

8、start() 和 run()

start():

  • 此方法是用来启动一个线程的,执行了start()方法之后,线程将进入就绪状态,之后会自动执行run函数中的内容,无需等待,是真正意义上的实现了多线程。

run():

  • 单独运行此方法,只是将其当做一个普通函数在使用,每次执行必须等待run函数里面的内容完成,才能接着往下执行,无法实现多线程。

9、线程安全性

主要体现在下面三方面:

  • 原子性:同一时刻只允许一个线程对数据进行操作(atomic开头的原子类,synchronized,Lock)。
  • 可见性:一个线程对共享变量的修改,可以及时地被其他线程观察到,(synchronized,volatile,Lock)。
  • 有序性:指的是可以观察到其他线程的指令执行顺序,由于指令重排,一般情况下是无序的(happens-before原则)。

10、线程死锁

死锁:

  • 指的是多个线程在执行过程中,因争夺资源而陷入环路阻塞的一种现象。
  • 示例代码如下(运行之后,两个线程陷入死锁中,如果没有外力中断,将会一直锁定):
  1. public class ThreadTest {
  2. //共享资源A
  3. private static Object A = new Object();
  4. //共享资源B
  5. private static Object B = new Object();
  6.  
  7. public static void main(String[] args){
  8. new Thread(() -> {
  9. synchronized (A){
  10. System.out.println(Thread.currentThread().getName() + ":得到资源---A");
  11. try {
  12. Thread.sleep(1000);//此处休眠是为了让其他线程获得执行机会
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println(Thread.currentThread().getName() + ":去获取资源---B");
  17. synchronized (B){
  18. System.out.println(Thread.currentThread().getName() + ":得到资源---B");
  19. }
  20. }
  21. },"Thread-01").start();
  22.  
  23. new Thread(() -> {
  24. synchronized (B){
  25. System.out.println(Thread.currentThread().getName() + ":得到资源---B");
  26. try {
  27. Thread.sleep(1000);//此处休眠是为了让其他线程获得执行机会
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. System.out.println(Thread.currentThread().getName() + ":去获取资源---A");
  32. synchronized (A){
  33. System.out.println(Thread.currentThread().getName() + ":得到资源---A");
  34. }
  35. }
  36. },"Thread-02").start();
  37. }
  38. }

死锁四个必要条件即如何避免死锁:

  • 互斥条件:一个资源在任意时刻只能被一个线程占用,如果有其他线程请求该资源,需要等待原线程释放资源(此条件不能被破坏,因为锁本身就是为了互斥访问)。
  • 请求和保持条件:一个线程已经获取了一部分资源,又去请求其他资源,如果其他资源正被占用,原线程陷入阻塞,且不会释放自己占用的资源(一次性申请所有资源;或者阻塞时,释放自己占用的资源)。
  • 不可剥夺条件:一个线程已经获得的资源,在自己未使用完之前不能被其他线程剥夺,只能自己使用结束后释放(如果去获取正在被其他线程使用的资源而阻塞时,可以释放自己占用的资源)。
  • 环路等待条件:多个进程之间形成一种头尾相接的循环等待资源关系(按一定的顺序来获取资源)。
  • 针对上述例子而言,可以让现线程1先获取资源AB,执行完之后,再让线程2获取资源AB,改动如下(执行顺序为:线程1先获取资源A,接着释放CPU,线程2执行准备去获取资源A,发现资源A已被占用,此时线程2阻塞,然后一秒之后释放CPU,线程1接着执行,去获取资源B,正常获取,执行完毕,释放CPU;线程2开始获取资源A,由于线程1已经执行完毕释放了锁,所以线程2正常获取资源A,接着休眠一秒释放CPU,然后又去获取资源B,同样正常获取,执行完毕):
  1. public class ThreadTest {
    //共享资源A
    private static Object A = new Object();
    //共享资源B
    private static Object B = new Object();
  2.  
  3. public static void main(String[] args){
    new Thread(() -> {
    synchronized (A){
    System.out.println(Thread.currentThread().getName() + ":得到资源---A");
    try {
    Thread.sleep(1000);//此处休眠是为了让其他线程获得执行机会
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + ":去获取资源---B");
    synchronized (B){
    System.out.println(Thread.currentThread().getName() + ":得到资源---B");
    }
    }
    },"Thread-01").start();
  4.  
  5. new Thread(() -> {
    //线程2去获取A时被阻塞
    synchronized (A){
    System.out.println(Thread.currentThread().getName() + ":得到资源---A");
    try {
    Thread.sleep(1000);//此处休眠是为了让其他线程获得执行机会
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + ":去获取资源---B");
    synchronized (B){
    System.out.println(Thread.currentThread().getName() + ":得到资源---B");
    }
    }
    },"Thread-02").start();
    }
    }

(以上所有内容皆为个人笔记,如有错误之处还望指正。)

Java中的多线程基础的更多相关文章

  1. java中的多线程 // 基础

    java 中的多线程 简介 进程 : 指正在运行的程序,并具有一定的独立能力,即 当硬盘中的程序进入到内存中运行时,就变成了一个进程 线程 : 是进程中的一个执行单元,负责当前程序的执行.线程就是CP ...

  2. Java中的多线程=你只要看这一篇就够了

    如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其 ...

  3. Java中的多线程技术全面详解

    本文主要从整体上介绍Java中的多线程技术,对于一些重要的基础概念会进行相对详细的介绍,若有叙述不清晰或是不正确的地方,希望大家指出,谢谢大家:) 为什么使用多线程 并发与并行 我们知道,在单核机器上 ...

  4. Java 中传统多线程

    目录 Java 中传统多线程 线程初识 线程的概念 实现线程 线程的生命周期 常用API 线程同步 多线程共享数据的问题 线程同步及实现机制 线程间通讯 线程间通讯模型 线程中通讯的实现 @(目录) ...

  5. 第87节:Java中的Bootstrap基础与SQL入门

    第87节:Java中的Bootstrap基础与SQL入门 前言复习 什么是JQ? : write less do more 写更少的代码,做更多的事 找出所有兄弟: $("div" ...

  6. Java中使用多线程、curl及代理IP模拟post提交和get访问

    Java中使用多线程.curl及代理IP模拟post提交和get访问 菜鸟,多线程好玩就写着玩,大神可以路过指教,小弟在这受教,谢谢! 更多分享请关注微信公众号:lvxing1788 ~~~~~~ 分 ...

  7. 【转】Java中的多线程学习大总结

    多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的. 一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的图: 上图中基本上囊括了Java中多线程 ...

  8. Java中的 多线程编程

    Java 中的多线程编程 一.多线程的优缺点 多线程的优点: 1)资源利用率更好2)程序设计在某些情况下更简单3)程序响应更快 多线程的代价: 1)设计更复杂虽然有一些多线程应用程序比单线程的应用程序 ...

  9. Android学习记录(5)—在java中学习多线程下载之断点续传②

    在上一节中我们学习了在java中学习多线程下载的基本原理和基本用法,我们并没有讲多线程的断点续传,那么这一节我们就接着上一节来讲断点续传,断点续传的重要性不言而喻,可以不用重复下载,也可以节省时间,实 ...

随机推荐

  1. Sersync 上配置 Sersync 服务

    上面的工作做好之后呢,下面就开始正式配置我们的 Sersync 了! 我们在 Sersync 安装过程中所用到包均是从谷歌 Sersync 项目组取得的,地址: https://code.google ...

  2. linux相关认证和权限配置

    [root@rsync-server-1 /]# echo 'rsync_backup:redhat' > /etc/rsync.password [root@rsync-server-1 /] ...

  3. MT41J256M16HA-125 原厂订购 现货销售

    作为一家科研公司,保证芯片的原厂品质和正规采购渠道是科学严谨的研发工作中重要的一环,更是保证研发产品可靠.稳定的基础.而研发中所遇到的各种不可预测的情况更是每个工程师向技术的山峰攀登中时会遇到的各种难 ...

  4. KC705E 增强版 基于FMC接口的Xilinx Kintex-7 FPGA K7 XC7K325T PCIeX8 接口卡

    KC705E 增强版 基于FMC接口的Xilinx Kintex-7 FPGA K7 XC7K325T PCIeX8 接口卡 一.板卡概述 本板卡基于Xilinx公司的FPGAXC7K325T-2FF ...

  5. 【串线篇】spring boot页面模板引擎

    如JSP.Velocity.Freemarker.Thymeleaf SpringBoot推荐的Thymeleaf:语法更简单,功能更强大: 一.引入thymeleaf <dependency& ...

  6. win cmd执行Python脚本提示找不到模块问题

    Windows关于命令行执行Python脚本,提示找不到模块的问题,我 本人也是在pycharm上运行没毛病的,后来在本地搞了个Jenkins做定时任务,谁知道就提示找不到模块 也百度了很多,都是说什 ...

  7. Java面向对象(一) 类和对象

    一.软件开发进化史 摘自<从零开始学架构> 机器语言(1940年) 最早的软件开发使用的是“机器语言”,直接使用二进制码0和1来表示机器可以识别的指令和数据. 汇编语言(20世纪40年代) ...

  8. 【leetcode】1028. Recover a Tree From Preorder Traversal

    题目如下: We run a preorder depth first search on the root of a binary tree. At each node in this traver ...

  9. go入门收集(转)

    go mod 使用 原文地址: https://juejin.im/post/5c8e503a6fb9a070d878184a  

  10. ht-3 linkedList特性

    LinkedList内部封装的是双向链表数据结构,每个节点是一个Node对象. Node对象中封装的是要被添加的元素,还有一个指向上一个Node对象的引用和 指向下一个Node对象的引用 , 与Arr ...