zoukankan      html  css  js  c++  java
  • Java 多线程详解

    一.重点

    1.创建和启动线程
    2.实现线程调度
    3.实现线程同步
    4.实现线程通信

    1.为什么要学习多线程?
    当多个人访问电脑上同一资源的时候,要用到多线程,让每个人感觉很多电脑同时为多个人服务。
    比如:
    1.1.排队叫号系统,多个人同一时间请电脑生成一张等待票据时,
    如果没有多线程的话,有可能会生成同一等待号的票据。
    1.2.两个乘客拿到同一张火车票或飞机票。

    2.什么是线程?


    先了解线程与进程的区别。


    1.一个进程包含多个线程
    2.cup执行的是线程,而不是进程
    3.线程是共享资源的,进程是独立占用资源的。
    4.线程虚拟瓜分计算机资源,而进程是真实的瓜分计算机资源。
    线程的定义:线程被认为带有自己的程序代码和数据的虚拟处理机的封装。


    结论:线程是强占式操作的。

    3.线程的创建与启动。


    定义线程有两种方式:
    3.1 继承java.lang.Thread类
    3.2 实现java.lang.Runnable接口 节约一个宝贵的继承机会,因为Java是单继承。
    使用线程的步骤:
    3.1 定义一个线程,同时指明这个线程所要执行的代码。
    3.2 创建线程对象
    3.3 启动线程
    线程的生命周期:有四个状态:
    3.1 新生状态、
    3.2 可运行状态、
    3.3 阻塞状态
    3.4 死亡状态

    解决线程的抢占执行有如下三种:
    4.线程调度
    线程调度的方法:
    4.1 join()方法
    4.2 sleep()方法
    4.3 yield()方法

    5.线程同步
    需要注意,同步不是同时,同步其实指的排队执行,异步反而指的时同时操作
    5.1 同步方法( synchronized )
    5.2 同步代码块( synchronized)

    public class TestAccount implements Runnable {
    // 所有用TestAccount对象创建的线程共享同一个帐户对象
    private Account acct = new Account();
    public void run() {
    for (int i = 0; i < 5; i++) {
    makeWithdrawal(100);// 取款
    if (acct.getBalance() < 0) {
    System.out.println("账户透支了!");
    }
    }
    }
    private void makeWithdrawal(int amt) {
    if (acct.getBalance() >= amt) {
    System.out.println(Thread.currentThread().getName() + " 准备取款");
    try {
    Thread.sleep(500);// 0.5秒后实现取款
    } catch (InterruptedException ex) {
    }
    // 如果余额足够,则取款
    acct.withdraw(amt);
    System.out.println(Thread.currentThread().getName() + " 完成取款");
    } else {
    // 余额不足给出提示
    System.out.println("余额不足以支付 " + Thread.currentThread().getName()
    + " 的取款,余额为 " + acct.getBalance());
    }
    }
    }

     

    6.线程通信
    线程通信的方法:
    wait();
    notify();
    notifyAll();


    生产者与消费者问题。



    二.线程定义

    1 何为线程
    在一个进程程序中运行的多条分支,这些分支会被cpu轮流切换执行,这些分支我们就叫线程

     


    2 进程vs线程
    进程是一个完整的程序应用,由操作系统去创建执行,进程之间是相互独立的,各自有自己的内存空间,互相之间不能访问各自的内存。例如我们打开记事本,就开启了记事本进程,我们打开酷狗,实际就运行了一个酷狗进程,记事本和酷狗进程是不能互相访问,CPU在调度切换执行不同进程,由于速度太快,感觉像是同时在进行
    线程是进程内部的多路分支,这些分支共享进程的整个内存,所以线程是可以互相访问,也经常容易造成数据冲突,跟进程一样,线程也是由CPU在高速的切换执行

    3 为什么需要线程
    1)多线程改善交互性
    2)提高性能
    cpu是非常强大,我们平常只是使用部分,为了更大程度利用cpu,我们可以开辟多个线程执行,cpu执行更多的任务

    三.定义线程的2种方式

    (1)继承Thread

    (2)实现Runnable

     

    为什么提供继承Thread的方式,又要提实现Runnable的方式?
    为了节约一个宝贵的继承机会


    Hero extends Spriter implements Runnable
    Tree extends Spriter implements Runnable


    四.线程运行的原理

     

     

    线程的三要素:
    1 执行代码
    2 CPU资源
    3 时间片

    start()启动线程,然后start()内部就会去调用线程run()

     

    五.线程的名字

    Thread(String name)
    setName(String name)
    getName()
    Thread.currentThread()

    六.线程的优先级

    1 多线程情况下,是平等的去抢夺cpu资源,换句话说,大家的优先级相同

    2 我们可以设置线程的优先级
    高 - 被cpu执行的概率要高(抢夺cpu的能力强)
    低 - 被cpu执行的概率要低(抢夺cpu的能力弱)

    3 Thread类提供三种优先级常量
    public static final int MAX_PRIORITY 10
    public static final int MIN_PRIORITY 1
    public static final int NORM_PRIORITY 5 (默认)

    4 API
    setPriority(int priority)
    getPriority()

    5 垃圾回收器
    后台的一个低优先级的线程,该线程会不定时扫描垃圾对象

     

     

    七.线程状态

      (1)线程状态总述

    对象
    创建--->消亡

    线程

     

    1 创建状态 (新生状态)
    刚刚new出来,在堆内存中分配内存空间
    2 就绪状态 (可运行状态)
    表示线程已经做好准备,可以被cpu执行,等待cpu分配时间片执行
    3 运行状态
    被cpu选中执行,是一种正在执行的状态,此时会执行run()方法内部的代码
    4 阻塞状态
    相当于一种休眠状态,被动的放弃cpu执行的机会,比如由IO流阻塞导致,或者由另一个线程合并导致
    5 等待池等待状态
    也是主动放弃cpu执行的机会,让给其它线程,需要等待其它的线程通知唤醒
    6 锁池等待
    多个线程同时执行同一份代码,如果想完整的执行完这段代码,然后才轮到另一个线程执行
    此时就可对这份代码上锁,先获得锁的线程先执行,其它线程就在锁池里等待,等待正在执行的线程释放锁
    7 结束(消亡)
    当线程的run()方法执行完,或者调用stop()停止时,线程就进入到消亡状态

    (2)就绪状态(可运行状态)start

    start()方法让一个线程做好准备,进入到就绪状态,还不会立刻执行,可运行状态

    (3)运行状态

    1.run

    当cpu调用到该线程时,那该线程就处于运行状态,运行状态中执行run()方法里面的代码

    2.yield

    如果此时yield的话则会进入就绪状态

    (4)阻塞

    1.sleep

    2.join

    等待调用该方法的线程结束后再继续执行本线程
    合并,如果A线程join到B线程,就会导致B线程进入阻塞状态

     

     

    3.io

    (5)等待

    1.线程同步的数据安全问题

    数据冲突发生的条件
    1 多线程
    2 多个线程访问操作同一个数据

     

     

    2.同步锁和锁池

    同步锁
    相对于异步来说
    需要注意,同步不是同时,同步其实指的排队执行,异步反而指的时同时操作

    锁池
    当多线程同时执行一段上了锁的代码时,获得锁的线程可以执行,其它线程就只能在锁池等待释放锁
    一旦线程释放锁,大家又回到同一起跑线,开始竞争锁

    3.Synchronized代码块

    1 如果一段代码会访问操作一个共同的数据,我们将这段代码用synchronized锁住,这段代码块就称为同步代码块
    synchronized (lock) {
    //代码
    }

    2 注意
    上的锁一定是同一把,如果不是同一把,多线程操作仍然可能会出问题。因为多把锁,每个线程都可能获得一把锁
    lock一定是同一个对象

    3 项目举例

    public Ticket takeTicket(String businessCode,String ip){
    synchronized (lock) {
    //1 按照业务类型查得该业务类型今天的最大号
    select max(no) from ticket where business_code=? and take_date between ? and ?;
    //2 判断max(no)有没有达到最大号
    if(max(no) <= business_limit){

    //3 生成号码对象
    Ticket t = new Ticket();
    t.setNo(max(no)+1);
    t.setBusinessCode(?);
    t.setTakeIp(ip);
    。。。。


    //4 将新生成的号插入数据库
    insert(t);
    //5 返回
    return t;
    }

    }

    4.Synchronized方法

    1 当我们锁住的代码外面没有更多的其它代码时,我们可以将synchronized放到放到方法的声明上
    public synchronized void fn(){
    }

    2 synchronized方法锁住锁是当前对象,就是this
    这里比较容易出问题,当创建多个对象时,就会有多个this,每个this都是不同的锁,达不到同步的效果了

    3 synchronized代码块比synchronized方法能够控制更细的粒度

    5.死锁

    发生在已经获得一把,又像获得另一锁,锁嵌套的问题
    synchronized(lock1){
    synchronized(lock2){

    }
    }

     

     

    死锁只能避免,不能解决

    6.synchronized的优缺点

    优点
    保证共享数据安全

    缺点
    消耗更多资源,效率低

    (6)消亡

    调用stop
    run()执行完成

    进入消亡的状态的线程不能再启动了,等待垃圾回收

     

     

     

    八.线程通信

    (1)wait

    对象监视器 - 实际上就是synchrozied锁住的锁对象
    只有在synchrozied锁住的锁对象上才可以调用wait方法,否则会报错
    调用wait()的线程会导致等待,此时会释放掉锁,让其它线程获得锁

    (2)notify

    (3)生产者&消费者

    当生产者生产的产品需要放置在一个空间,而这个空间是资源有限的时候,就需要等待(空间满时)
    当消费者消费产品时,如果没有产品可以消费,那么此时也需要等待

    九.同步集合

    1 同步集合也叫线程安全的集合,在多线程操作时不会发生数据冲突问题,以前我们学习的ArrayList这种集合是线程不安全的,在多线程情况下会发生问题

     

    //private static List list = new ArrayList();//线程不安全
    //private static List list = new Vector();//线程安全
    //建议使用下面的方式得到一个同步集合,在效率上要比Vector高一点
    private static List list = Collections.synchronizedList(new ArrayList());//线程安全
    2

  • 相关阅读:
    rake db:migrate学会的一些tips
    不提拔你,就因为你只想把工作做好
    jira4r:使用Ruby操作JIRA
    <当下的力量>读书笔记
    Your template was not saved as valid UTF8. 解决方法
    lxml.etree 教程3:Elements carry attributes as a dict
    Ruby目录及文件操作
    体验最火的敏捷SCRUM!(网络直播课程 免费)
    UMLonline技术沙龙:体验一把SCRUM(20121027 广州 免费)
    视频分享:挨踢项目求生法则(1)——团队建设篇
  • 原文地址:https://www.cnblogs.com/Transkai/p/10487076.html
Copyright © 2011-2022 走看看