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

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

 1.线程中断  

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

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

 

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

    由于这wait/notify这种不怎么好理解,我们就详细说说,其他的随便看看就好。。。

    大家不知道有木有发现,java中几乎所有的数据类型都重写了Object的wait()、notify()、notifyAll()这三个方法,那这三个方法到底是干什么的呢?

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

  ·   举例下面两种用法:

 

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

    

  当线程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。。。。

    

posted @ 2019-04-27 17:34  java小新人  阅读(627)  评论(0编辑  收藏  举报