Java并发编程-基础概念全解

时间:2022-03-15 15:31:26

1、基础

1.1、什么是进程和线程

  进程和线程都是操作系统所运行的程序运行的基本单元。进程可以说是是线程的集合。
  进程:从系统资源讲,进程都有自己独立的地址空间,一个进程的崩溃不会影响另一个进程的执行。
  线程:进程中的一个执行路径,一个进程中可以同时有多个线程在执行,当其中一个线程对公共资源做了修改,其他线程是可以看到的。

1.2、什么是并行和并发

  并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
  并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。

  高并发编程的意义、好处和注意事项
  好处:充分利用cpu的资源、加快用户响应的时间,程序模块化,异步化
  问题:1、线程共享资源,存在冲突;
  2、容易导致死锁;
  3、启用太多的线程,就有搞垮机器的可能

1.3、Java线程基础

1.3.1、如何创建一个线程

  第一种方式:继承Thread类

package com.syw.study.thread;
public class Demo001_Thread extends Thread {
    @Override
    public void run() {
        System.out.println("我是一个线程。。。。。");
    }
    public static void main(String[] args) {
        Demo001_Thread t = new Demo001_Thread();
        t.start();
    }
}

  第二种方式:实现Runable接口

package com.syw.study.thread;
public class Demo003_Thread implements Runnable {
    @Override
    public void run() {
        System.out.println("我是一个实现了Runnable接口的线程.....");
    }
    public static void main(String[] args) {
        Thread t = new Thread(new Demo003_Thread());
        t.start();
    }
}

  第三种方式:匿名类

package com.syw.study.thread;
public class Demo004_Thread{
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是一内部匿名类线程.....");
            }
        });
        t.start();
    }
}

1.3.2、线程的状态、及它们之间的转换关系

  1、New:创建
  2、Runnable:准备好了
  3、Running:运行
  4、Blocked:阻塞
  5、Dead:死亡

  五种状态之间的关系,如下图:
  Java并发编程-基础概念全解

 

  现在对以上图中12条线分别解释说明:
  1、Thread对象的start()方法,使线程由“新建”状态转变为“可运行状态”, 此状态下,线程在等待CPU资源;
  2、此时线程获得CPU资源,变为“运行”状态,开始执行自己的业务代码,即:run()方法中的代码;
  3、有三种情况,导致线程死亡,一、run()方法结束;二、线程响应Interrupt异常;三、抛出异常;
  4、Thread对象的yield()方法使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程;
  5、6、Thread类的sleep()方法,让线程阻塞指定时间,此时如果它正占有某对象的锁,那么不会释放锁;指定时间一过,线程重新回到运行状态;
     Thread类的suspend()方法,同样也使线程进入阻塞状态,也不会释放锁,只不过需要resume()方法让线程回到运行状态;
  7、8、当前线程调用 XX线程.join() 方法,当前线程进入阻塞状态(当前线程调用了XX线程.wait()方法),直到XX线程执行完毕,当前线程回到Runnable状态(线程运行完毕后会调用自身的notifyAll()方法);
  9、10、当前线程在获得对象锁的情况下,调用该对象的wait()方法,会使用当前线程进入阻塞状态,直到其他线程调用此对象的notify() 或 notifyAll()方法;
  11、12、当前线程遇到synchronized关键字,如果此时锁被占用,则当前线程进入阻塞状态,等待其他线程调用notify() 或 notifyAll()方法,使自己进入Runnable状态,等待获得CPU执行时间;


1.3.3、怎么样才能让Java里的线程安全停止工作

  1、线程自然终止:自然执行完或抛出未处理异常
  2、stop(),resume()和suspend()已不建议使用,stop()会导致线程不会正确释放资源,suspend()不释放锁资源,容易导致死锁。
  3、java线程是协作式,而非抢占式,调用一个线程的interrupt() 方法中断一个线程,并不是强行关闭这个线程,只是跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。
  4、注意事项:
    1)isInterrupted() 判定当前线程是否处于中断状态。
    2)static方法interrupted() 判定当前线程是否处于中断状态,同时中断标志位改为false。
    3)方法里如果抛出InterruptedException,线程的中断标志位会被复位成false,如果确实是需要中断线程,要求我们自己在catch语句块里再次调用interrupt()。   

 
1.4、线程的协作 wait()、notify()、notifyAll() 

  wait()、notify()、notifyAll() 都是Object类的实例方法;

1.4.1、等待和通知的标准范式

  等待方:
  1、获取对象的锁;
  2、循环里判断条件是否满足,不满足则调用wait方法,释放锁,给其他线程获得锁资源的机会;
  3、条件满足执行业务逻辑;
  通知方来说
  1、获取对象的锁;
  2、循环里改变条件(保证等待方条件的满足);
  3、调用notify或notifyAll方法通知所有等待在对象的线程;
  4、如果是循环体,调用wait方法释放锁;

1.4.2、notify和notifyAll应该用谁?
  
notifyAll :使所有原来在该对象上等待被notify的所有线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争; 
  notify : 它只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们;

  notify :因为每次只唤醒一个线程,所以要求每个线程执行完业务后,必须执行notify一下,否则所有等待锁的线程永远没机会获得锁(都处于等待notify状态,而不是等待锁状态) ; 
  notifyAll : 则就免去了线程运行完了notify通知其他线程的必要,因为已经通知过了; 

  所以推荐尽量使用notifyAll,使用notify因为有可能发生信号丢失的的情况;
 

1.4.3、join()方法
  把指定的线程加入到当前线程,将两个交替执行的线程合并为顺序执行的线程;
  比如:线程A,执行了线程B的join方法,线程A必须要等待B执行完成了以后,线程A才能继续自己的工作;


1.4.4、调用yield() 、sleep()、wait()、notify()等方法对锁有何影响?
  yield():线程在执行yield()以后,持有的锁是不释放的
  sleep():方法被调用以后,持有的锁是不释放的
  wait():调动方法之前,必须要持有锁。调用了wait()方法以后,锁就会被释放,当wait方法返回的时候,线程会重新持有锁
  notify():调动方法之前,必须要持有锁,调用notify()方法本身不会释放锁的

 

==============================================
我不能保证文章中每个观点都是对的,但是至少保证每一句话、每一行代码都经过了认真的推敲、仔细的斟酌。
每一篇文章的背后,希望都能看到自己对于技术、对于生活的态度。
==============================================