java多线程总结

ps:参考了多篇大神的技术博文,由于太多就没有一一记录,若侵权请告知~

1.基础概念

  进程/线程/多线程/并发并行/线程(不)安全/同步异步/监视器(锁)/等待池/锁池/临界资源/临界区/多线程和高并发/线程状态转换图/进程的几种状态

进程:系统进行资源分配和调度的基本单位,进程之间相互隔离互不影响。

线程:独立运行和独立调度的基本单位(真正的获取cpu执行的东西,拥有cpu时间片),一个进程中的多个线程中,如果有线程发生异常,那么会影响到整个进程。

线程的优势

  因为线程比进程小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。同一个进程中的线程可以共享内存,共享该进程的资源,但是如果一个进程中的某个线程发生了异常,那么整个进程就会奔溃。

多线程:

  是为了同步完成多项任务,是为了提高资源使用效率来提高系统的效率,让cpu时刻处于高效的运行状态。

线程和进程的关系:
  一个进程最少含有一个线程,进程就好比一辆火车,而线程就好比火车上的每一节车厢;车厢离不开火车,火车同样也需要车厢。

并行
  多个处理器同时执行,真正意义上的同时执行。

并发

  系统通过cpu调度算法,为进程分配时间片,宏观上感觉是同时执行,实际上从cpu操作层面来看是轮流执行。

线程安全(通常用来描述代码):

  在并发情况下,某段代码经过多线程使用,线程的调度顺序不影响最终的结果,这个时候我们使用多线程,只需要关注系统的内存,cpu是否够用即可。线程不安全既反之。

同步:

  同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为

异步:

  异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中,“真实”地执行着。整个过程,不会阻碍调用者的工作。

单线程就是同步,做事只能一件一件的来,多线程的话就如异步,我调用了某个方法,然后会有另外一个线程在真正的执行这个方法。

监视器mointer和锁:

  java中每个对象都有一个监视器,平时不发挥作用--当该对象被synchronized修饰的时候发挥作用;为了发挥监视器的互斥功能,每个对象还拥有一个锁(操作系统中称:信号量/互斥量),如果一个线程拥有了某个数据的锁,那么其他的线程则无法获取该数据的锁,则无法访问数据。为了使数据不被多个线程同时访问,java提供了同步块以及同步方法两种实现锁功能,一旦一段代码被嵌入到一个synchronized关键字中,意味着放入了监视区域(监视器生效),JVM在后台会自动为这段代码实现锁的功能。

等待池:

  假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中;如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池。整个流程大概是:等待池------>锁池------>获取对象的锁(可以执行)

锁池:用于排队获取对象的锁。

临界资源:一次仅允许一个进程使用的共享资源。

临界区:每个进程访问临界资源的那段代码。

多线程和高并发:

  多线程是完成任务的一种方式,而高并发是系统运行的一种状态;通过多线程有助于系统承受高并发状态的实现。高并发需要考虑很多东西例如:硬件配置,网络拓扑,系统架构,数据库优化,jvm调优等等;而多线程在这里只是在同/异步角度上解决高并发问题的其中的一个方法手段,是在同一时刻利用计算机闲置资源的一种方式。多线程在解决高并发问题中所起到的作用就是使计算机的资源在每一时刻都能达到最大的利用率,不至于浪费计算机资源使其闲置。

 2.java开启线程的方式

2-1.继承自Thread类

/*开启多线程的方式:继承Thread类,重写run方法*/
public class MyThread extends Thread {

    @Override
    public void run(){
        System.out.println("myThread is running");
    }
    public static void main(String []args){
        MyThread thread=new MyThread();
        MyThread thread1=new MyThread();
        thread.start();
        thread1.start();
    }
}

2-2.实现Runnable接口

/*开启多线程的方式:实现Runnable接口*/
public class RunnableThread  implements  Runnable{
    @Override
    public void run() {
        System.out.println("this is my Thread");
    }
    public static void main(String []args){
        RunnableThread myThread=new RunnableThread();
        RunnableThread myThread1=new RunnableThread();
        Thread thread=new Thread(myThread);
        Thread thread1=new Thread(myThread1);
        thread.start();
        thread1.start();
    }
}

实现Runnable接口比继承Thread类所具有的优势:

1:适合多个相同的程序代码的线程去处理同一个资源

2:可以避免java中的单继承的限制

3:增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

4:线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

扩展:main方法其实也是一个线程。在java中所有的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个jvm,每一个jvm实际在就是在操作系统中启动了一个进程。

 3.线程类和Object类的某些常见方法

 3.1线程类的方法:

  static void sleep(long millis) 线程休眠

  运行---->阻塞 millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。

  注意:不会释放锁资源,直到休眠结束转为就绪态的时候才会释放
  作用:sleep可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会。

  static void yeild()  线程让步
  运行--->就绪 使当前线程从运行态重新回到就绪态
  注意:不会释放锁资源
  作用:只能让同优先级或者优先级更高的线程获取执行的机会,当然自己又重新获得执行机会的情况也很常见。

   void join(long millis) 线程加入

  在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态;当join(0)时等待一个线程直到它死亡, 当join(1000)时等待一个线程1000纳秒,后回到主线程继续执行.
  存在的意义:因为有的时候,某些线程需要等待另外的一些线程执行的结果来进行操作

  void interrupt() 线程中断

  中断线程,但是不会中断正在执行的线程而是中断被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞的线程将收到一个中断异常,从而提早终结被阻塞状态。  

3.2:Object类的三个方法(必须在有synchronized修饰的地方使用):

  void wait() 线程等待
  线程等待,当前线程释放锁资源并且进入到等待池,等待唤醒。
  void notify() 线程唤醒
  唤醒该对象监视器所在的等待池中的随机一个线程,进入锁池准备争夺锁资源。
  void notifyAll() 线程唤醒
  唤醒该对象监视器所在的等待池中的所有线程,进入锁池准备争夺锁资源。

原文地址:https://www.cnblogs.com/shan-kylin/p/9448946.html