首页 > 技术文章 > Java多线程

lgh544 2020-05-13 10:46 原文

1、进程与线程的区别

进程:

  •   正在运行的一个程序,作为资源分配的单位。
  •   一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进 程的一部分。
  •     一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

线程:

  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc).
  • 进程可进一步细化为线程,线程是一个程序内部的一条执行路径,一个程序同时执行多个任务通常每一个任务称为一个线程。
  • 一个进程可以同时运行一个以上线程的程序称为多线程程序。

 

 

 

 

多线程的优点:

  • 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  • 提高计算机系统CPU的利用率
  • 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解个修改

2、线程的创建和启动

  通过java.lang.Thread类来提现。

 

Thread类的特性:

  • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常run()方法的主体称为线程体
  • 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()

 

构造器:

  • Thread()创建Thread对象
  • Thread(String threadname)创建线程并指定线程实例名
  • Thread(Runnabletarget)指定创建线程的目标对象,它实现了Runnable口中的run方法
  • Thread(Runnable target, String name)创建新的Thread对象

JDK1.5之前创建新执行线程有两种方法

  • 继承Thread类的方式
  • 实现Runnable接口的方式

  方式一:继承Thread 

  • 1)定义子类继承Thread类。
  • 2) 子类中重写Thread类中的run方法。
  • 3) 使用 new 关键字创建Thread子类对象,即创建了线程对象
  • 4) 调用线程对象start方法:启动线程,调用run方法

注意点:

  • 1.如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
  • 2.run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定
  • 3.要启动多线程,必须调用start方法
  • 4.个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException

  方式二 实现Runnable接口

  • 1) 定义子类,实现Runnable接口。
  • 2) 类中重写Runnable接口中的run方法。
  • 3) 通过Thread类含参构造器创建线程对象。
  • 4) Runnable接口的子类对象作为实际参数传递Thread的构造器中
  • 5) 调用Thread类的start方法:开启线程调用Runnable子类接口的run方法。

继承方式和实现方式的联系与区别

区别:

  继承Thread:线程代码存放Thread子类run方法中。

  实现Runnable:线程代码存在接口的子类的run方法。

 

实现方式的好处:

  避免了单继承的局限性

  多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

线程的调度

Java的调度方法:

   同优先级线程组成先进先出队列(先到先服务),使用时间片策略

   对高优先级,使用优先调度的抢占式策略

优先级:

  MAX_PRIORITY10

  MIN _PRIORITY1

  NORM_PRIORITY5

涉及的方法:

  getPriority() 返回线程优先值

  setPriority(intnewPriority) 改变线程的优先级

说明:

  线程创建时继承父线程优先级

  低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

 

3、线程的生命周期

  在它的一个完整的生命周期中通常要经历如下的种状态

  1. 新建:一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
  2. 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行条件,只是没分配到CPU资源
  3. 运行:当就绪的线程被调度获得CPU资源,便进入运行状态,run()方法定义了线程的操作和功能
  4. 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
  5. 死亡:线程完成了它的全部工作或线程被提前强制性中止或出现异常导致结束

 

 

 4、线程的同步

 模拟火车站售票程序,开启三个窗口售票。

package multiThread;

public class ThreeTickets {

    public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.start();
        t2.start();
        t3.start();
    }

}
class Ticket implements Runnable {
    private int tick = 100;
    public void run() {
        while(true) {
            if(tick > 0) {
                System.out.println(Thread.currentThread().getName()+"售出车票,tick号为:"+tick--);
                
            }
        }
    }
}

 

 

 

 这个时候多线程出现了安全问题,当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。上图t1进入线程但是输出前被阻塞,t2线程执行时,tick的值还是1,继续执行,仍然在输出前被阻塞,t3执行是,tick的值还是没有变,还是为1,继续执行,仍然被阻塞,t1阻塞完继续执行输出语句,打印车票为1号,tick-1 = 0,t2阻塞完成后输出打印车票时tick=0,tick-1= -1,t3阻塞完成继续输出打印,此时tick=-1。t2和t3线程出现了错误。解决办法为对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行,提出了专业的解决方式:同步机制,使用关键字synchronized来达到这一目的。

Synchronized的使用方法

1、synchronized(对象){

  //需要被同步的代码;

}

2、synchronized还可以放在方法声明中,表示整个方法为同步方法

例如:

public synchronized void show (String name){

....

}

必须确保使用同一个资源的多个线程共用一把锁这个非常重要否则就无法保证共享资源的安全

如何确定代码是否存在线程安全非常重要

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

2明确多个线程是否有共享数据

3明确多线程运行代码中是否有多条语句操作共享数据

释放锁的操作

  1)当前线程的同步方法、同步代码块执行结束。

  2)当前线程在同步代码块、同步方法中遇到breakreturn终止了该代码块、该方法的继续执行。

  3)当前线程在同步代码块、同步方法中出现了未处理的ErrorException致异常结束。

  4)当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

线程的死锁问题

死锁:  不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程死锁出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

解决方法专门的算法、原则;尽量减少同步资源定义;尽量避免嵌套同步

Lock()

    JDK 5.0开始Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当

   ReentrantLock 类实现了Lock ,它拥有与synchronized 相同的并发性和内存语义,实现线程安全的控制中,比较常用的是ReentrantLock可以显式加锁、释放锁

     Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)

线程的通信

  wait():令当前线程挂起并放弃CPU同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)调用此方法后,当前线程将释放对象监控权,然后进入等待

  notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待,当前线程必须具有对该对象的监控权(加锁)

  notifyAll():唤醒正在排队等待资源的所有线程结束等待.

三个方法只有在synchronized方法或synchronized代码块中才能使用。

4、新增线程创建方式

方式一:实现Callable接口

方式二:使用线程池

  经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

好处:提高响应速度(减少了创建新线程的时间);降低资源消耗(重复利用线程池中线程,不需要每次都创建);便于线程管理

 

注:尚硅谷多线程学习笔记

  

 

推荐阅读