在了解多线程之前,先来了解一下进程与线程之间的关系。

进程和线程:
  进程是指在系统中正在执行的一个程序,每个进程之间是独立的。
  线程是进程的一个基本执行单元。一个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程)

主线程:
  在java程序中存在一个主线程(JVM线程),main方法自带的一个线程。
  之所以在系统进行多个程序的时候(QQ,web网页等),看似是在同步执行,那是因为cpu在进程中进行多线程的切换,cpu切换的速度之快让我们 觉得是在同步执行,其实在进程的执行过程中是存在一定的先后顺序的。

线程的执行原理:
  线程运行的结果每次都不同,因为多个线程都在获取cpu的执行权,cpu执行到了谁,谁就执行。
  明确一点:在某一时刻,只能有一个程序在运行(多核cpu除外)——也可以说多线程的运行行为是在互相抢夺cpu的执行权,同时cpu在做着快速的切换动作,已达到看似同时运行的结果。因此多线程的特性是随机性。

实现多线程的方式有两种:继承Thread类、实现Runnable接口

继承Thread类

  1、定义类继承Thread类

  2、复写Thread中的run()方法

     目的:将自定义代码写在run()方法中,让线程运行

  3、调用start()方法,启动线程,调用run()方法

(一个线程是不允许调用两次start()方法,第二次调用时会抛出java.lang.IllegalThreadStateException异常。多次调用会认为是变成错误。

  在第二次调用 start() 方法的时候,线程可能处于终止或其他(非 NEW)状态,但是不论如何,都是不可以再次启动的。)


public class ThreadTest extends Thread{@Overridepublic void run(){    //复写Thread中的run方法for(int i=0;i<10;i++){System.out.print(" demo run");}}}
class ThreadDemo{public static void main(String[] args) {ThreadTest dt = new ThreadTest();dt.start();    //调用start方法(启动线程,调用run方法)for(int i=0;i<10;i++){System.out.print("main run");}}
}

  代码解说:在这一个程序中,存在两个线程:main主线程和ThreadTest自定义线程。

  这两个线程同是在一个进程(程序)中执行,为确保程序的正常执行,因此在进程的内存空间中,开辟了两个线程空间,这两个线程空间在要执行的时候,都要获取cpu的执行权,所以,会互相的去抢夺cpu的执行权。会先抢到谁就执行。因此,这段程序的运行结果是随机的。

  意思就是说:运行结果一会是main run,一会是demo run。存在无序性。

 至于为什么要复写run方法,原因是:
  Thread用于描述线程,该线程就只定义了一个功能,用于存储线程要运行的代码,该存储功能就是run()方法。也就是说Thread中的run()方法,用于存储线程要允许的代码。

(主线程的代码写在main方法中,而main方法是JVM定义的。所以JVM调用main方法的原因是因为主线程要使用,如果将自定义的代码写在main方法中,让JVM去执行,那就是一种单线程(主线程),不是多线程了。)

  

public class ThreadTest extends Thread{@Overridepublic void run(){for(int i=0;i<10;i++){System.out.print(" demo run");}}}
class ThreadDemo{public static void main(String[] args) {ThreadTest dt = new ThreadTest();dt.start();  //①dt.run();  //②for(int i=0;i<10;i++){System.out.print("main run");}}
}

  代码解说:在上段程序中,如果①被注释掉,直接dt.run(),那这就不是一个多线程的执行,只是相当于一个简单的对象调用方法,而线程创建了,并没有运行。

       所以,如果②被注释掉,直接dt.start(),那就是创建了一个线程对象dt,然后dt.start()将线程dt开启,并调用了run()方法,让线程去运行。

实现Runnable接口

  1、定义类实现Runnable接口

  2、复写Runnable接口中的run()方法

  3、通过Runnable建立线程对象

  4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数

      目的:因为自定义的run()方法,所属的对象是Runnable接口的子类对象,所以要让线程去指定对象的run()方法,就必须明确该run()方法的所属对象

  5、调用Thread类的static方法开启线程并调用run()方法

class RunnableTestDemo implements Runnable{@Overridepublic void run() {System.out.println("实现Runnable接口");}}
public class RunnableTest {public static void main(String[] args) {RunnableTestDemo r = new RunnableTestDemo();Thread t1 = new Thread(r);t1.start();}
}

  

  实现Runnable接口与继承Thread的区别:

    1、接口方法避免了Java单继承的局限性

    2、继承Thread,线程代码存放在Thread子类run()方法中

    3、实现Runnable接口,线程代码存放在接口紫子类的 run()方法中

  所以,在选择实现Runnable接口与继承Thread这两者中,建议使用实现Runnable接口的方式

线程的状态

      

    见上图,可看到线程的状态分为:

      1、创建线程(new 线程类),调用start()方法执行run()方法

      2、运行状态:具有执行资格,并且有执行权  

      3、冻结状态:没有执行资格,没有执行权

      4、临时状态(阻塞):具有执行资格,没有执行权。等待cpu的执行权

      5、消亡状态:线程结束

   根据线程的状态的执行原理是:创建一个线程A,调用start()方法,执行run()方法,当线程运行时,遇到sleep(time)或者wait()方法,线程就会进去冻结状态(放弃了执行资格),只有当sleep(time)或者执行了notify()方法,冻结状态就会变成临时状态(阻塞),这时临时状态的线程就会去同其他的线程一起抢夺cpu的执行权,一旦抢到cpu的执行权,该临时状态的线程就会运行(run),变成运行状态run,直到run()方法中的代码执行完,线程才会结束,变成最后的消亡状态。

同步代码块

  较多情况下,Java在处理多线程的时候,会出现一些安全问题,而Java处理多线的安全问题的是方式,是使用同步代码块(synchronized(对象/this/类名.class))或者同步代码函数。

  但是在解决多线程的安全问题时,使用同步代码块的前提是:

    1、必须要有两个或者两个以上的线程

    2、必须是多个线程使用同一个锁(如果存在多个类使用到同步,那么在这几个类中找唯一类,也就是找使用到同步快的共同类)

    3、必须保证听你同步代码块中只能有一个线程在运行。

  同步代码块的好处是解决了线程的安全问题,坏处是多个线程进去同步代码块的时候都要去判断,较为消耗资源。

  在使用同步代码块的前提下,得确认线程是否存在安全性问题,如果找到线程的安全性问题;

    1、明确哪些代码是多线程运行的代码

    2、明确共享数据

    3、明确多线程运行代码中哪些语句是操作的共享数据

同步代码块或者同步代码函数的运行原理:

    1、将需要解决安全问题的代码放到同步代码块中

    2、当多个线程运行时,第一个线程A获得cpu的执行权,首先会来判断真假是否持有锁,如果有,线程A就会进入到同步代码块中,并且进入到里面的第一件时间就是将同步锁给关闭。

    3、然后线程A在同步代码块中,执行同步代码块中的代码。

    4、在此之前,其他线程即使获得cpu的执行权也无法进入到同步代码块中,因为没有获得锁。

    5、当线程A将同步代码块中的代码执行完毕时,最后一步是打开同步锁,然后退出。

    6、这时,其他线程就可以获得cpu的执行权后并且获得同步锁,进入到同步代码块中,执行操作。

public class MyThread implements Runnable{private static int i = 100; //多个线程共享的数据boolean flag = true;public  void printVal(){if(flag){while(true){synchronized(this){if(i>0){System.out.println("-----code"+i--);}}}}else{while(true){printStaticVal();}}}public static void printStaticVal(){while(true){synchronized(MyThread.class){if(i>0){System.out.println("-----Static"+i--);}}}}public void run(){printVal();printStaticVal();}}
class TestSync {public static void main(String[] args) {TestSync t = new TestSync();Thread my1 = new Thread(t);Thread my2 = new Thread(t);my1.start();my2.start();   }
}

  

    上述代码解析的结果是:有两个线程my1和my2,当my1首先抢到cpu的执行权,进去到run方法中,run方法中调用了printVal(val)方法。在这个方法上使用到了同步(synchronized),这样就相当于一个同步函数。当my1进来访问时,首先进行判断是否有锁,如果有,就会进去printVal(val)方法,然后首先就将锁给关闭,然后执行for循环,打印v的值。当for循环结束时,线程my1就会开启同步锁,然后出去。这时线程my2可能就会抢到cpu的执行权,然后进入到同步函数中。

    如果printVal(val)方法不是同步方法,那么就会出现当线程my1抢到执行权进入run方法,执行for循环,当i=1时,执行到if(i>0)完,cpu的执行权被线程my2抢到了,这时它也来判断i = 1,i>0,满足,判断完后,这时cpu的执行权又被my1抢到了,这时,i被打印,然后i--,这时线程my2抢到了,它就不会去执行if判断了,它答应出来i的值就变了-1,这时就出现了多线程的安全问题。

    出现多线程的原因是:当多条语句在操作同一个线程共享数据时,一个线程对多余语句只执行了一部分,还没执行完,另一个线程参与进来执行,导致共享数据的错误。如果一个让一个线程进入方法将语句全部执行完再让其他的线程进来执行,那么线程的安全性的问题就会被解决。因此,我们在方法体上添加了同步(synchronized)。

    在上述代码中,会发现同步代码块(synchronized)后面带的参数(锁)有所不同,函数需要被对象调用,那么函数都有一个对象引用this,所以,一般方法里,同步使用的锁是this。但是如果同步函数是个静态的,而静态方法中不可以定义this,静态在进内存时,内存中没有本类的对象,但是一定有该类对应的字节码对象(类名.Class)——该对象的类型是Class。因此静态同步方法使用的锁是该同步方法所在类的字节码对象(类名.Class)。  

  在Java中,还有一种机制:等待-唤醒机制(wait-notify,notifyAll)

    线程在运行时,线程会开启一个线程池,等待线程就存在线程池中,而且唤醒的是唤醒线程池中第一个等待的线程。

  这种机制用于同步中,因此这种机制在操作同步中的线程时,都必须要标识它们说操作线程持有的锁,只有同一个锁上被等待的线程,才能被痛一个锁给唤醒(notify),不可以对不同锁中的线程进行唤醒。

  也就是说,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义在object中。

停止线程(run方法结束)

  开启多线程运行,运行代码通常是循环结构的,只要控制循环就可以让run方法结束,也就是线程结束。

  

转载于:https://www.cnblogs.com/MoreThinking/p/6902673.html

Java基础之多线程详细分析相关推荐

  1. java消费者模式_基于Java 生产者消费者模式(详细分析)

    生产者消费者模式是多线程中最为常见的模式:生产者线程(一个或多个)生成面包放进篮子里(集合或数组),同时,消费者线程(一个或多个)从篮子里(集合或数组)取出面包消耗.虽然它们任务不同,但处理的资源是相 ...

  2. java生产线消费者,基于Java 生产者消费者模式(详细分析)

    生产者消费者模式是多线程中最为常见的模式:生产者线程(一个或多个)生成面包放进篮子里(集合或数组),同时,消费者线程(一个或多个)从篮子里(集合或数组)取出面包消耗.虽然它们任务不同,但处理的资源是相 ...

  3. Java基础、多线程、JVM、集合八股文自述(持续更新)

    Java基础.多线程.JVM.集合八股文自述 一.Java基础 1.1 object类有哪些方法? getClass().hashCode().equals().clone().toString(). ...

  4. JAVA基础+集合+多线程+JVM

    1. Java 基础 1.1. 面向对象和面向过程的区别 面向过程性能比面向对象高. 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候 等一般采用面向过程开发.但是 ...

  5. Java基础21 多线程线程两种实现方式 锁

    一.多线程的概念 1.程序 :一个固定逻辑与数据的集合 就称为程序 例如淘宝 贪吃蛇小游戏 2.CPU: 中央处理器 主要用于协调程序与硬件进行配置的工作 3.并发与并行 1.并发(高并发) 在同一个 ...

  6. java基础学习-多线程笔记

    说说Java中实现多线程有几种方法 创建线程的常用三种方式: 1. 继承Thread类 2. 实现Runnable接口 3. 实现Callable接口( JDK1.5>= ) 4. 线程池方式创 ...

  7. java lam表达式_详细分析Java Lambda表达式

    在了解Lambda表达式之前我们先来区分一下面向对象的思想和函数式编程思想的区别 面向对象的思想: 做一件事情,找一个能解决这个事情的对象,调用他的方法来解决 函数时编程思想: 只要能获取到结果,谁去 ...

  8. Java基础之多线程框架

    一.进程与线程的区别 1.定义: 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比 ...

  9. 8.Java基础之多线程

    1. 回顾 接口 ①方法(分jdk版本) ②多继承 ③变量特点 ④多态的前提之一 ⑤工厂设计模式,起码要知道简单工厂 ⑥vs 抽象类 异常 ①异常的祖宗类:Throwable ②异常的分类:编译(受检 ...

最新文章

  1. mysql启动后在哪里编程_启动mysql后怎么连接数据库
  2. 看完苹果这场最新发布会,我只能说:太sao了
  3. HTML5与CSS3实战指南读书笔记之一些可能会有用的东西
  4. sharepoint中一些gridview的使用方法
  5. Office 2010 系统要求
  6. java基础:数据类型
  7. js 上下箭头滚动_JS中的this完全讲解,再也不会被this搞晕了
  8. [CMMI]中型项目流程梳理
  9. 反转链表python
  10. 代码审计之CVE-2017-6920 Drupal远程代码执行漏洞学习
  11. python里split_python中使用split()实现嵌套列表理解
  12. 在Excel表中进行度分秒单位转换
  13. 【兴趣】无需电和油的水锤泵的抽水方式
  14. linux 下的加密和解密详解
  15. C-V2X技术发展、应用及展望
  16. 【云原生概念和技术】1.1 云原生的概述
  17. star-rating评分插件的使用
  18. 数字化时代,更多的徐玉玉需要保护
  19. linux下普通文件和目录文件区别
  20. 全球首个开源图像识别系统上线了!

热门文章

  1. Mesos:一个开源的分布式弹性资源管理系统
  2. 【HDOJ】1058 Humble Numbers
  3. rhel6.3挂载HP-EVA6400磁阵--linux端操作流程
  4. 手把手玩转win8开发系列课程(11)
  5. IOS开发中发送Email的两种方法
  6. 删除数据 DataIntegrityViolationException: not-null property references a null or transient value解决...
  7. 一路去**ddss第二天
  8. JAVA8的LocalDateTime使用心得和工具类
  9. TCP 的那些事儿(下)
  10. UI产品设计流程中的14个要点