JAVA多线程深度学习

  • 线程与进程

所有的操作系统都支持进程,当一个程序进入内存时就变成了一个进程。进程就是处于运行过程中的程序,并且具有一定的独立能力,进程是系统进行资源分配和调度的一个独立单元。

进程包含如下3个特征:

  • 独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个进程不可以直接访问其他进程的地址空间。
  • 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。进程具有自己的生命周期和各种不同的状态,这些在程序中都是不具备的。
  • 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

    线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其他线程共享该进程所有拥有的全部资源。因为多个线程共享父进程里的全部资源,因此编程更加方便,但必须要注意的一点是:确保线程不会妨碍同一进程里的其他线程。

    线程可以完成一定的任务,可以与其他线程共享父进程中的共享变量及部分环境,相互之间协同来完成进程所要完成的任务。

    线程是独立运行的,它并不知道进程中是否存在其他的线程存在。线程的执行是抢占式的,也就是当前运行的线程在任何时候都有可能被挂起,以便另外一个线程可以运行。

    一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。

    从逻辑角度来看,多线程存在于一个应用程序中,让一个应用程序中可以有多个执行部分同时执行,但操作系统无需将多个线程看成多个独立的应用,对多线程实现调度和管理以及资源分配。因为线程的调度和管理是由进程本身负责完成。

    简而言之,一个程序运行后至少有一个进程,一个进程可以包含多个线程,但至少要包含一个线程。

  • 使用多线程编程具有如下优点:

  • 进程之间不能共享内存,但线程之间共享内存非常容易。
  • 系统创建进程是需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高。
  • Java语言内置了多线程功能支持,而不是单纯的作为底层操作系统的调度方式,从而简化了Java的多线程编程。  

线程的创建与启动在上一篇博客 这里就不一一介绍了(懒)

线程的生命周期

新建和就绪状态

注意:启动线程使用start()方法,而不是run()方法,永远不要调用线程对象的run()方法,调用线程的start方法来启动线程,系统则会把该run方法当成线程执行体来处理。

运行和阻塞状态、

线程发生阻塞状态时的情况:

  • 线程调用sleep()方法主动放弃所占用的处理器资源。
  • 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞。
  • 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。关于同步监视器的知识,后面会进行讲解。
  • 线程在等待某个通知(notify)
  • 程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法。

当前正在执行的线程被阻塞后,其他线程就可以获得执行的机会。被阻塞的线程会在合适的时候重新进入就绪状态,合适的时候就是指线程的阻塞解除后,会重新进入就绪状态,等待线程调度器的再次调度。

在发生如下特定情况下,可以解除线程的阻塞状态,让线程重新进入就绪状态。

  • 调用sleep()方法的线程经过了指定时间。
  • 线程调用的阻塞式IO方法已经返回。
  • 线程成功地获得了试图取得的同步监视器。
  • 线程正在等待某个通知时,其他线程发出了一个通知。
  • 处于挂起的线程被调用了resume()恢复方法。

线程死亡:

    1、方法执行完,正常结束

    2、使用一个标志位进行终止变量,当flag=false;则线程终止

    3、直接使用stop或destoy  不推荐使用 容易造成死锁

线程礼让:

    1、让当前正在执行的线程挺值,但不阻塞

    2、将线程从运行状态转为就绪状态

    3、让cpu重新调度,不一定成功

Join线程:join合并线程,待此线程执行后,在执行其他线程,其他线程阻塞死亡后的线程不能再次启动

线程休眠:

    1、可以通过调用Thread类的静态sleep()方法来实现,()指定当前线程阻塞的毫秒数,1000毫秒=1秒

    2、sleep存在异常 InterruptedException,可以放大问题的发生性

测试线程优先级:

     Thread类提供了setPriority(int newPriority),getPriority()方法来设置和获取指定线程的优先级

线程同步:

同步方法:就是使用synchronized关键字来修饰某个方法,则此方法被称为同步方法,锁的是this,当前调用的类对象

同步块: synchronized(obj){  };

obj称之为同步监视器,可以使任何对象,但推荐共享资源作为同步监视器

同步方法中无需指定同步监视器,因为同步方法监视器就是this,这个对象本身或者是class

同步监视器执行过程:

1、第一个线程访问锁定同步监视器,执行其中代码

2、第二个线程访问,发现同步监视器被锁定,无法访问

3、第一个县城访问完毕,解锁同步监视器

4、第二个线程访问,发现同步监视器没有锁,然后锁定并访问

死锁

  多线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情况,其中一个同步块同时拥有两个以上对象的锁时就可能会方发生死锁的问题

产生死锁的条件

1、互斥条件:一个资源每次只能被一个进程使用

2、请求与保持条件:一个进程引擎求资源而阻塞时,对已获得的资源保持不放

3、不可剥夺条件:进程已获得的资源,在未使用完之前不能强行剥夺

4、循环都等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

Lock锁

利用ReentrantLock 可重复锁

calss A {
        Private final ReentrantLock=new ReenTrantLock();
        public void m() {
            lock.lock();
                try{
                        //保证线程安全的代码
             }
                  finallu{
                            lock.unlock();
                }

synchronized与lock的对比

1、Lock是显式锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放

2、Lock只有代码块锁synchronized有代码块锁和方法锁

3、使用Lock锁 JVM将话费较少时间来调度进程,性能更好,并且具有很好地扩展性(提供更多子类)

优先使用顺序:Lock>同步代码块>同步方法

线程池

背景:将经常创建和销毁实用量特别大的资源,比如并发情况大的线程,对性能影响很大

思路:提前创建好多个线程,放入线程池,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用

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

你可能感兴趣的