Java并发(多线程)

一、多线程的基本概念

  1.什么是进程、多进程有什么作用?

    大家都使用计算机,当我们打开某一个软件的时候,其实就是启动了一个进程,可以打开任务管理器看看,我们打开的每一个软件,都是一个进程,在同一个操作系统中,可以同时启动多个进程。

    单进程计算机只能在一个时间内做一件事情。在我们打开办公软件的的时候,也可以同时听歌,为什么呢?不是只能做一件事情吗,这是因为操作系统将cpu的时间片分配给了每一个进程,让我们感觉是并行处理的。

    多进程不是为了提高执行速度,是为了提高CPU使用率,具体只是可以去学习操作系统进行了解

    进程与进程之间是独立的

  2.什么是线程,多线程有什么作用?

    线程是进程中的一个执行场景,他们是共享数据的(堆内存和方法区内存、栈内存是独立的),一个线程一个栈

    与进程相比,线程更轻量级,创建、撤销一个线程比启动新进行的开销要小得多

  3.java程序的运行原理

    java命令会启动Java虚拟机,启动JVM,等于启动了一个应用程序,表示启动了一个进程,该进程会自动自动一个“主线程”,然后主线程去调用某个类的main方法,所以main方法运行在主线程中,在此之前所有的程序都是单线程的。

二、线程的创建和启动

  1、继承Thread类

 1 public class Test{
 2     
 3     public static void main(String[] args) {
 4         Person person = new Person();
 5         person.start();  //start不是马上执行,而是使线程进入就绪,真正执行是由JAVA的线程调度机制来完成
 6         
 7         for(int i=0; i<10; i++){
 8             System.out.println("main ->" + i);
 9         }
10     }
11     
12 }
13 class Person extends Thread{
14      public void run(){
15          for(int i=0; i<30; i++){
16              System.out.println("run ->" +i);
17          }
18      }
19 }

  2、实现Runnable接口(推荐)

 1 public class Test{
 2     
 3     public static void main(String[] args) {
 4         Person person = new Person();
 5         Thread t = new Thread(person);
 6         t.start();
 7         
 8         for(int i=0; i<10; i++){
 9             System.out.println("main ->" + i);
10         }
11     }
12     
13 }
14 class Person implements Runnable{
15      public void run(){
16          for(int i=0; i<30; i++){
17              System.out.println("run ->" +i);
18          }
19      }
20 }

    由于Runnable是一个函数式接口,可以用lamda表达式建立一个实例:

1 Runnable r = () ->{...};

三、线程的生命周期

    这是网上找的图

    初始状态:新创建一个线程对象

    可运行状态:也就是就绪状态,调用start()方法后,该线程有权利获取cpu时间片

    运行状态:获取到cpu时间片后开始运行,如果这次没有运行完,下一次接着运行,不是从头开始

    阻塞状态:因为某种原因放弃了cpu的使用权,暂时停止运行,直到线程调度器重新激活它     

      (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
      (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
      (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重                           新转入就绪状态。(注意,sleep是不会释放持有的锁)

    死亡状态:线程执行完了或因异常退出了run方法

  方法比较: 

  1. Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIME_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
  2. Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变会就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。
  3. t.join()/t.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入TIME_WAITING/TIME_WAITING状态,当前线程不释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程进入就绪状态。
  4. obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
  5. obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。

  为什么弃用stop和suspend方法?

   stop方法天生就不安全,当线程被终止,立即释放对象锁,这会导致对象处于不一致的状态。例如:在转账的时候,钱已转出,对方还没到账,对象锁已经被破坏;当线程要终止另一个线程的时候,无法知道什么时候调用stop方法是安全的,什么时候导致对象被破坏

     在看suspend,和stop不同,suspend不会破坏对象,但是如果用suspend挂起一个持有一个锁的线程,该锁在恢复之前是不可用的,如果要调用suspend方法的线程试图获得同一个锁,那么程序死锁;被挂起的线程等着被恢复,而将其挂起的线程等待获得锁

补一张图(牛客上看见的):

1. 新建( new ):新创建了一个线程对象。
2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。
3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。
4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。阻塞的情况分三种:
(一). 等待阻塞:运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放 入等待队列( waitting queue )中。
(二). 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。
(三). 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。            当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。
5. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。
原文地址:https://www.cnblogs.com/ccxka/p/9524605.html