带着新人看java虚拟机07(多线程篇)

  这一篇说一下比较枯燥的东西,为何说枯燥呢,由于我写这都感受很无聊,无非就是几个阻塞线程的方法和唤醒线程的方法。。。 java

 1.线程中断  安全

  首先咱们说一说怎么使得一个正在运行中的线程进入阻塞状态,这也叫作线程中断,最多见的就是Thread.sleep(1000)这种方式的,咱们直接看一个简单粗暴的图:多线程

  此图应该列举了全部中断,咱们选择几个比较重要的说说,其中有几个方法已经被废弃了,缘由要么就是不安全,要么就是容易产生死锁等等,图中已经划去了!ide

 

 2.等待通知和收到通知(wait、notify、notifyAll)性能

    因为这wait/notify这种不怎么好理解,咱们就详细说说,其余的随便看看就好。。。测试

    你们不知道有木有发现,java中几乎全部的数据类型都重写了Object的wait()、notify()、notifyAll()这三个方法,那这三个方法究竟是干什么的呢?spa

    前提:要用这三个方法必需要放在synchronized里面,这是规则!!!至于调用这三个方法必须是锁定(也就是我前面说的锁芯)才能调用,还有,锁定几乎能够是任何类型的!线程

  ·   举例下面两种用法:code

 

    介绍一个东西,名字叫作“wait set”,这是个什么东西呢?你就把这个看做是一个存阻塞线程的集合(大白话来讲就是线程的休息室,把线程用wait阻塞了就至关于让线程去休息室休息,而且线程颇有自觉,去休息了以后就会释放锁),并且每一个锁定(也就是我前面说的锁芯)都有一个。好比一个线程调用obj.wait()以后,那么这个线程就被阻塞了,这个阻塞的线程就被放在了obj的“wait set”中了,咱们能够用一个图来表示:
blog

    

  当线程A阻塞以后就被丢进了obj的wait set中以后,线程A就会释放当前的锁,此时线程B就能够访问这个方法或相同锁定的方法;可是假如在B中调用了notify()方法,那么就是从obje的wait set中唤醒A线程,而后直到B线程结束后释放锁,A线程才变成准备就绪状态,能够随时被CPU调度再次得到这个锁;

  注意:必须等B线程执行完以后释放锁,线程A才能变成准备就绪状态(代码是从wait方法后面的代码开始执行,不是从新开始)  

 

  根据上面两个图随意举个小例子: 

package com.wyq.thread;

public class Bank {
    Object obj = new Object();//咱们随便建立一个锁定 public void tomoney(Integer money){
//在转帐方法的锁中调用wait方法,此时执行这个方法的线程会中断,保存在obj的wait set中,而且该线程会释放锁其余线程能够访问相同锁定的锁
synchronized(obj){ try { obj.wait(); System.out.println("转帐:"+money+"元"); } catch (InterruptedException e) { e.printStackTrace(); } } } public void save(Integer money){
//在存钱方法的锁中咱们调用notify从obj的wait set中唤醒存在其中的某一个线程,那个被唤醒的线程不会立刻变成准备就绪状态,
  //必需要等本存钱方法的线程执行完毕释放锁,才会进入准备就绪状态
synchronized(obj){ obj.notify(); System.out.println("存钱:"+money+"元"); } } public static void main(String[] args) { Bank bank = new Bank(); //咱们能够屡次运行这两个线程,老是先执行存钱方法,而后才是转帐方法(其实转帐线程能够利用for循环建立几十个,这样效果更明显) new Thread(new Runnable() { @Override public void run() { bank.tomoney(100); } }).start(); new Thread(new Runnable() { @Override public void run() { bank.save(100); } }).start(); } }

     运行结果以下:

     

  notify()是唤醒wait set集合中随意一个线程;而那个notifyAll()方法能够唤醒wait set集合中全部的线程,用法和notify同样,就不举例子了;那么咱们日常应该用哪个呢?用notify的刷唤醒的线程比较少效率高一点,可是缺点就是到底唤醒哪个线程的实现可能有点难度,一个处理很差程序就可能挂掉了;可是用notifyAll的话效率比较低一点,可是却比较可靠稳定;

  因此啊,若是咱们对于程序代码都理解得十分透彻,那就用notify比较好,不然仍是用稳妥一点的notifyAll吧!

  顺便说一点,有的时候咱们把一个线程阻塞以后放进wait set中以后,却忘记调用notify/notifyAll了,那么这些阻塞线程就会一直留在了wait set中,咱们能够在wait()方法指定一个时间,在规定时间内若是没有被notify唤醒,那么放在wait set中的该线程就会自动唤醒!还有obj.wait()方法其实本质是调用obj.wait(0),wait(long timeout)是一个Native方法!好比obj.wait(3000)表示三秒以后会自动唤醒!这里就是随意提一下,通常不多去指定这个超时时间的

  补充wait set的定义:wait set是一个虚拟的概念,它既不是实例的字段,也不是能够获取在实例上wait中线程的列表的方法。

 

3.sleep和interrupt方法

  有没有以为上面的这种用法比较麻烦,虽然在某些状况下比较适用,可是咱们日常测试用的话这也太麻烦了,还有个什么锁定这种鬼东西,有没有比较简单的用法啊!

  因而咱们有了sleep方法对应于wait方法,interrupt方法对应于notify方法;(注意,这里只是功能上面的对应,可是其中的原理是不相同的!)

  首先说说sleep方法,这个应该比较熟悉,直接就是Thread.sleep(xxx)这种方式来使得当前线程阻塞必定时间(注意sleep方法和wait方法最大的区别就是sleep方法不会释放锁),若是没有到达相应时间咱们非要让阻塞状态的线程又从新变成准备就绪状态,就使用a.interrupt()方法,其中a指的是当前线程的实例;

  咱们看一个最简单的例子:

package com.wyq.thread;

public class Bank {
    
    public void tomoney(Integer money){
        try {
            //将运行这个方法的线程中止一个星期
            Thread.sleep(604800000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("转帐:"+money+"元");
        
    }
    
    public static void main(String[] args) {
        Bank bank = new Bank();
        
        Thread thread = new Thread(new Runnable() {
            
            @Override
            public void run() {
            bank.tomoney(100);
            }
        });
        thread.start();
    //若是没有调用interrupt方法,那么thread这个线程就会暂停一个星期 thread.interrupt(); } }

 

  会抛出一个异常这也是用interrupt方法的特点;

  随意一提:这个interrupt方法比较厉害,即便线程中断是因为wait(),join(),sleep()形成的,可是均可以用interrupt方法进行唤醒,比较厉害!

 

4.join()方法

  因为线程的执行是随机的,那么咱们有没有设什么方法可让线程以必定的顺序执行呢?虽然可能会有点影响性能,但这不是咱们暂时关心的。

  join()方法可让一个线程B在线程A以后才执行,咱们继续看一个简单的例子;

package com.wyq.thread;

public class Bank {
    
    public void tomoney(String name,Integer money){
        
        System.out.println(name+"转帐:"+money+"元");
        
    }
    
    public static void main(String[] args) {
        Bank bank = new Bank();
        
        Thread A = new Thread(new Runnable() {
            
            @Override
            public void run() {
            bank.tomoney("A",100);
            }
        });
        
        Thread B = new Thread(new Runnable() {
            
            @Override
            public void run() {
            bank.tomoney("B",200);
            }
        });
        
        A.start();
        try {
            //必要要等到A线程执行完毕才会执行其余的线程
            A.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //这个时候查看一下B的线程是NEW,说明B线程还没调用start()方法,
        //调用start方法以后就成Runnable状态了
        System.out.println(B.getState());
        B.start();
        
        
    }
}

 

 

5.线程优先级和yield()方法

  其实线程优先级这这种东西比较坑,这是一个几率问题,优先级分为10级,1级最低,10级最高,咱们通常建立的线程默认5级;可是优先级高的线程不必定先执行,这个优先级的意思就是线程获取CPU调用的几率比较大,因此这是一个几率问题,基本用法是Thread thread = new Thread(xxx);  thread.setPriority(1);   thread.start();

  那么yield方法是干什么的呢?yield的意思是放手,让步,投降,在多线程中是对一个正在运行的A线程用这个方法表示该线程放弃CPU的调度,从新回到准备就绪状态,而后让CPU去执行和A线程相同优先级的线程,并且有可能又会执行A线程;并且还有可能调用yield方法无效,emmmm......日了狗了!

  我表示最这个方法没有什么好感,感受很难控制,这个方法是Thread的静态方法,直接用Thread.yield()直接用便可,我感受我一生都不会用到这个....这个方法就不测试了,有兴趣的小伙伴本身查查别的资料吧!

 

6.总结

  不知道你们有没有以为多线程这个东西不能随便用,用好了虽说能够提升效率,可是用的很差很容易出现不可控制的问题,这让我有一种错觉就是引入了多线程以后,又要解决由多线程引发的更多更麻烦的问题,emmm。。。。

相关文章
相关标签/搜索