美文网首页
java并发编程基础

java并发编程基础

作者: G__yuan | 来源:发表于2019-07-15 14:41 被阅读0次

    1.java并发基础编程基础

    1.1什么是线程

    线程是进程中的一个实体,线程本身是不会独立存在的。进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。

    每个线程都有自己的栈资源,用于存储该线程的局部变量,这些局部变量是该线程私有的,其他线程是访问不了的,除此之外栈还用来存放线程的调用栈帧。

    堆是一个进程中最大的一块内存,堆是被进程中的所有线程共享的,是进程创建时分配的,对里面主要存放使用new操作创建的对象实例。

    方法区则用来存放JVM加载的类、常量及静态变量等信息,也是线程共享的。

    1.2线程的创建与运行

    小结

    使用继承方式的好处是方便传参,可以在之类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递。不好的地方是java不支持多继承,如果继承了Thread类,那么子类不能再继承其他类,而Runable则没有这个限制,这两种方式都没有办法拿到任务的返回结果,但是Futuretask方式可以。

    1.3线程通知与等待

    什么是等待通知:

    通俗来讲:

    拿一个厨师和服务员之间的例子来说:

    (1).厨师做完一道菜的时间是不确定的,所以菜到服务员手中的时间是不确定的;

    (2).服务员就需要去等待(wait);

    (3).厨师把菜做完之后,安一下铃,这里的按铃就是通知(nofity)

    (4).服务员听到铃声之后就知道菜做好了,他可以去端菜了。

    用专业术语讲:

    等待/通知机制,是指一个线程A调用对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()/notifyAll()方法,线程A收到通知后退出等待队列,进入可运行状态,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()方法和notify()/notifyAll()方法的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

    1> Wait()函数

     当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起,直到发生下面几件事情之一才返回:

    (1).其他线程调用了该共享对象的notify()或者notifyAll()方法;

    (2).其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。

      需要注意:如果调用wait()方法的线程没有事先获取该对象的监视器锁,则调用wait()方法时调用线程会抛IllegalMonitorStateException异常。

    一个线程获取一个共享变量的监视器锁的方式:

    (1)Synchronized(共享变量){

    //doSomething

    }

    (2)调用该共享变量的方法,并且该方法使用了synchronized

    Synchronized void add(int a, int b){

    //doSomething

    }

    注意:一个线程可以从挂起状态变为可以运行状态(也就是被唤醒),即使该线程没有被其他线程调用notify()、notifyAll()方法进行通知,或者被中断、或者等待超时,这就是所谓的虚假唤醒。

    虚假唤醒在实际应用中很少发生,但是要防范于未然,做法就是不停地去测试该线程被唤醒的条件是否满足,不满足则继续等待,也就是说在一个循环中调用wait()方法进行防范。退出循环的条件是满足了唤醒该线程的条件。

    Synchronized (obj){

    While(条件不满足){

    Obj.wait();

    }

    }

    代码案例:

    注意:当线程调用共享对象的wait()方法时,当前线程只会释放当前共享对象的锁,当前线程持有的其他共享对象的锁的监视器锁并不会被释放。当一个线程调用对象的wait()方法被阻塞挂起之后,如果其他线程中断了该线程,则改线程会抛出InterruptedException异常并返回。

    2> notify()函数和notifyAll()

    一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。一个共享变量上可能会有个多个线程在等待,具体唤醒那个等待的线程是随机的。

    此外,被唤醒的线程不能马上从wait方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回,也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程与其他线程一起竞争该锁,只有获取该共享变量的监视器锁才可以继续执行。

    只有获取到了共享变量的监视器锁之后,才可以调用共享变量的notify()方法,否则会抛出Illega!MonitorStateE ception异常。

    notifyAll()函数

    该函数会唤醒所有在该共享变量上由于调用了wait()系列方法而挂起的线程。

    代码案例

    需要注意的一个地方是:在共享变量上调用notifyAll()方法只会唤醒调用这个方法前调用了wait系列函数而被放入共享变量等待集合里面的线程。如果调用notifyAll()方法后一个线程调用了该共享变量的wait方法而被放入阻塞集合,则该线程是不会被唤醒的。例如:将上面代码的休眠1s注释掉,结果为:

    3> 等待线程执行终止的join方法

    在很多情况下,主线程生成并起动了子线程,如果子线程里面要进行大量的耗时运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用join()方法了。另外,一个线程需要等待另一个线程也需要用join().

    除了join()方法外,还有join(long millis)、join(long millis,int nanos),

    如果thread在指定的超时时间没有终止,主线程将会继续往下执行。

    Sleep和 join的区别: Thread.sleep(2000)不会释放锁thread.join(2000)会释放锁。

    4> Sleep方法

    线程调用sleep方法后,该线程会暂时让出指定时间的执行权,也就是在这个期间不参与CPU的调度,但是该线程所持有的锁是不让出的。在指定的睡眠时间到了后该函数会正常返回,线程处于就绪状态,然后参与CPU的调度,获取到CPU资源后就可以继续运行了。如果在睡眠期间其他线程调用了该线程的interrupt()方法中断了该线程,则该线程会调用sleep方法的地方抛出IntermptedException异常而返回。

    5> 让出CPU执行权的yield方法

    正常情况下当一个线程把分配给自己的时间片使用完后,线程调度器才会进行下一轮的线程调度,而当一个线程调用了yield时,告诉线程调度器自己占有的时间片中还没使用完的部分自己不想使用了,这暗示线程调度器现在就可以进行下一轮的线程调度。

    一个线程调用yield方法时,当前线程会让出CPU使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU的那个线程来获取CPU执行权。

    总结:sleep与yield方法的区别在于,当前线程调用sleep方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会调度改线程。而调用yield方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是出就绪状态,线程调度器下一次调度是就有可能调度到当前线程执行。

    6> 线程中断

    Java中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。

    Void interrupt()方法:中断线程,例如,当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志位true并立即返回。设置标志仅仅是设置标志,线程A实际并没被中断,它会继续往下执行。

    Boolean isInterrupted()方法:检测当前线程是否被中断,如果是返回true,否则返回false。

    Boolean interrupted()方法:检测当前线程是否被中断,如果是返回true,否则返回false。该方法时获取当前运行线程的中断标志而不是调用interrupted()方法的实例对象的中断标志。

    下面是一个线程使用Interrupted优雅退出的例子:

    Public void run(){

    try{

    //线程退出条件

    While(!Thread.currentThread().isInterrupted()&& moer work to do ){

    //do more work;

    }

    }catch(){

    }finally{

    }

    }

    7> 守护线程与用户线程

    Java中的线程分为两类,分别为daemon线程(守护线程)和user线程(用户线程)。在JVM启动时会调用main函数,main函数所在的线程就是一个用户线程,其实在JVM内部同时还启动了好多守护线程,比如垃圾回收线程。

    守护线程和用户线程的区别:只要一个用户线程没有结束,正常情况下JVM就不会退出。守护线程没退出,JVM会退出。

    相关文章

      网友评论

          本文标题:java并发编程基础

          本文链接:https://www.haomeiwen.com/subject/filnkctx.html