程序:计算机指令的集合,它以文件的形式存储在磁盘上,是应用程序执行的蓝本。
进程:是一个程序在其自身的地址空间中的一次执行活动。进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源。而程序不能申请系统资源,不能被系统调度,也不能作为独立运行的单位,它不占用系统的运行资源。作为蓝本的程序可以被多次加载到系统的不同内存区域分别执行,形成不同的进程。基于进程的特点是允许计算机同时运行两个或更多的程序。
线程:是进程中的一个单一的顺序控制流程,一个进程在执行过程中,可以产生多个线程。每个线程也有自己产生、存在和消亡的过程。
线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间。这使得线程间的通信远较进程简单,而进程之间的通信则比较困难,另外在资源的占用上,线程比进程要小。

线程(Thread)和进程(Process)的关系很密切,进程和线程是两个不同的概念,进程的范围大于线程

通俗的讲,进程就是一个程序,线程是这个程序能够同时做的各件事情。例如:媒体播放器运行时就是一个进程,而媒体播放器同时下载文件和播放歌曲就是两个线程。
从另一个角度讲,每个进程都拥有一组完整的属于自己的变量,而线程则共享一个进程中的这些数据。

主线程:程序启动时,一个线程立即运行,该线程称为程序的主线程。(main()方法),其他线程都是由主线程产生的,主线程通常必须最后完成执行,因此需要执行各种关闭动作。

观察以下程序:

  public class MainThreadDemo  {public static void main(String args[])   {Thread t = Thread.currentThread();System.out.println("当前线程名称是: " + t.getName());t.setName("MyJavaThread");System.out.println("改名后线程名称是: " + t.getName());System.out.println("输出当前线程: " + t);       }
}

代码分析:

  • Thread.currentThread()是一个静态方法,返回正在执行的线程对象的引用。
  • getName()方法可以得到当前引用线程的名称
  • setName(String s)可以改变线程的内部名称
  • 当将一个线程对象作为输出时,将输出[线程名称,优先级别,线程组名 ]
  • 每个线程都属于一个线程组,如果没有设定,则由JVM来设定。

并发编程:在计算机编程中有一个基本概念,就是在同一时刻处理多个任务的思想。许多程序设计问题都要求程序能够停下正在做的工作,转而处理某个其他问题,然后再返回主进程。多线程的任务相比传统的进程而言(只有一个主线程),可以同时有多个地方执行代码。

把问题切分成多个可独立运行的部分(任务),从而提高程序的响应能力。在程序中,这些彼此独立运行的部分称之为线程,上述概念被称为“并发”。

Java提供了类 java.lang.Thread 来方便多线程编程,这个类提供了大量的方法方便控制线程.

Thread类最重要的方法是run(),它为Thread类的方法start()所调用,为了指定我们自己的代码,需要覆盖run()方法,来提供我们线程所要执行的代码。

创建线程

  1. 继承java.lang.Thread()类,覆盖run()方法。在创建的Thread类的子类中重写run(),加入线程所要执行的代码。
class mythread extends Thread
{public void run(){}
}

例如:

package Test;class FileTransThread extends Thread{ private String fileName; public FileTransThread(String fileName){ this.fileName = fileName; } public void run(){ System.out.println("传送" + fileName); try{ Thread.sleep(1000 * 10); }catch(Exception ex){} System.out.println(fileName + "传送完毕"); }
}
public class Main { public static void main(String[] args) throws Exception { FileTransThread ft1 = new FileTransThread("文件1"); FileTransThread ft2 = new FileTransThread("文件2"); FileTransThread ft3 = new FileTransThread("文件3"); ft1.start(); System.out.println("1");System.out.println("2");ft2.start();System.out.println("3");System.out.println("4");ft3.start(); System.out.println("5");System.out.println("6");}
}

运行结果:(两次完全相同的运行,但是结果却不同,说明线程大概是同步进行的,而且顺序是随机的)

  1. 继承java.lang.Thread()类方法简单明了,符合大家的习惯。可是如果这个类已经继承了一个类,则没有办法再继承java.lang.Thread()类。这时我们可以实现java.lang.Runnable接口,并实现run()方法。然后通过这个类创建线程。详见实例:
class mythread implements Runnable  {public void run( )  {/* 实现该方法*/ }
}

例如:

package Test;class FileTransRunnable implements Runnable
{ private String fileName; public FileTransRunnable(String fileName){ this.fileName = fileName; }public void run(){ System.out.println("传送" + fileName); try{ Thread.sleep(1000 * 10); }catch(Exception ex){} System.out.println(fileName + "传送完毕"); }
}
public class Main
{ public static void main(String[] args) throws Exception { FileTransRunnable ft1 = new FileTransRunnable("文件1"); FileTransRunnable ft2 = new FileTransRunnable("文件2"); FileTransRunnable ft3 = new FileTransRunnable("文件3");Thread f1=new Thread(ft1);   //创建线程Thread f2=new Thread(ft2);Thread f3=new Thread(ft3);f1.start(); f2.start();f3.start();}
}

运行对象:

  • 对象可以自由地继承自另一个类。
  • 同一个runnable对象可以传递给多个线程,可以实现资源共享。

线程的启动

新建的线程不会自动开始运行,必须通过strat()方法启动线程。如果不调用这个方法,线程将不会运行。

线程的实现

线程从创建、启动到终止的整个过程叫做一个生命周期。线程总共由五个状态。

  1. 新建状态(new):创建之后还没有调用start()
  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法,处于该状态的线程位于可运行线程池中,成为可运行,等待获取CPU的使用权。线程有资格运行但调度程序还没有把他选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
  3. 运行状态(Running):线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态,这也是线程进入运行状态的唯一一种方式。就绪状态的线程获取了CPU,执行程序代码。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
  • 等待阻塞:运行的线程执行wait()方法,该线程放入等待池中。
  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,该线程放入锁池中。
  • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  1. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。这个线程对象也许还存在,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

简单来讲:

  1. 创建状态:使用new运算符创建一个线程。
  2. 可运行状态:使用start()方法启动一个线程后,系统分配了资源。
  3. 运行中状态:执行线程的run()方法。
  4. 阻塞状态:运行的线程因某种原因停止继续运行。
  5. 死亡状态:线程结束。

线程的调度

  1. 线程睡眠:public static void sleep(long millis) throws InterruptedException
    millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()方法的平台移植性较好。
  2. 线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法是Object类中的方法,行为等价于调用 wait(0) 一样。
  3. 线程让步:public static void yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。通过yield()方法,当前线程把cpu让给别的线程,而不用进入休眠状态而等待很长时间。该方法只影响当前正在运行的线程,且没有任何机制保证它将会被采纳。
  4. 线程加入:public final void join() throws InterruptedException 方法,在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
    另外,join()方法还有带超时限制的重载版本。 例如t.join(5000);则让线程等待5000毫秒,如果超过这个时间,则停止等待,变为可运行状态。
  5. 线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。

多线程的互斥和同步

线程的同步

在多线程的程序中,多个线程可能会对同一个资源并发访问。这种情况下,如果不对共享的资源进行保护,就可能产生问题。
所谓同步(synchronize),就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。
通俗地讲,一个线程是否能够抢占CPU,必须考虑另一个线程中的某种条件,而不能随便让操作系统按照默认方式分配CPU,如果条件不具备,就应该等待另一个线程运行,直到条件具备。

同步的原理

Java中每个对象都有一个内置锁,当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。

一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。释放锁是指持锁线程退出了synchronized同步方法或代码块。

同步的实现

在Java中通过互斥锁标志Synchronized关键字的运用来实现同步。Java中同步有两种方法:

  1. 方法级同步
 synchronized void method( ) {  //同步的方法}
  • 实现方法:在要标志为同步的方法前加上synchronized关键字。如:public synchronized void call(String msg){ }
  • 实现原理:当调用对象的同步方法时,线程取得对象锁或监视器,如果另一个线程试图执行同步方法,他就会发现被锁住了,就会进入挂起状态,直到对象监视器上的锁被释放为止。当锁住放啊的线程从方法中返回时,只有一个排队等候的线程可以访问对象。
  • 锁的作用域:该方法被执行的整个时间。
  1. 程序块级同步
synchronized()object
{}
  • 临界区:只希望防止多个线程同时访问方法内部的部分代码,而不是防止访问整个方法,通过这种方式分离出来的代码段被称为“临界区”,即需要进行互斥的代码段。
  • 实现方法:用synchronized来指定某个对象,此对象的锁被用来对花括号内的代码进行同步控制。如:
synchronized(target){target.call(msg);}
  • 实现原理:在进入同步代码前,必须得到object对象的锁,如果其他线程已经得到这个锁,那么就得等到锁被释放后才能进入临界区。
  • 锁的作用域:只在代码块运行的时间内

例子:

class TicketRunnable implements Runnable
{ private int ticketNum = 3; //  以3 张票为例 public void run() { while (true) { String tName = Thread.currentThread().getName(); //  将需要独占 CPU的代码用 synchronized(this)包围起来 synchronized (this) { if (ticketNum <= 0) { System.out.println(tName + "无票"); break; } else { try { Thread.sleep(1000);//  程序休眠 1000 毫秒 }catch (Exception ex) {} ticketNum--; // 代码行1 System.out.println(tName + "卖出一张票,还剩" + ticketNum + "张票'); } } } }
} public class Main
{ public static void main(String[] args){ TicketRunnable tr = new TicketRunnable(); Thread th1 = new Thread(tr, "thread 1"); Thread th2 = new Thread(tr, "thread 2"); th1.start(); th2.start(); }
} 

从以上代码可以看出,该方法的本质是将需要独占 CPU 的代码用synchronized(this)包围起来。如前所述,一个线程进入这段代码之后,就在 this 上加了一个标记,直到该线程将这段代码运行完毕,才释放这个标记。如果其他线程想要抢占 CPU,先要检查 this 上是否有这个标记。若有,就必须等待。

但是可以看出,该代码实际上运行较慢,因为一个线程的运行,必须等待另一个线程将同步代码段运行完毕。因此,从性能上讲,线程同步是非常耗费资源的一种操作。我们要尽量控制线程同步的代码段范围,理论上说,同步的代码段范围越小,段数越少越好,因此在某些情况下,推荐将小的同步代码段合并为大的同步代码段。

死锁

如果出现一种极端情况,一个线程等候另一个对象,而另一个对象又在等候下一个对象,以此类推。这个“等候链”如果进入封闭状态,也就是说,最后那个对象等候的是第一个对象,此时,所有线程都会陷入无休止的相互等待状态,造成死锁。尽管这种情况并非经常出现,但一旦碰到,程序的调试将变得异常艰难。
发生死锁必须同时满足的四个条件:

  1. 互斥条件。线程中使用的资源中至少要有一个是不能共享的。
  2. 至少有一个线程它必须持有一个资源且正在等待获取一个当前被别的线程持有的资源。
  3. 资源不能被线程抢占。
  4. 必须有循环等待,这时,一个线程等待其他线程所持有的资源,后者又在等待另一个线程所持有的资源。

死锁的实例:(待更,脑袋不转了)

【Java学习笔记九】多线程相关推荐

  1. java学习笔记(九)----多线程

    class ThreadDemo1 { public static void main(String[] args)   { new TestThread().start(); //用start()默 ...

  2. Java学习笔记5-1——多线程

    目录 前言 核心概念 线程创建 继承Thread类 实现Runnable接口 上述两个方法小结 实现Callable接口 并发问题简介 静态代理模式 线程状态 线程停止(stop) 线程休眠(slee ...

  3. Java学习笔记2 多线程简单总结

    多线程简单总结 1. 相关概念 1.1 线程与进程 进程 线程 1.2 线程调度 分时调度 抢占式调度 1.3 同步与异步 同步 异步 1.4 并发与并行 并发 并行 2. 创建线程 2.1 继承Th ...

  4. 【Java学习笔记】——多线程

    [笔记]跟着狂神学Java-多线程篇 线程/进程 我的理解:电脑上执行的一个游戏窗口是一个进程,每个模式或者按钮是一个线程 继承Thread类 一个类继承了Thread类之后,可以重写Thread类中 ...

  5. JAVA学习笔记--4.多线程编程 part5.这些年的那些坑

    2019独角兽企业重金招聘Python工程师标准>>> 基本要求 没指定线程name. 没有限定线程个数. Thread#stop等被废弃的方法不安全,详见TODO. 注意锁作用的对 ...

  6. JAVA学习笔记--4.多线程编程 part1.背景知识和内存模型

    2019独角兽企业重金招聘Python工程师标准>>> 背景知识 CPU Cache 如上简易图所示,在当前主流的CPU中,每个CPU有多个核组成的.每个核拥有自己的寄存器,L1,L ...

  7. Java学习笔记5-2——多线程

    目录 线程同步 三大不安全案例 一.不安全的买票过程 二.不安全的取钱过程 三.线程不安全的集合 synchronized 解决三大不安全案例 一.解决不安全的买票过程 二.解决不安全的取钱过程 三. ...

  8. java时间规划书_【计算机本科补全计划】Java学习笔记(九) Java日期时间

    正文之前 终于好像仿佛看完了菜鸟教程的Java课程,感觉自己收获颇丰!很好,Java看完之后正愁如何开始进阶呢!结果发现菜鸟还准备了Java实例这种好东西!简直就是教程界的良心啊 !!!没事,先写写笔 ...

  9. JAVA学习笔记(九)- 初始化块与静态代码块

    初始化块 /** 初始化块* 初始化变量方式:声明时.构造函数.代码块* 使用static关键字修饰的代码块,称为静态代码块* * 执行顺序:静态代码块>代码块>构造方法* * 静态代码块 ...

最新文章

  1. 全球数百万台 Mac 疑似因 Big Sur 更新险酿计算灾难,苹果官方回应来了!
  2. 【转】mysqldump的锁表的问题
  3. careercup-数组和字符串1.7
  4. adf开发_ADF BC:创建绑定到业务组件的UI表
  5. micropython教程modbus_基于S7-300400 CPU集成PN接口的Modbus TCP在TIA Portal的使用入门教程...
  6. python+tensorflow+captcha库:基于TF快速破解验证码
  7. 详细教程Desktop Goose又在整什么幺鹅子呢?
  8. Atitit.常用语言的常用内部api 以及API兼容性对源码级别可移植的重要性 总结
  9. 电脑、手机 自动化 键鼠操作( 类似按键精灵 )
  10. 基于SSM的物业管理系统-JSP MYSQL小区物业费管理系统
  11. bmfont使用心得
  12. 时间序列分类实践介绍(使用Python代码)
  13. android手机otg,OTG是什么?Android手机OTG功能怎么开启和使用?
  14. pytorch torchvision.datasets
  15. 如何修改本地hosts文件
  16. 各航空公司的网址和电话
  17. 公有云弹性IP的实现原理及优势
  18. Linux的一些快捷键
  19. 经济机器是怎么运行的
  20. 【观点】区块链手机说到底还是伪命题

热门文章

  1. flask基础之jinja2模板-语法定义
  2. 系统架构的演变 -----自 罗文浩
  3. 九度OJ1486 /POJ 1029/2012北京大学研究生复试上机
  4. Ajax和Jsonp实践
  5. 使用IndexReader.repen提高搜索速度
  6. Oracle转Sqlserver 记录
  7. How to use fb.data.query to get friends info?
  8. (转)利用MS AJAX 扩展服务器端控件
  9. php date当天,php5中date()获得的时间不是当前时间的解决方法
  10. mysql 数据库 应用_MySQL数据库的应用