第五天:java多线程(线程)

进程与线程

进程

        程序执行过程中,进程是动态的,放在磁盘中的文件并不是进程,只有在运行状态的才可以称之为进程;持有资源和线程。进程是资源和线程的载体。

线程

        线程是系统中的最小执行单元;同一进程中可以有多个线程;线程共享进程的资源。

        线程的交互:多个线程需要正确的通信才能进行工作。交互方式有互斥和同步两种。

区别

        1、操作系统引入线程机制后,进程是资源分配和调度的单位,线程是处理机调度和分配的单位,资源分配给进程,线程只拥有很少资源,线程切换代价比进程低。

        2、不同进程地址空间相互独立,同一进程内的线程共享同一地址空间。一个进程的线程在另一个进程内是不可见的。

        3、创建进程或撤销进程,系统都要为之分配或回收资源,操作系统开销远大于创建或撤销线程时的开销。

创建方式

继承Thread类

步骤        1、创建线程类,继承Thread类;

                2、重写run()方法;

                3、创建线程类对象,对象调用start()方法开启线程。

start() 和main()方法多线性同时执行两个线程

注意:线程开启不一定立即执行,有CPU调度执行

实现Runable接口

步骤:1、创建线程类,实现Runable接口;

           2、实现run方法;

           3、创建线程类对象,new Thread(对象).start()来启动线程(代理)。

推荐使用:避免单继承的局限性,方便灵活,方便同一个对象被多个线程所使用。

实现callable接口

步骤:1、继承callable接口

           2、重写call()方法,含返回值和抛出异常

           3、创建类对象

           4、创建执行任务 ExecutorService service = Executors.newFixedThreadPool(3);

           5.提交执行 Future<Boolean> f1 = service.submit(t1);

           6.获取结果 boolean rs1 = f1.get();

           7.关闭服务 service.shutdownNow();

控制方法

sleep(long millis):

        必须带有一个时间参数,使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会;不会释放锁标志。

join():

        调用该方法的线程在此之前执行完毕,也就是等待调用该方法的线程执行完毕后再往下继续执行。该方法也要捕获异常。

yieId():

        sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出CPU占有权,但让出的时间是不可设定的。

        yield()也不会释放锁标志。

        实际上,yield()方法对应了如下操作: 先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程。

        sleep方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,所以不可能让出较低优先级的线程些时获得CPU占有权。 在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

        yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。所以yield()只能使同优先级的线程有执行的机会。

run()和start():

        把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由Java的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void.

wait()、notify()、notifyAll():

        这三个方法用于协调多个线程对共享数据的存取,所以必须在Synchronized语句块内使用这三个方法。wait()方法使当前线程暂停执行并释放对象锁标志,让其他线程可以进入Synchronized数据块,当前线程被放入对象等待池中。当调用 notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中的线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。

        notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。

synchronized:

        这个关键字用于保护共享数据,当然前提是要分清哪些数据是共享数据。每个对象都有一个锁标志,当一个线程访问该对象时,被Synchronized修饰的数据将被“上锁”,阻止其他线程访问。当前线程访问完这部分数据后释放锁标志,其他线程就可以访问了。

生命周期

新建

        当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值;此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。

就绪

        当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度

运行

        在运行状态的线程执行自己的run方法中代码,直到等待某资源而阻塞或完成任何而死亡。如果在给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态。

阻塞

        当处于运行状态的线程失去所占用资源之后,便进入阻塞状态;

进入阻塞:

        ① 线程调用sleep()方法主动放弃所占用的处理器资源

        ② 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞

        ③ 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。关于同步监视器的知识、后面将存更深入的介绍

        ④ 线程在等待某个通知(notify)

        ⑤ 程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法

解除阻塞:

        ① 调用sleep()方法的线程经过了指定时间。

        ② 线程调用的阻塞式IO方法已经返回。

        ③ 线程成功地获得了试图取得的同步监视器。

        ④ 线程正在等待某个通知时,其他线程发出了个通知。

        ⑤ 处于挂起状态的线程被调甩了resdme()恢复方法。

死亡

进入死亡状态:

        1.run()或call()方法执行完成,线程正常结束

        2.线程抛出一个未捕获的异常

        3.直接调用stop()方法来结束该线程(容易造成死锁)

同步的实现方式

并发

并发概念:

        1.原子性:操作要么执行,要么不执行,中途不被打乱;

        2.可见性:一个线程对某变量的修改对其他线程来说是可见的,即能知道值进行过修改

        3.有序性:程序的执行按照代码的顺序执行

        多线程共享数据时,会发生线程不安全的情况,多线程共享数据必须同步。

实现方式

使用同步代码块

使用同步方法

使用互斥锁ReetrantLock(更灵活的代码控制)

死锁

死锁一定发生在并发中。

产生原因:

        1、系统资源不足

        2、资源分配不当

        3、进程运行推进的顺序不合适

产生条件:

        1、互斥条件:所谓互斥就是进程在某一时间内独占资源

        2、请求与保持条件:一个进程因为请求资源而堵塞时,对已获得的资源保持不变

        3、不剥夺条件:进程已获得资源,在未使用完之前,不能强行剥夺

        4、循环等待条件:若干进程之间形成一种首尾相接的循环等待资源关系

        例如下图,线程A持有锁1 ,想去拿锁2, 线程B持有锁2, 想去拿锁1, 但是此时释放都不释放自己的锁, 那么就会进入无尽的阻塞。锁的特性是只能被一个线程所拥有;

 如果多个线程之间的依赖关系是环形, 存在环路的锁的依赖关系, 那么也可能会发生死锁.

如下图所示, 三个线程死锁的环路. 除非拿到了想要的锁, 才会释放资源.

预防死锁:

        1、破坏请求和保持条件:1.一次性的申请所有资源。之后不在申请资源,如果不满足资源条件则得不到资源分配。2.只获得初期资源运行,之后将运行完的资源释放,请求新的资源。

        2、破坏不可抢占条件:当一个进程获得某种不可抢占资源,提出新的资源申请,若不能满足,则释放所有资源,以后需要,再次重新申请。

        3、破坏循环等待条件:对资源进行排号,按照序号递增的顺序请求资源。若进程获得序号高的资源想要获取序号低的资源,就需要先释放序号高的资源。

解除死锁:

        1、抢占资源。从一个或多个进程中抢占足够数量的资源,分配给死锁进程,以解除死锁状态。

        2、终止(撤销)进程:将一个或多个思索进程终止(撤销),直至打破循环环路,使系统从死锁状态解脱。

线程通信

        通信的目的是为了更好的协作,线程无论是交替式执行,还是接力式执行,都需要进行通信告知。

通信方式:

1.volatile

        volatile有两大特性,一是可见性,二是有序性,禁止指令重排序,其中可见性就是可以让线程之间进行通信。

原则保证:

        1.所有volatile修饰的变量一旦被某个线程更改,必须立即刷新到主内存

        2.所有volatile修饰的变量在使用之前必须重新读取主内存的值

2.等待/通知机制

3.join方式

4.threadLocal

猜你喜欢

转载自blog.csdn.net/qq_35056891/article/details/126538728