曹阳的博客 仅用于学习和分享

Java线程

2019-11-15

核心类库D6:线程、线程的同步机制

1.线程

1.1 基本概念

  1. 程序 - 数据结构 + 算法,主要指存放在硬盘上的可执行文件。
  2. 进程 - 主要指运行在内存中的程序。
  3. 目前主流的操作系统都支持多进程,为了让操作系统同时执行多个任务从而提高效率,但进程是重量级的,新建进程对系统资源的消耗比较大,因此进程的数量比较局限。
  4. 线程是进程内部的程序流,也就是操作系统中支持多进程,而每个进程的内部又可以支持多线程,并且线程是轻量级的,新建线程会共享所在进程的系统资源,因此以后的开发中都采用多线程技术。
  5. 目前主流的多线程技术采用时间片轮转法实现多线程的并发执行,所谓并发就是宏观并行,微观串行的机制。

2.2 线程的创建(重中之重)

  1. 线程创建和启动的方式:java.lang.Thread类主要用于描述线程,Java虚拟机允许应用程序并发地运行多个执行线程,具体创建和启动方式如下:
    • 自定义类继承Thread类并重写run方法,创建该类的对象来调用start方法。
    • 自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参构造Thread类型的对象,使用Thread类型的对象调用start方法。
    • 注意:线程创建和启动的方式一相对来说代码简单,但Java语言中只支持单继承,若该类继承Thread类后则无法继承其它类;而方式二相对来说代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后开发中推荐方式二。
  2. 相关方法的解析
    • Thread() - 使用无参方式构造对象。
    • Thread(String name) - 根据参数指定的名称来构造对象。
    • Thread(Runnable target) - 根据参数指定的接口引用来构造对象。
    • Thread(Runnable target, String name) - 根据参数指定的引用和名称构造对象。
    • void run() - 若使用Runnable类型的引用构造出来的对象调用该方法,则最终调用引用所指向对象的run方法,否则调用该方法啥也不做。
    • void start() - 用于启动线程,Java虚拟机会自动调用该线程的run方法。
  3. 常用方法
    • static void yield():当前线程让出处理器(离开Running状态),使当前线程进入Runnable状态等待
    • static void sleep(times):使当前线程从Running放弃处理器进入Block状态,休眠times毫秒,再返回到Runnable如果其他线程打断当前线程的Block(sleep),就会发生InterruptedException
    • int getPriority():用于获取线程的优先级
    • void setPriority(int):更改线程的优先级
    • void join():等待该线程终止
    • void join(long millis):表示等待参数指定的毫秒数
    • boolean isDaemon():用于判断是否为守护线程
    • void setDaemon(boolean on):用于设置线程为守护线程
  4. 原理分析
    • 执行main方法的线程叫做主线程,而执行run方法的线程叫做子线程。
    • 程序启动时只有主线程来执行main方法中的代码,当start方法调用成功之后,线程的个数由1个瞬间变成了2个,而新启动的线程去执行run方法的代码,而执行main的线程继续向下执行,两个线程各自独立运行互不影响,当run方法执行完毕后子线程结束,而当main方法执行完毕后主线程结束。
    • 主线程和子线程的先后执行次序没有明确的规定,取决于系统的调度算法。
  5. 注意:
    线程创建和启动的方式一相对代码简单,但Java语言中支持单继承,若该类继承Thread类后无法继承其他类,而线程创建和启动的方式二相对代码复杂,但该方式不影响该类继承其它类而且还可以多实现,因此以后的开发中推荐第二种方式。

2.3 线程的编号和名称(会用即可)

  1. long getId():用于获取调用对象所表示线程的编号
  2. String getName():用于获取调用对象所表示线程的名称
  3. void setName(String name):用于设置线程的名称为参数制定的数值
  4. static Thread currentThread():获取当前正在执行线程的引用

2.4 线程的常用方法(重点)

2.线程的同步机制

2.1 基本概念:

  1. 当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要进行线程之间的通信和协调,该机制就叫线程的同步机制。
  2. 例如:2003年 存折 和 银行卡 对应同一个账户

2.2 解决方案

  1. 由程序结果可知:当两个线程同时进行取款时,导致最终的账户余额不合理
  2. 引发原因:线程一还没有执行完毕取款操作,此时线程二已经开始取款操作
  3. 解决方案:让两个线程的并发操作改为串行操作即可,也就是依次执行取款操作
  4. 方案的缺点:若依次启动多个线程则导致多线程的意义不复存在。

2.3 实现方式

  1. 在Java语言中使用synchronized关键字来实现同步/对象锁机制,来保证线程执行该段代码时的原子性(要么不执行,要么就执行完整),具体方式如下:
    1.使用同步语句块的方式来锁定部分代码  
    synchronized(任意类型的引用){  
     编写需要锁定的代码  
    }  
    2.使用同步方法的方式来锁定所有代码  
    public synchronized void run(){  
    }  
    

2.4 原理分析

> 当多个线程调用start方法后同时去抢占共享资源,由于同步锁的存在导致只有一个线程能够抢到共享资源并进行加锁处理,其它没有抢到共享资源的线程进入阻塞状态,当该线程执行完毕所有锁定的代码后自动释放同步锁,此时阻塞状态的所有线程继续抢占共享资源,抢不到的线程再次回到阻塞状态。  

2.5 死锁的概念

  1. 线程一执行的代码:
    public void run() {  
     synchronized(a) {     持有同步锁a等待同步锁b  
      synchronized(b) {  
          ...  
      }  
     }  
    }  
    
  2. 线程二执行的代码:
    public void run() {  
     synchronized(b) {     持有同步锁b等待同步锁a  
      synchronized(a) {  
          ...  
      }  
     }  
    }  
    
  3. 经验分享:在以后的开发中尽量少使用同步代码块的嵌套结构!

事务与多线程同步的区别与联系

  1. 事务为保证一个操作的原子性而设置的,一个事务必定包含多个操作,多个操作再逻辑上要保证完整一致,如果中间只要有一个操作失败,那么事务必须回滚,必须回到整个操作的初始状态

  2. 多线程为了提高应用的执行效率而设置的,多个线程可以做同样的事情或不同的事情,单个线程只能处理1个客户请求,那么多线程就可以同时处理多个请求。每一个线程处理的业务涉及到多个操作,如果有一致性的要求,那么必须介入事务

  3. 同步是为了解决多线程使用过程中,使用相同资源导致数据不一致而引入的,使用了同步机制,那么多个线程在访问同一资源时,必须等到另一个线程使用完毕,释放了这个资源,其它的线程才有机会使用

作业:

  1. 重点掌握线程创建的两种方式和对象流的使用。
  2. 不断地提示用户输入要发送的内容,若发送的内容是”bye”则聊天结束,
    否则将用户输入的内容写入到文件c:/a.txt中。
    要求使用BufferedReader类来读取键盘的输入 System.in代表键盘输入
    要求使用PrintStream类负责将数据写入文件
  3. 编程创建两个线程,线程一负责打印1 ~ 100之间的所有奇数;
    其中线程二负责打印1 ~ 100之间的所有偶数;
    在main方法启动上述两个线程同时执行,主线程等待两个线程终止;
  4. 编程实现Account类的封装,特征有:账户余额;
    编程实现AccountTest类,在main方法中创建对象并传入1000元,最后打印余额

上一篇 Java文件操作

Content