Java多线程那点事(一)

一:相关概念:

1.并行与并发:

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

       2.进程与线程

进程就是程序的一次执行过程(进程是一个动态过程 包括:为变量、方法执行开辟空间,程序执行计数器.......)

线程就是程序的一次执行路径 即可以认为进程包含线程

 

 

3.线程的特点?

并发性

随机性

 

4.使用多线程一定会提升程序执行效率吗?

    不一定

  •  多线程会出现安全性问题,只要处理多线程安全性问题 ,则程序执行效率肯定下降.
  •  每个线程按照基本理论就是一个线程对象  就会占用机器内存  所以需要在线程数量和执行效率间取平衡

     

 

二:线程的生命周期:

                                

线程5大状态:

  •     新建(new):创建Thread对象
  •     就绪(Runnable):调用了start方法后 线程进入就绪态
  •     运行(Running):在就绪态的线程对象获取到cpu可执行时间片 就会调用run方法进入运行状态
  •     阻塞(Blocked):当调用sleep(),wait()等方法的时候.
  •     消亡(Dead):run()结束或抛出异常

各种状态一目了然,值得一提的是"blocked"这个状态:

    线程在Running的过程中可能会遇到阻塞(Blocked)情况

  • 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
  • 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
  • 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。

 

    此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。该过程实际上是让running状态的线程放弃获取的cpu时间片.重新转入到runnable状态.但是我们并不能指望该方法能够削弱该线程的执行优先级(可以通过设置线程的优先级来增加线程获取cpu时间片的权重).仅仅是针对该次cpu时间片分配.进入runnable状态后很有可能下一个cpu时间片又被该线程获得.


 

三:线程中常用的方法(sleep()、Join()、yeild()、wait()、notify()、notifyAll())

     1.sleep() yeild() 方法都是静态方法,在同步语句块中该线程不会交出监视器(monitor)所有权(我们常说的锁就是monitor实现的,以下统称为监视器所有权)

     2.yeild()方法只是使得当前线程放弃本次CPU分配的时间片(执行权),回到等待状态.至于下次该线程是否执行仅取决于该线程是否能够再次获取执行权.

 

 3.wait()是从Object中继承过来的实例方法,调用该方法的线程必须拥有该对象监视器所有权,调用成功则进入阻塞状态, 此句后面的代码将不会执行直到该线程别唤醒。如果该线程没有对象监视器的所有权则会抛出InterruptedException。

 

    4.join()是Thread类的实例方法,当join被调用时,存在两个对象,

  • join方法所在线程
  • 调用join方法的线程

注意,这二者是有区别的。比如main方法中new了一个thread1,thread1调用了join方法,这里的join方法所在线程即main方法,调用join方法的线程为thread1。

那么该调用方式的作用:

    阻塞该方法所在线程main方法(实际上是循环调用(循环条件是调用该方法的线程isAlive,如果调用线程isAlive则一直阻塞

,直到线程执行结束,isAlive方法返回false,main阻塞结束,继续执行后面的代码

 

 

    5.Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,使处于阻塞的线程抛出一个中断信号, 这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞, 那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态,事实上就是通过异常打断阻塞。想深度了解下可以转http://blog.csdn.net/wxwzy738/article/details/8516253;

    总结一下:大概有以下几点:

  • wait(),notify(),notifyAll()都需要在拥有对象监视器的前提下执行,否则会出现异常IllegalMonitorStateException。

  • 多个线程可以同时在一个对象上等待

  • notify()将随机唤醒一个在对象上等待的线程,没有一个都没有,则什么都不做。

  • notify()唤醒的线程,将在notify()线程释放了对象监视器以后才执行,并不是notify了以后马上执行。

 

四.关于对象的锁

  •   锁有两种,一种是类锁(以下统称类锁),其实是类的Class对象的实例锁(所有该类的实例共用同一个Class对象),另一种是类的实例的锁.
  •  锁是通过对象监视器实现的,而对象监视器由JVM负责
  •  同一个线程可以对一个对象多次加锁
  • 线程同步中,会有等待区、入口区和监视器区域三部分,只有监视器区域的线程获取监视器,也就是对象锁

 

五:Demo分析:

package Thread;

public class Num {
    static int count;
    static boolean flag;

    public Num(int count, boolean flag) {
        this.count = count;
        this.flag = true;
    }

    /**
     * 该demo完成
     * ①两个线程一个输出奇数,一个输出偶数
     * ②必须交替进行,完成完整的自然数打印.
     * @author syl
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        Num num = new Num(0, true);
        //0.由于flag的初始值为true,该线程必须先启动.
        //因为逻辑上必须满足,某个线程的notify方法执行时,另一个线程必须是wait状态.
        // 否则一旦A线程notify先于B线程的wait执行,那么A,B线程将陷入死等待.
        new Thread(() -> {//t1线程
            while (flag) num.printOdd();
        }).start();
        new Thread(() -> {//t2线程
            while (!flag) num.printEven();
        }).start();
    }
    public synchronized void printOdd() {//1.执行到该出就会获num的对象锁.当然方法如果是静态的,获取的就是Num类的类锁
            System.out.println(Thread.currentThread().getName() + "-->" + (count++));
            flag = false;//2.该句执行完成后,t2开始执行printEven(),但是由于对象锁在本方法中,所以t2线程开始等待.
            try {
                this.notify();
                Thread.sleep(1000);//3.sleep不会释放锁,所以该方法沉睡时,t2线程还在等着
                this.wait();//4.此方法一旦执行,交出对象锁,t2线程开始执行
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
    }

    public synchronized void printEven() {
            System.out.println(Thread.currentThread().getName() + "-->" + (count++));
            try {
                flag = true;
                this.notify();//5.唤醒t1线程
                Thread.sleep(1000);
                this.wait();//6. 交出对象锁,t1开始执行,回到1
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
    }
}

 

这个flag有人说没有一样行,为什么多此一举?

          这个论断的前提条件是:wait方法不会在外界未干预的条件下自己醒来.

          当然这个条件确实不是绝对成立的.以下引用JDK1.8中的api.

       

     意思大概是:wait()执行时,中断和虚假唤醒是可能的,该方法应该在循环中使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值