Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 276|回复: 0

[默认分类] java 基础之多线程概述

  [复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2018-3-19 09:14:20 | 显示全部楼层 |阅读模式

    1、多线程的简介
    1)在学习线程之前我们先了解一下进程:
    (1)现在很多操作系统都支持多进程,我们可以单纯的理解为一个应用程序就是一个进程。比如:QQ,搜狗等
    (2)实际上的多进程也不是多进程,比如表面上:我们一边听音乐,一边聊天,但实际上这些进程并不是同时进行的。
    (3)在计算机上所有的进程都是由CPU执行的,对于CPU来说,某一个时间点只能运行一个进程。只是CPU在很短的时间内将运行的各个进程进行切换,由于CPU运行的很快,并且时间很短,才给人同时运行很多程序的感觉。
    2)线程的了解
    (1)在每一个运行的进程之中,都可以包含多个执行单元同时运行,这些执行单元可以看做是程序执行的一条条分流,被称为线程。
    (2)每一个进程都至少含有一个线程,创建进程,会默认进入一个线程main()方法里面的代码。
    (3)线程其实也是轮换执行的,这是CPU的执行机制决定的。
    2、线程的创建
    线程的创建主要由两种方法:
    1)继承java.lang包下的Thread类。
    2)实现java.lang.Runnable接口。
    他们都是复写run()方法来实现功能的实现。
    1)Thread创建多线程的方法
    大体思路:
    (1)首先创建一个类MyThread继承Thread类:class MyThread extends Thread { }
    (2)通过创建的MyThread类创建一个Thread对象 : Thread myThread = newThread();
    (3)在MyTread里复写Thread()类里面的run方法,新线程实现的功能。
    (4)开启线程:myThread.start()
    2)实现Runable接口实现多线程
    通过创建类来继承Threa具有局限性,因为类只能继承一个类,而接口却可以实现很多个。为了解决这种弊端,我们还有一种方法来创建一个新的线程,就是通过Thread(Rundble target)接口。
    Thread(Runable target)接口只有一个run()方法,通过Tread(Runable target)构造方法创建线程的时候,只需要为它传递一个实现了Runable接口的实例化对象,就可以创建一个新的线程,并且运行新的run()方法,来实现新的功能。
    大体实现思路:
    (1)创建一个新的类MyThreadRunable接口Runable类 class MyThreadRunable implement Runable{}
    (2)实例化继承类Runable对象:MyThreadRunable myRunable = new MyThreadRunable();
    (3) 将实例化的Runable对象带入Thread(Runable target) 参数,创建新的线程:Thread myRunableThread = new Thread(myRunable);
    (4)开启线程,myRunableThread.start();
    主要就是Thread通过带入实例化对象myRnable 将线程开启的run方法,复写到Runable()的run方法里面。
    3、Thread和Runable的区别
    他们主要的区别是对共有资源的调度上:
    举个例子:卖车票
    1)首先创建一个private int ticket =100;表示由100张票
    2)用Thread创建四个线程表示四个窗口,然后获取ticket,对其做减法运算,类似卖出
    3)打印log可以看到,实际上是四个线程都获取了一个ticket = 100,各自进行处理减法操作。
    这在事实上是不合理的,因为一张票不可能同时卖出四个人。
    4)用Thread(Runable target) 接口实现的四个线程操作,可以观察到
    这四个线程是共同操作一个ticket = 100; 当线程1把ticket减去1 = 99之后,别的线程就会从99开始去减,而不是重新获取到一个ticket =100
    关于Thread 、Thread(Runable target) 的实例请参阅:
    java多线程---Thread和Runnable简单实例
    4、后台线程的概念
    当一个线程添加setDaemon(true)语句之后就变成了后台线程:
    1. [code]········
    2. myRunableThred.setDaemon(true);
    3. myRunableThread.start();
    4. ··········
    复制代码
    [/code]
    1)进程中只要还有前台进程,进程就不会结束。(新建的线程默认都是前台进程)
    2)但是当进程中的线程都是后台进程的时候,进程就会结束。
    5、线程的运行周期
    (1)线程的新建
    (2)线程的就绪
    (3)线程的运行
    (4)线程的阻塞
    (5)线程的结束
    1)线程的新建
    可以理解myThread.start();代码
    2)线程的就绪
    可以理解从可以理解myThread.start();代码到run(){}代码之间时间,等待CPU分配运行资源。
    3)线程的运行
    可以理解是run(){}方法运行之后
    4)线程的阻塞
    可以理解是线程运行的时候,停止了,导致阻塞的原因:
    (1)当线程获取某个对象的同步锁的时候,锁被其他对象调用,还没有释放。
    (2)当线程调用了阻塞的 IO方法时,比如管道,socket等
    (3)当线程调用某个对象的wait()方法,就必须等到调用notify()才能唤醒。
    (4)当线程调用Thread.sleep(long mills)方法,只有等到时间结束才会进入就绪状态。
    (5)当线程通过join()方法调用其他的线程,需要等新加入的线程运行结束,才会进入就绪状态。
    线程阻塞的例子:待续·····
    5)线程的死亡
    线程一般由三个方式死去:
    1)运行完run()方法。
    2)线程捕获到了异常(Exception)、错误(Error)。
    3)使用thread.stop()来强行终止线程方法终止线程。
    6、线程的调度
    1)线程的优先级
    设置线程的优先级,优先级越高获得CPU执行的机会越大。
    线程的优先级用1~10之间的整数表示,数字越大表示优先级越高。
    除了直接使用数字表示优先级外,Thread还提供了三个静态常量来表示优先级:
    Static int MAX_PRIORITY 表示线程优先级最高,相当于10
    Static int NORM_PRIORITY 表示线程优先级普通,相当于5
    Static int MIN_PRIORITY 表示线程优先级最低,相当于1
    2)使用方法:
    最高优先级
    1. [code]········
    2. myRunableThred.setPriority(10);
    3. myRunableThread.start();
    4. ··········
    复制代码
    [/code]
    最低优先级
    1. [code]
    2. ········
    3. myRunableThred.setPriority(1);
    4. myRunableThread.start();
    5. ··········
    复制代码
    [/code]
    一般的操作系统,都会先去执行优先级较高的线程,然后再去执行优先级较低的线程。
    7、线程的休眠
    当我们想让自己的线程暂停一段时间,等待一段时间,把CPU让给别的线程使用。我们可以调用sleep(long millis)方法。
    该方法会在声明的时候抛出一个InterruptException异常,我们应该给以捕获,或者声明这个异常。
    1)用法
    1. [code]········
    2. try{
    3. Thread.sleep(2000);//当前线程休眠2S
    4. } catch  (interruptedException e){
    5. e.printStackTrace();
    6. }
    7. ··········
    复制代码
    [/code]
    请看清是当前线程,放在你要暂停的线程执行程序之中。
    8、线程使用yield()方法让步
    当线程使用yield()时,线程就进入了就绪状态,等待系统调度器重新调度。
    这个时候只有同级别的线程或者更高级别的线程才能够得到CPU 的执行。
    1)用法
    假设由Thread_one / Thread_two两个同级别线程
    1. [code]········
    2. Thread_one.yield();//当前线程做出让步,同级别或者更高级别的线程去执行(Thread_two 去执行)
    3. ··········
    4. ········
    5. Thread_two.yield();//当前线程做出让步,同级别或者更高级别的线程去执行(Thread_one去执行)
    6. ··········
    复制代码
    [/code]
    9、线程插队
    类似与现实中插队一样,线程也会通过调用join()方法。插队遭到阻塞,只有当插队的线程运行结束之后,才能重新运行。
    1)用法:
    假设由Thread_one / Thread_two两个线程,
    在线程一执行的代码中添加Thread_two.join();
    就可以使得线程一得到阻塞,当线程2,运行完时,线程一才会继续运行。
    10、多线程同步
    1)同步代码块
    当多个线程操作统一资源的时候,往往会造成一些意外情况:
    比如
    1)四个Thread(Runable target)线程模拟售票,共ticket = 10张票。
    2)线程执行run()函数中延时1S,模拟现实中的延时操作。
    3)最后打印出来票数有可能ticket = 0;ticket = -1; ticket = -2;
    为什么会导致这样的情况呢:
    是因为当ticket = 1;的时候,比如线程1获取到了数据,但是此时线程是有一个延时的,线程一并没有把-1的操作及时返回给ticket.
    这个时候线程2/3/4也是正在执行的,它们此时读到的ticket 的值也是ticket = 1; 也参与了 -1 的操作。所以最后ticket 会被多减好多次。
    4)解决办法
    (1) 就是让多个线程在访问共有资源的时候,一次对共有资源操作的那一块代码。每次只能有一个线程去操作。
    这样也就类似于排队去执行共有资源,当一个线程操作完成之后另一个才能去操作。
    (2) 我们采用同步机制,意思是共有资源对每个线程同时更新同步,我们使用synchronized(lock){
    操作共享资源的代码快;
    }
    5)上面的lock是一个锁对象,当一个线程操作共享代码是被置0,此时别的线程无法进入操作共享资源的代码。当操作完成时,锁被置1,别的线程可以进行操作。
    1. [code]········
    2. TicketCheck ticket = new TicketCheck();//创建TicketCheck 对象
    3. new Thread(ticket,"线程1").start();
    4. new Thread(ticket,"线程2").start();
    5. new Thread(ticket,"线程3").start();
    6. new Thread(ticket,"线程4").start();
    7. ·········
    8. ···········
    9. class TicketCheck implements Runnable {
    10. private int ticket = 10; //十张票
    11. Object lock = new Object();//定义任意一个对象,作为同步代码的锁
    12. public void run() {
    13. while (true) {
    14. synchronized (lock) {
    15.                 try{
    16.                 Thread.sleep(2000);//当前线程休眠2S
    17.                 } catch  (interruptedException e){
    18.                     e.printStackTrace();
    19.                 }
    20.                 if (ticket > 0) {
    21.                 Log.v(TAG,"ticket:" + ticket--);
    22.                 } else {
    23.                 break;//跳出while循环,结束线程
    24.                 }
    25.             }
    26.         }//对应的synchronized代码块,要包含所有对共有资源的操作
    27.     }
    28. }
    29. ···········
    复制代码
    [/code]
    2)同步方法
    和同步代码块类似,同步方法可以理解为,把执行共有资源的操作的代码块放到了一个方法里面,然后用synchronized 来修饰这个方法。来达到每次只有一个线程访问执行共有资源的方法。
    思路:
    1)把每个线程操作共有资源的代码块放到一个函数里面。
    2)用synchronized 在前面修饰这个函数。
    1. [code]········
    2. TicketCheck ticket = new TicketCheck();//创建TicketCheck 对象
    3. new Thread(ticket,"线程1").start();
    4. new Thread(ticket,"线程2").start();
    5. new Thread(ticket,"线程3").start();
    6. new Thread(ticket,"线程4").start();
    7. ·········
    8. ···········
    9. class TicketCheck implements Runnable {
    10. private int ticket = 10; //十张票
    11. Object lock = new Object();//定义任意一个对象,作为同步代码的锁
    12. public void run() {
    13. while (true) {
    14.                 saleTicket();//在run()方法里面调用saleTicket 方法,来对共有资源进行操作
    15.                 if (ticket < 0) {
    16.                 break;//跳出while循环,结束线程
    17.                 }
    18.             }
    19.     }
    20. //用synchronized 修饰调用的方法表示每次只能一个线程调用
    21. private synchronized void saleTicket() {
    22. if (ticket > 0) {
    23.                 try{
    24.                 Thread.sleep(2000);//当前线程休眠2S
    25.                 } catch  (interruptedException e){
    26.                     e.printStackTrace();
    27.                 }
    28.             Log.v(TAG,"ticket:" + ticket--);
    29.             }
    30.     }
    31. }
    32. ···········
    复制代码
    [/code]
    同步方法其实也是有锁的,就是调用同步方法的那个类,实例出来的对象。大多是this指向的对象,但是在静态方法中该对象是该方法所在的类的class对象,该对象可以直接用类名.class来获取。
    11、了解死锁现象
    死锁现象,就类似于,A、B两个人手里拿着对方家门的钥匙,但是此时都是在自己家里面。所以自己出不去,对方也进不来。
    代码实现:
    1)建立两个锁
    2)在run()方法里面,互相嵌套锁对象同步的代码;
    1. [code]········
    2. TicketCheck ticket = new TicketCheck();//创建TicketCheck 对象
    3. new Thread(ticket,"线程1").start();
    4. new Thread(ticket,"线程2").start();
    5. ·········
    6. ···········
    7. class TicketCheck implements Runnable {
    8. static Object lock_A = new Object();//定义一个锁对象
    9. static Object lock_B = new Object();//再定义一个锁对象
    10. private boolean flag = true;
    11. public void run() {
    12. if (flag) {
    13. flag = false;
    14. while (true) {
    15. synchronized (lock_A) {
    16.         Log.v(TAG," A 锁"); //这个线程只能运行到这里
    17.         synchronized (lock_B) {
    18.         Log.v(TAG," B 锁");//打印不出来这一句,因为lock_B 已经被另一个线程占据
    19.                 }      
    20.             }
    21.         }//对应的synchronized代码块,要包含所有对共有资源的操作
    22.         } else {
    23.         flag = true;
    24.         while (true) {
    25.         synchronized (lock_B) {
    26.         Log.v(TAG," B 锁");//这个线程只能运行到这里
    27.         synchronized (lock_A) {
    28.         Log.v(TAG," A 锁");//打印不出来这一句,因为lock_A 已经被另一个线程占据
    29.                 }      
    30.             }
    31.             }
    32.         }
    33.     }
    34. }
    35. ···········
    复制代码
    [/code]
    12、线程之间的通信
    当我们建立的线程对共有的一个事情分阶段操作的时候。
    比如:A 线程存数据,B线程读数据。
    我们期待A线程存好数据之后,B线程再度数据。
    不然可能出现混乱的情况。
    在此我们就可以使用wait()方法,和notify()、notifyAll()方法
    1)void wait() 使当前线程放弃同步锁并进入等待,直到其它线程进入此同步锁,并且调用notify()方法,或者notifyAll()方法唤醒该线程为止。
    2)void notify() 唤醒此同步锁等待的第一个调用wait()方法的线程。
    3)void notifyAll()唤醒唤醒此同步锁上调用的wait()方法的所有线程。
    需要注意的是这三个方法使用的必须是同一个同步锁对象,如果这三个不是同一个同步锁对象,会抛出IllegaMonitorStateException异常。
    具体实例请参考:
    线程--简单多线程通信实例
    参考文档:
    Java基础入门 传智博客高教产品研发部 
    本人郑重声明,本博客所著文章、图片版权归权利人持有,本博只做学习交流分享所用,不做任何商业用途。访问者可將本博提供的內容或服务用于个人学习、研究或欣赏,不得用于商业使用。同時,访问者应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人的合法权利;如果用于商业用途,须征得相关权利人的书面授权。若以上文章、图片的原作者不愿意在此展示內容,请及时通知在下,將及时予以刪除。

    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-16 13:22 , Processed in 0.383053 second(s), 47 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表