转自(http://blog.csdn.net/jerrying0203/article/details/45563947)
本文学习并总结java多线程与线程间通信的原理和方法,内容涉及java线程的众多常见重要知识点,学习后会对java多线程概念及线程间通信方式有直观清晰的了解和掌握,可以编写并分析简单的多线程程序。

进程与线程

进程:是一个正在执行的程序。
每一个进程执行都有执行顺序,一个执行顺序是一个执行路径,或者叫控制单元;
每一个程序启动时,都会在内存中分配一片空间,进程就用于标识这片空间,并封装一个或若干控制单元。

线程:就是进程中的一个独立的控制单元。
线程控制进程的执行,一个进程至少有一个线程。

Java程序编译时,java编译器启动,对应javac.exe进程启动,编译结束后javac.exe进程退出;java程序运行时,jvm启动,对应java.exe进程启动,java.exe中有一个主线程负责java程序的执行,这个主线程运行的代码就存在于main方法中。其实jvm启动时,不止一个主线程,还有负责垃圾回收机制的线程。

有多条执行路径的程序,称为多线程程序。多线程的好处是可以让程序的多个部分代码产生同时运行的效果,程序的多个功能分支并行执行,优化程序功能结构并提高效率。

自定义创建线程的2种方法

1. 继承Thread类,具体步骤:
    a) 自定义类,继承Thread;
    b) 复写Thread类的run()方法,run()方法中存储线程要运行的代码;
    c)  创建继承Thread的自定义类对象,调用线程的start()方法,start()方法的作用:启动线程,并自动调用run()方法。
2. 实现Runnable接口,具体步骤:
    a) 定义类实现Runnable接口;
    b) 覆盖Runnable接口中的run()方法,run()中存放线程要运行的代码;
    c) 创建Thread类线程对象,并将Runnable接口的子类对象作为实参传递给Thread类构造函数;
    d) 调用Thread类对象的start()方法。
2种方式的区别:
    实现方式时,线程代码存放在实现Runnable接口的子类的run()方法中,可以使用该子类创建多个Thread类,这样多个线程运行时可以共用Runnable子类中的成员变量,实现资源的独立共享。
    继承方式时,线程代码存放在Thread子类的run()方法中,而一个线程不能多次start(),所以达不到Thread子类中资源数据的共享使用。
    自定义线程时,建议使用实现Runnable接口的方式,因为这样还可以避免单继承的局限性。

线程的几个零散知识点

多线程运行结果的随机性:单核CPU环境,多个线程并非真正的同时运行,而是互相抢夺CPU的执行权限和资源,谁抢到谁执行,至于执行多长时间,CPU说了算(所以多线程程序的每次运行结果可能都不一样)(后续可以加以控制)。
多核CPU环境,多个线程可以分布运行到多个CPU上,实现真正的同时运行。
    多核CPU环境上多个线程同时打印输出信息时,可能打印顺序混乱,这是因为多个CPU核抢占DOS输出屏是随机的,有的打印被临时阻塞。
   多核CPU时,程序运行效率就卡在了内在空间上,必须要有足够大的内存存储很多线程,才能让这些线程运行在多个CPU上。
线程状态及状态间切换

已start()过的线程不能再次start(), 否则会报异常java.lang.IllegalThreadStateException。
线程的名称
线程对象都有自己默认的名称:Thread-编号,编号从0开始。
设置自定义线程名称,可以在子类构造函数中调用super(name), 也可以直接创建对象后调用setName()方法。
Thread.currentThread(), 返回当前运行的线程对象,也就是this引用指向的对象。

多线程安全问题

当多条语句在操作多个线程共享数据时,一个线程对多条语句执行了一部分,还没执行完,另一个线程参与进来执行,会导致共享数据的错误。
解决方法:对多条操作共享数据语句,只能让一个线程执行完,在执行过程中,其他线程不可以参与执行。
java对多线程安全问题的专业解决方法就是同步synchronized,具体表现形式有同步代码块和同步函数。
同步代码块

copy

synchronized

  • {
  • }

同步函数:将synchronized作为修饰符放在函数定义上,函数返回值类型前面。
同步的原理
     同步代码块对象如同锁,持有锁的线程可以在同步语句中执行;没有持有锁的线程即使获得了CPU执行权,也进不去,无法执行同步代码。
     同步函数使用的锁是this对象;静态同步函数使用的锁是该函数所在类对应的类字节码文件对象,即类名.class,该对象的类型是Class。
     synchronized修饰符不属于方法签名的一部分,当子类覆盖父类方法时,synchronized修饰符不会被继承,因此接口中方法不能被声明为synchronized,同样,构造函数也不能被声明为synchronized。
     线程进入同步代码块或同步函数前先判断锁标志位,若判断结果为真,则进入同步代码块或同步函数后,修改锁标志位为假,线程退出后,再恢复锁标志位为真。

copy

/*

  • */ class Runnable{
  • tick=;
  • Object obj= Object();
  • run(){
  • (){
  • (obj){
  • (tick>){
  • {Thread.sleep();}(Exception e){e.printStackTrace();}
  • System.out.println(Thread.currentThread().getName()+ public TicketDemo{
  • main(String[] args){
  • Ticket t= Ticket();
  • Thread(t);
  • Thread t2= Thread(t);
  • Thread(t);
  • Thread t4= Thread(t);
  • }

同步的前提
1. 必须要有2个或者2个以上的线程
2. 必须是多个线程使用同一个锁,多个线程可以同时操作同一个锁下的代码。
同步的弊端
1. 线程每次进入同步代码块或同步函数都要判断锁,浪费资源,影响效率
2. 可能出现死锁现象,多发生在一个同步代码块或同步函数中嵌套另一个同步函数或同步代码块,且2个同步上使用不同的锁。即同步中嵌套同步而锁不同就容易引发死锁。
下面是一个很直观的死锁的例子,跟毕老师讲得MyLock的例子原理一样,只是形式上有差别:

copy

class

  • say(){
  • ) ;
  • }
  • get(){
  • System.out.println() ;
  • class
  • say(){
  • ) ;
  • }
  • get(){
  • System.out.println() ;
  • public ThreadDeadLock  Runnable{
  • Zhangsan zs =  Zhangsan() ;
  • Lisi ls =  Lisi() ;
  • flag =  ;
  • run(){
  • (flag){
  • (zs){
  • zs.say() ;
  • {
  • Thread.sleep() ;
  • (InterruptedException e){
  • e.printStackTrace() ;
  • (ls){
  • {
  • (ls){
  • ls.say() ;
  • {
  • Thread.sleep() ;
  • (InterruptedException e){
  • e.printStackTrace() ;
  • (zs){
  • main(String args[]){
  • ThreadDeadLock() ;
  • ThreadDeadLock t2 =  ThreadDeadLock() ;
  • ;
  • t2.flag =  ;
  • Thread(t1) ;
  • Thread thB =  Thread(t2) ;
  • //双方僵持在这,谁都没法继续运行

线程间通讯

Object类方法wait(),notify(),notifyAll()
      线程执行wait()后,就放弃了运行资格,处于冻结状态;线程运行时,内存中会建立一个线程池,冻结状态的线程都存在于线程池中,notify()执行时唤醒的也是线程池中的线程,线程池中有多个线程时唤醒第一个被冻结的线程。
      notifyall(), 唤醒线程池中所有线程。
      wait(), notify(),notifyall()都用在同步里面,因为这3个函数是对持有锁的线程进行操作,而只有同步才有锁,所以要使用在同步中。
      wait(),notify(),notifyall(),  在使用时必须标识它们所操作的线程持有的锁,因为等待和唤醒必须是同一锁下的线程;而锁可以是任意对象,所以这3个方法都是Object类中的方法。

wait和sleep区别:从执行权和锁上来分析这2个方法
wait():可以指定时间也可以不指定时间,不指定时间时,只能由对应的notify()或notifyAll()来唤醒。
sleep():必须指定时间,时间到自动从冻结状态转入运行状态或临时阻塞状态。
wait():线程会释放执行权,并释放锁。
sleep():线程会释放执行权,但是并不释放锁。

单个消费者生产者例子:

copy

class

  • String name;
  • count=;
  • flag=;
  • set(String name){
  • (flag)
  • {wait();}(Exception e){}
  • .name=name++count++;
  • +.name);
  • flag=;
  • .notify();
  • }
  • out(){
  • (!flag)
  • {wait();}(Exception e){}
  • System.out.println(Thread.currentThread().getName()++.name);
  • ;
  • .notify();
  • class Runnable{
  • Resource res;
  • .res=res;
  • run(){
  • (){
  • res.set();
  • class Runnable{
  • Resource res;
  • Consumer(Resource res){
  • .res=res;
  • }
  • run(){
  • (){
  • public ProducerConsumerDemo{
  • main(String[] args){
  • Resource();
  • Producer pro= Producer(r);
  • Consumer(r);
  • Thread t1= Thread(pro);
  • Thread(con);
  • t1.start();
  • //运行结果正常,生产者生产一个商品,紧接着消费者消费一个商品。

但是如果有多个生产者和多个消费者,上面的代码是有问题,比如2个生产者,2个消费者,运行结果就可能出现生产的1个商品生产了一次而被消费了2次,或者连续生产2个商品而只有1个被消费,这是因为此时共有4个线程在操作Resource对象r,  而notify()唤醒的是线程池中第1个wait()的线程,所以生产者执行notify()时,唤醒的线程有可能是另1个生产者线程,这个生产者线程从wait()中醒来后不会再判断flag,而是直接向下运行打印出一个新的商品,这样就出现了连续生产2个商品。
为了避免这种情况,修改代码如下:

copy

class  String name;

  • count=;
  • flag=;
  • set(String name){
  • (flag)
  • {wait();}(Exception e){}
  • .name=name++count++;
  • +.name);
  • flag=;
  • .notifyAll();
  • }
  • out(){
  • (!flag)
  • {wait();}(Exception e){}
  • System.out.println(Thread.currentThread().getName()++.name);
  • ;
  • .notifyAll();
  • public ProducerConsumerDemo{
  • main(String[] args){
  • Resource();
  • Producer pro= Producer(r);
  • Consumer(r);
  • Thread t1= Thread(pro);
  • Thread(con);
  • Thread t3= Thread(pro);
  • Thread(con);
  • t1.start();
  • }

jdk1.5中,提供了多线程的升级解决方案:将同步synchronized替换为显式的Lock操作,将Object类中的wait(), notify(),notifyAll()替换成了Condition对象,该对象可以通过Lock锁对象获取; 一个Lock对象上可以绑定多个Condition对象,这样实现了本方线程只唤醒对方线程,而jdk1.5之前,一个同步只能有一个锁,不同的同步只能用锁来区分,且锁嵌套时容易死锁。

copy

class  String name;

  • count=;
  • flag=;
  • Lock lock =  ReentrantLock();
  • Condition condition_pro=lock.newCondition();
  • Condition condition_con=lock.newCondition();
  • set(String name){
  • lock.lock();
  • {
  • (flag)
  • .name=name++count++;
  • +.name);
  • flag=;
  • }
  • {
  • lock.unlock();
  • out(){
  • lock.lock();
  • {
  • (!flag)
  • System.out.println(Thread.currentThread().getName()++.name);
  • ;
  • condition_pro.signqlAll();
  • {
  • }

线程通信的其他几个常用方法:

终止线程
jdk1.5起,stop()方法(非静态)已过时,不能再使用(否则会报错),终止线程的唯一方法是run()方法结束。
开启多线程运行时,运行代码通过是循环结构,只要控制住循环,就可以让run()方法结束。
中断线程
interrupt()方法,如果线程在调用Object类的 wait()、wait(long) 或wait(long,int) 方法,或者该类的 join()、join(long)、join(long,int)、sleep(long) 或sleep(long,int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。  
线程的中断状态即冻结状态,interrupt()是将处于冻结状态的线程强制地恢复到运行状态。  
守护线程
setDaemon(), 将线程设置为守护线程,当正在运行的所有线程都是守护线程时,jvm自动退出。意思差不多是:前台线程(如main线程)结束后,后台线程(如t1,t2)也自动结束。
setDaemon()方法必须在启动线程前调用。下面是interrupt()和setDeamon()方法的一个示例。

copy

class Runnable{

  • flag=;
  • run(){
  • (flag){
  • {
  • wait();
  • (InterruptedException e){
  • );
  • flag=;
  • );
  • changeFlag(){
  • flag=;
  • public StopTreadDemo {
  • main(String[] args) {
  • StopThread();
  • Thread t1= Thread(st);
  • Thread(st);
  • t1.start();
  • num=;
  • (){
  • (num++==){
  • t1.interrupt();
  • ;
  • +num);
  • );
  • }

join()方法
当A线程执行到了B线程的join()方法时,A就放弃运行资格,处于冻结等待状态,等B线程执行完,A才恢复运行资格;如果B线程执行过程中挂掉,那需要用interrupt()方法来清理A线程的冻结状态;join()可以用来临时加入线程执行。
toString()方法
返回线程名称、优先级和线程组字符串。
默认情况下,哪个线程启动了线程t1, t1就属于哪个线程组,也可创建新的ThreadGroup对象;所有方法,包括main(),线程优先级默认是5;Thread.MAX_PRORITY为10,Thread.MIN_PROTITY为1,NOR_PRORITY为5.
yield()方法
暂时释放执行资格,稍微减缓线程切换的频率,让多个线程得到运行资格的机会均等一些。

转载于:https://www.cnblogs.com/sddychj/p/6252173.html

java多线程与线程间通信相关推荐

  1. Java多线程:线程间通信之Lock

    Java 5 之后,Java在内置关键字sychronized的基础上又增加了一个新的处理锁的方式,Lock类. 由于在Java线程间通信:volatile与sychronized中,我们已经详细的了 ...

  2. Java多线程:线程间通信之volatile与sychronized

    由前文Java内存模型我们熟悉了Java的内存工作模式和线程间的交互规范,本篇从应用层面讲解Java线程间通信. Java为线程间通信提供了三个相关的关键字volatile, synchronized ...

  3. java多线程方式轮询,深入理解JAVA多线程之线程间的通信方式

    一,介绍 本总结我对于JAVA多线程中线程之间的通信方式的理解,主要以代码结合文字的方式来讨论线程间的通信,故摘抄了书中的一些示例代码. 二,线程间的通信方式 ①同步 这里讲的同步是指多个线程通过sy ...

  4. 【java笔记】线程间通信(2):生产者和消费者案例分析

    [java笔记]线程间通信(1):等待唤醒机制_m0_52043808的博客-CSDN博客 类: 资源类:包子类:皮,馅,有无 生产者: 包子铺类(线程类)(继承Thread) 设置线程任务(run) ...

  5. Java基础学习——多线程(线程间通信-生产者消费者代码示例)

    JDK 1.5提供了多线程升级方案 将同步synchronized替换成了显示的Lock操作.可以实现唤醒.冻结指定的线程. Lock接口 Lock 实现提供了比使用 synchronized 方法和 ...

  6. java 生产者消费者_Java多线程:线程间通信—生产者消费者模型

    一.背景 && 定义 多线程环境下,只要有并发问题,就要保证数据的安全性,一般指的是通过 synchronized 来进行同步. 另一个问题是, 多个线程之间如何协作呢 ? 我们看一个 ...

  7. Java进阶知识 - 多线程与线程间通信

    CountdownLatch, CyclicBarrier 分别适合什么场景呢? 大部分情况下, 子线程只需要关心自身执行的任务. 但在某些复杂的情况下, 需要使用多个线程来协同完成某个任务, 这就涉 ...

  8. 【java笔记】线程间通信(1):等待唤醒机制

    线程间通信概念:多个线程处理同一个资源,但是处理的动作却不相同 必要性:多个线程并发执行时,在默认情况下CPU是随机切换线程的,当需要多个线程来共同完成一件任务,并且希望它们有规律的执行,那么多线程之 ...

  9. 11_C++多线程及线程间通信

    参考: https://m.imooc.com/article/289630   C++11 标准库新引入的线程库 https://www.jianshu.com/p/e5a3498ba930 (一) ...

最新文章

  1. 最新电子皮肤的触觉有多灵?连空气流动都能感受到
  2. css揭秘笔记——用户体验
  3. 爬虫笔记12完结篇实例:股票
  4. P4198 楼房重建
  5. 一步步学习微软InfoPath2010和SP2010--第九章节--使用SharePoint用户配置文件Web service(2)--在事件注册表单上创建表单加载规则...
  6. sql 成功率_备考2022年MBA: 如何提高清华、北大MBA提前面试成功率|博雅汇MBA
  7. [CF.Skills]Windows Mobile如何编程实现免提功能
  8. python ray分布式_分布式框架Ray及RLlib简易理解
  9. oracle 对象的审计,初识!聊聊ORACLE的审计功能
  10. 【信息系统项目管理师】第8章-项目质量管理 知识点详细整理
  11. PAT (Basic Level) Practice1019 数字黑洞
  12. 453. Minimum Moves to Equal Array Elements 一次改2个数,变成统一的
  13. 计算机网络-扩展路由器
  14. 拒绝外部投资、融资及收购,Epic是如何成为美国电子病历“一姐”的?
  15. Operation和OperationQueue详解
  16. 软件测试工作职责,软件测试经理岗位职责
  17. 自定义万能回弹插值器 BounceInterpolator
  18. 概率论中两个独立连续随机变量X,Y,变量Z=X+Y的密度函数为X,Y的卷积与特征函数原理
  19. 从0开始学c语言-01-如何完整运行一个程序
  20. 压缩包修改所属目录Linux,linux文件/目录/压缩解压 操作指令

热门文章

  1. Oracle RAC的Failover
  2. Bing Maps开发扩展三:Bing Maps中渲染ESRI SHP空间数据
  3. 怎么查看WINDOWS端口被哪个程序占用
  4. JavaWeb——MyBatis入门程序
  5. 一张图彻底了解Unity脚本的生命周期
  6. 走在网页游戏开发的路上——页游资源管理
  7. VueRouter基础知识记录1
  8. 小程序和网站中无限滚动的实现
  9. android5.1禁用通知栏,android-阻止通知栏
  10. HUE与Oozie的集成