• java之多线程 二


    线程的生命周期:

    当线程被创建并被启动时,它既不是一启动就进入了执行状态,在线程的生命周期中,它要经过new(新建),就绪(Runnable),运行(Running),阻塞(Blocked),dead(死亡)。

    当线程启动之后,它不可能一直霸占着cpu独自运行,所有cpu需要在多条线程轮流切换,于是线程就也会多次在运行.就绪之间切换。

    新建和就绪状态

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

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

    启动线程要调用start方法,而不是run方法,永远不要调用线程的run方法,如果调用run方法,系统会把线程对象当作普通的对象,会吧线程的执行体当作普通方法来调用!

    在调用了run方法之后,该线程就不在处于新建状态,不要再调用该线程的start方法!

    java中只能对处于新建状态的线程使用start方法,否则将会引发IllegalThreadStateException异常!

    如果希望调用子线程的start()方法后子线程立即开始执行,可以使用Thread.sleep(1)来让当前运行的线程(主线程)睡眠一毫秒,这样CPU就会立即启动另一个处于就绪状态的线程,需要注意的是,使用Thread.sleep()方法需要声明InterruptedException异常!

     

    运行和阻塞状态:

    当发生如下的几种情况时,将会进入阻塞状态:

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

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

    线程试图获得一个同步监视器,但该同步监视器正被其他线程锁持有

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

    程序调用了线程的suspend方法将该线程挂起

    当以上几个情况,当发生如下的情况将会重新进入就绪状态

    调用sleep()方法过了指定时间
     
    线程调用的阻塞时IO方法依旧返回
     
    线程成功地获得了试图获得的同步监视器
     
    现在正在等待某个通知,而其它线程发出一个通知
     
    处于挂起状态的线程被调用了resume()方法

     

    线程从阻塞状态只能进入就绪状态,无法直接进入运行状态。就绪和运行状态之间的转换通常不受程序控制,而是系统线程的调度决定的。

    调用yield()方法可以让处于运行时的线程转入就绪状态。

    线程死亡:

    线程会以以下三种方式结束,结束后处于死亡状态

    run或call方法执行完成,程序结束

    线程抛出一个未捕获的Exception或者Error

    直接调用该线程的stop方法来结束线程

    当主线程结束时,其它线程不受任何影响,并不会随之结束。一旦子线程启动起来后,他就会拥有和主线程相同的地位,它不会受主线程影响。

    为了测试某个线程是否死亡,可以调用该线程的isAlive方法,当线程处于就绪,运行,阻塞三种状态时,将返回true;当线程处于新建,死亡两种状态时返回为false。

    不要试图对一个已经死亡的线程调用start方法让它重新启动,死亡后的线程无法作为线程使用。

    如果处于非新建状态的线程使用start方法,就会引发IllegalThreadStateException异常。

    控制线程:

    join线程:

    Thread提供了让一个线程等待另一个线程完成的方法--join方法,当在某个程序执行流中调用其他线程的join方法,调用线程将被阻塞,直到被join方法加入的join线程执行完毕为止。

    join方法通常由使用线程的程序调用,以将大问题划为许多小问题,每个小问题分配一个线程,当所有的小问题都被处理之后,再调用主线程进行下一步操作!

    package Test1;
    
    public class JoinThread extends Thread{
    
        public JoinThread(String name)
        {
            super(name);
        }
        @Override
        public void run()
        {
            for (int i = 0; i <10; i++) {
                System.out.println(getName()+" "+i);
            }
        }
    }
    package Test1;
    
    public class Test {
    public static void main(String[] args) throws InterruptedException {
        new JoinThread("新线程").start();
        for (int i = 0; i <10; i++) {
            if(i==5)
            {
                JoinThread j1=new JoinThread("被join的线程");
                j1.start();
                j1.join();
            }
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
    }

    在被join的线程执行前,两个线程交替执行,而主线程处于等待状态,直到被join的线程执行完毕,主线程继续执行!

    后台线程:

    有一种线程,是在后台运行的,其任务是为其他线程提供服务,这种线程称之为后台线程(Daemon Thread),又称之为守护线程。jvm的垃圾回收器就是典型的后台进程。

    当前台线程全部死亡,后台线程会自动死亡

    调用Thread的setDaemon(ture)方法可以将指定线程设置成为后台线程。

    当整个虚拟机只剩下后台线程时,程序就没有运行的必要了,所有虚拟机将退出

    Thread类还提供了一个isDaemon方法,用于指定该线程是否是后台线程!

    前台创建的线程默认为前台线程,而后台创建的线程默认为后台线程。

     

    前台线程死亡时,jvm会通知后台线程死亡,但它从接受指令到做出响应需要一段时间 此外,如果要将某个线程设置为后台线程,必须要在该线程启动之前设置

    ,也就是setDaemon(true)必须在start方法之前调用,否则会引发IllegalThreadStateException异常。

    线程睡眠:sleep

    当前线程调用sleep方法进入阻塞状态时,在其睡眠时间内,该线程不会获得执行的机会

    即便系统中没有其它可执行的线程,处于sleep的线程也不会执行,因此sleep方法常用于暂停程序的执行!

    线程让步:

    yield会让该线程暂停,但是它不会阻塞线程,其只是将线程转入就绪状态,也就是说,yield方法只是让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能出现这种情况,--某个线程调用了yield后,线程调度器又将其调用出来执行

    在多CPU并行的环境下,yield功能有时并不明显

    sleep()方法和yield方法的区别:

    sleep方法暂停当前线程后会给其它线程执行机会,不会理会其它线程的优先级,但yield方法之后给优先级相同,或优先级更高的线程执行机会。

    sleep方法会将线程转入阻塞状态,直到经过阻塞时间才会转为就绪状态,而yield方法不会转入阻塞状态,只是强制将当前线程转入就绪状态

    sleep方法声明抛出了InterruptedException异常,所有调用sleep方法就要捕获此异常,而yield方法则没有

    sleep方法比yield方法有更好的执行!

    改变线程的优先级:

    每个线程都有一定的优先级,优先级更高的线程将会有更多的执行机会

    每个线程默认的优先级都与创建它的父进程的优先级相同,默认情况下,main进程具有普通优先级

    Thread类提供setPriority(int newPriority)和getPriority()方法来设置和返回线程的优先级其中setPriority参数是int类型,范围0到10之间

    Thread类有三个静态常量:MAX_PRIORITY :10  MIN_PRIORITY :1 NORM_PRIORITY:5

    线程同步:

    同步代码块:

    synchronize(obj){
    
    }

    obj:同步监视器,含义:线程开始执行同步代码块时,必须先获得对同步监听器的锁定

    任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块完成执行后,该线程会释放对该同步监视器的锁定。
     
    虽然Java程序允许使用任何对象作为同步监听器,但通常推荐使用可能被并发访问的共享资源充当同步监视器。
     
    同步方法:
    与同步代码块,Java的多线程安全支持还提供了同步方法,同步方法就是使用某个synchronized关键字修饰某个方法,则该方法被称为同步方法。
    对于被synchroize修饰的方法(非static方法而言),无需显示指定同步监视器,同步方法的同步监视器是this,也就是调用该方法调用的对象
    synchronized关键字可以修饰方法,可以修饰代码块,但不能修饰构造器、成员变量。
    线程安全的类具有如下特征:
    该类的对象可以被多个线程安全地访问
    每个线程调用该对象的任一方法之后都将得到正确的结果
    每个线程调用该对象的任一方法之后,该对象状态依然保持合理状态
     
    package Test2;
    
    public class Account {
       private String AccountNo;
       private double balance;
       public Account(){  
       }
       public Account(String accountNo, double balance) {
        AccountNo = accountNo;
        this.balance = balance;
       }
    public String getAccountNo() {
        return AccountNo;
    }
    public double getBalance() {
        return balance;
    }
    public void setAccountNo(String accountNo) {
        AccountNo = accountNo;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }
       public synchronized void draw(double drawcount)
       {
           if(balance>=drawcount)
           {
               System.out.println(Thread.currentThread().getName()+"取款成功,取出"+drawcount+"元");
               try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
               balance-=drawcount;
               System.out.println("余额:"+balance);
           }
           else
           {
               System.out.println(Thread.currentThread().getName()+" "+"余额不足");
           }
       }
       public int hashCode()
       {
           return AccountNo.hashCode();
       }
       public boolean equals(Object obj)
       {
           if(this==obj)
               return true;
           if(obj!=null&&obj.getClass()==Account.class)
           {
               Account a=(Account)obj;
               return a.getAccountNo().equals(AccountNo);
           }
           return false;
       }
    
    }
    package Test2;
    
    public class Test extends Thread {
        private Account account;
        private double drawAccount;
        /**
         * @param account
         * @param drawAccount
         */
        public Test(String name,Account account, double drawAccount) {
            super(name);
            this.account = account;
            this.drawAccount = drawAccount;
        }
        @Override
        public void run() {
            account.draw(drawAccount);
        }
        public static void main(String[] args) {
            Account a=new Account("3242332",1000);
            Test t1=new Test("甲", a, 600);
            Test t2=new Test("乙", a, 600);
            t1.start();
            t2.start();
        }
        
    }

    可变类的线程安全是以降低运行效率为代价的,为减少线程安全锁带来的负面影响,可采用以下策略:
    不要对线程安全类中的所有方法都同步,只对那些会改变竞争资源(竞争资源也就是共享资源)的方法进行同步。
    如果可变类有单线程和多线程两种运行环境,那么要为该可变类提供两种版本(线程安全版和线程不安全版)
     
    StringBuffer和StringBuilder就是这种情况,在单线程时应使用StringBuilder,多线程时使用StringBuffer。

    释放同步监视器的锁定

    线程会在以下几种情况下释放对同步监听器的锁定:
    当前线程的同步方法、同步代码块执行结束,当前线程释放了同步监听器。
    当前线程在同步代码块、同步方法中遇到了break、continue,终止了该代码块、方法的运行,当前线程释放了同步监听器。
    当前线程在同步代码块、同步方法中遇到了未处理的error、exception,导致该代码块、方法意外结束,当前线程释放了同步监听器。
    当前线程执行同步代码块或同步方法时,程序执行了同步监听器对象的wait()方法,则当前线程暂停,并释放同步监听器。
     
    在如下所示的情况下,线程不会释放同步监听器:
    线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器。
    线程执行同步代码块时,其它线程调用了该线程的suspend()方法将该方法挂起,该线程不会释放同步监听器。(所有程序应该避免使用suspend和resume来操控线程)
     
     

    同步锁(Lock)

    从Java5开始,Java提供了一种功能更强大的线程同步机制——通过显式定义同步锁对象来实现同步,在这种机制下,同步锁有Lock对象充当。
     
    Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock允许实现更灵活的结构,可以具有很大的差别的属性,并支持多个相关的Condition对象。
     
    锁提供了对共享资源的独占访问,每次只有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
     
    某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock、ReadWriteLock是JAVA5提供的两个根接口,并未Lock提供了ReetrantLock(可重入锁)的实现类,为ReadWriteLock提供了ReentrantReadWriteLock实现类。
     
    JAVA8新增了新型的StrampedLock类,在大多数场景下它可以替代传统的ReentrantReadWriteLock。ReentrantReadWriteLock提供了三种锁模式——Writing,ReadingOptimistic,Reading。
     
    在实现线程安全的控制中,比较常用的ReentrantLock。使用该对象可以显式的加锁、释放锁。
     
    使用ReentrantLock对象进行同步,加锁和释放锁出现在不同的作用范围时,通常建议使用finally块确保在必要时释放锁。
     
     
    ReentrantLock锁具有可重入性,也就是说一个线程可以对已被加锁的ReentrantLock再次加锁,ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()加锁后,必须显式的地调用unlock()来释放锁,所以一段被锁的代码可以调用另一个被相同锁保护的方法。
     
     
    个人理解,所谓锁,就是指在锁的范围内必须一次性执行,不能中途挂起并执行其它线程。
     

    死锁

    当两线程相互等待对方释放同步监视器是就会发生死锁,JAVA虚拟机没有监测、处理死锁的措施,所以一定要避免死锁的出现。
     
    一旦出现死锁,整个程序不会出现任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
     
    但是死锁很容易发生,尤其是系统中出现多个同步监听器的情况下。
     
    由于Thread类的suspend()方法也很容易导致死锁,所有JAVA不再推荐使用该方法来暂停线程的执行。
     

     

  • 相关阅读:
    获取当前div中的所有div的个数和每一个div的ID and 根据屏幕分辨率计算高度
    在当前页获取父窗口中母版页中的服务器控件的ID
    Asp.net C# 获取本周上周本月上月本年上年第一天最后一天时间大全
    C#后台调用前台js方法
    IComparable与排序
    C# 与.NET2.0 中类型Type的GetMethod方法
    下拉菜单及时间段的获取
    黑马程序员——JAVA基础之简述集合collection
    黑马程序员——JAVA基础之基本数据类型包装类和1.5JDK新特性装箱
    黑马程序员——JAVA基础之String和StringBuffer
  • 原文地址:https://www.cnblogs.com/chimingyang/p/5981895.html
Copyright © 2020-2023  润新知