• 线程(java课堂笔记)


    1.两种方式的差异
    2.线程的生命周期
    3.线程控制(线程的方法)
    4.线程同步
    5.线程同步锁
    一、 两种方式的差异
    A extends Thread :简单
    不能再继承其他类了(Java单继承)同份资源不共享
    B implements Runnable:( 推荐) )多个线程共享一个目标资源,适合多线程处理同一份资源。
    该类还可以继承其他类,也可以实现其他接口。
    二、 线程的生命周期
    新建:当程序使用new创建一个线程后,该线程处于新建状态,此时他和其他java对象一样,仅仅由Java虚拟机为其分配内存并初始化成员变量值。【 Thread r = new Thread()】
    就绪:当线程对象调用start()方法后,该线程处于就绪状态,线程计入线程队列排队,此时该状态线程并未开始执行,它仅表示可以运行了。至于该线程何时运行,取决于JVM线程调度器的调度。【 r.start() 】
    运行:若处于就绪状态的线程获得了CPU,开始执行run()线程执行体,该线程处于执行状态。
    阻塞:线程运行过程中需要被中断,目的是是其他的线程获得执行的机会。该状态就会进入阻塞状态。
    注意:阻塞状态不能直接转成运行状态,阻塞状态只能重新进入就绪状态。
    死亡:run()执行完成,线程正常结束;
    线程抛出未捕获的Exception或Error;
    调用线程的stop()。(易导致死锁,不推荐)
    注意:
    主线程结束后,其他线程不受其影响,不会随之结束;一旦子线程启动起来后,就拥有和主线程相等地位,不受主线程影响。
    例子:
    class MyThread implements Runnable {
      public void run() {
        for (int i = 0; i < 200;i++) {
          System.out.println(i);
        }
      }
    }
    public class MyThreadDemo {
      public static void main(String[] args) {
        for (int i = 0; i < 10;i++) {
          if(i == 5){
            MyThread t = new MyThread();
            new Thread(t).start();
          }
          System.out.println("main" + i);
        }
      }
    }
    测试线程是否活着,可用线程对象的isAlive()方法。当线程处于就绪,运行,阻塞状态返回true。
    当线程处于新建和死亡状态,返回false。
    已死亡的线程是不可能通过start()方法唤醒线程的。
    否则引发IllegalThreadStateException异常;
    public class DieThreadDemo {
      public static void main(String[]args) {
        DeadThread dt = new DeadThread();
        for (int i = 0; i < 100; i++) {
          Thread mainThread =Thread.currentThread();//获得当前线程(Main线程)
          System.out.println(mainThread.getName()+ "--" + i);
          System.out.println("------>"+mainThread.isAlive());
          if (i == 10) {
            dt.start();
          }
        }
        if (!dt.isAlive()) {
          dt.start();
        }
      }
    }
    class DeadThread extends Thread {
      public void run() {
        for (int i = 0; i < 50; i++) {
          System.out.println(getName() +"--" + i);
        }
      }
    }
    三、线程控制
    Join()方法:等待该线程终止。
    join 方法:调用join方法的线程对象强制运行,该线程强制运行期间,其他线程无法运行,必须等到该线程结束后其他线程才可以运行。
    有人也把这种方式成为联合线程
    join方法的重载方法:
    join(long millis):
    join(long millis,int nanos):
    通常很少使用第三个方法:
    程序无须精确到一纳秒;
    计算机硬件和操作系统也无法精确到一纳秒;
    public class JoinDemo {
      public static void main(String[]args) throws Exception {
        Thread join = new Thread(newJoin(),"Join线程");
        join.start();
        int i = 0;
        while(i < 500){
          if(i == 100){
            join.join();
          }
          System.out.println("Main -- > "+ i ++);
        }
      }
    }
    class Join implements Runnable {
      public void run() {
        int i = 0;
        while(i< 200){
          System.out.println(Thread.currentThread().getName() + "--" + i ++ );
        }
      }
    }
    setDaemon()方法
    后台线程:处于后台运行,任务是为其他线程提供服务。也称为“守护线程”或“精灵线程”。
    JVM的垃圾回收就是典型的后台线程。
    特点:若所有的前台线程都死亡,后台线程自动死亡。
    设置后台线程:Thread对象setDaemon(true);
    setDaemon(true)必须在start()调用前。
    否则出现IllegalThreadStateException异常;
    前台线程创建的线程默认是前台线程;
    判断是否是后台线程:使用Thread对象的isDaemon()方法;
    并且当且仅当创建线程是后台线程时,新线程才是后台线程。
    class Daemon extends Thread {
      public void run() {
        for (int i = 0; i < 10000; i++) {
          System.out.println(getName() +"-" + i);
        }
      }
    }
    public class DaemonDemo {
      public static void main(String[]args) {
        Daemon d = new Daemon();
        d.setDaemon(true);//把 d 线程设置成后台线程
        d.start();
        for (int i = 0; i < 5; i++) {
          System.out.println(Thread.currentThread().getName()+"--" + i);
        }
      }
    }
    Sleep()
    线程休眠:
    让执行的线程暂停一段时间,进入阻塞状态。
    sleep(long milllis) throws InterruptedException:毫秒
    sleep(long millis,int nanos) throws InterruptedException:毫秒,纳秒
    调用sleep()后,在指定时间段之内,该线程不会获得执行的机会。
    public class SleepDemo {
      public static void main(String[]args) {
        for (int i = 10; i > 0; i--) {
          System.out.println("还剩" + i);
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e){
            e.printStackTrace();
          }
        }
      }
    }
    四、线程优先级
    每个线程都有优先级,优先级的高低只和线程获得执行机会的次数多少有关。
    并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度;
    默认情况下main线程具有普通的优先级,而它创建的线程也具有普通优先级。
    Thread对象的setPriority(int x)和getPriority()来设置和获得优先级。
    MAX_PRIORITY : 值是10
    MIN_PRIORITY : 值是1
    NORM_PRIORITY : 值是5(主方法默认优先级)
    class Priority extends Thread{
      public void run() {
        for (int i = 0; i < 100; i++) {
          System.out.println(getName()+",优先级=" + getPriority() +"--"+ i);
        }
      }
    }
    public class PriorityDemo {
      public static void main(String[]args) {
        Thread.currentThread().setPriority(7);
        for (int i = 0; i < 100; i++) {
          if(i == 10){
            Priority p1 = new Priority();
            p1.setPriority(Thread.MAX_PRIORITY);
            p1.start();
          }
          if(i == 15){
            Priority p2 = new Priority();
            p2.setPriority(Thread.MIN_PRIORITY);
            p2.start();
          }
          System.out.println("main" + i);
        }
      }
    }
    Yield()方法
    线程礼让:
    暂停当前正在执行的线程对象,并执行其他线程;
    Thread的静态方法,可以是当前线程暂停,但是不会阻塞该线程,而是进入就绪状态。
    所以完全有可能:某个线程调用了yield()之后,线程调度器又把他调度出来重新执行。
    class Yield implements Runnable {
      public void run() {
        for (int i = 0; i < 100; i++) {
          System.out.println(Thread.currentThread().getName() + "--> " + i);
          if (i % 2 == 0) {
            Thread.yield();// 礼让
          }
        }
      }
    }
    public class YieldDemo {
      public static void main(String[]args) {
        Yield y = new Yield();
        new Thread(y, "A").start();
        new Thread(y, "C").start();
      }
    }
    API 过时方法 -- 易死锁, , 不推荐使用
    stop:终止线程
    马上让线程停止运行,并释放该线程所持有的锁,该操作无法保证对象的内部状态正确;
    suspend:挂起线程
    使线程进入“阻塞”状态,该状态下CPU不会分给线程时间片,进入这个状态可以用来暂停一个线程的运行,在被resume方法调用前,不可用.
    如果要suspend的目标线程对一个重要的系统资源持有锁,那么没任何线程可以使用这个资源直到要suspend的目标线程被resumed。
    resume:恢复线程
    恢复被suspend方法挂起的线程Java2开始已经废弃了suspend()和resume()方法,因为使用这两个方法可能会产生死锁,所以应该使用同步对象调用wait()和notify()的机制来代替suspend()和resume()进行线程控制。
    五、线程的安全性问题
    导致安全问题的出现的原因:
    多个线程访问出现延迟。
    线程随机性。
    注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
    我们可以通过Thread.sleep(long time)方法来简单模拟延迟情况。
    六、线程同步
    当多个线程访问同一份数据的时候,很容易出现线程安全的问题。
    让多个操作在同一个时间段内只能有一个线程进行,其它线程要等到该线程完成后才可以继续执行。
    经典的事例: 银行取钱
    用户输入取款金额;
    系统判断余额是否大于取款金额
    若余额大于取款金额,取款成功;
    若余额小于取款金额,取款失败。
    例子分析:
    账户类:Account
    字段balance表示余额
    取钱类:DrawMoney 线程类
    字段a表示账户对象,money表示需要取的金额;
    把取钱行为放在线程体里面;
    提示:
    public void run(){
      if(c.getBlance() >= money){
        System.out.println(getName()+",取走" + money);
        Thread.sleep(100);
        c.setBlance(c.getBlance()- money);
        System.out.println("余额= " +c.getBlance());
      }else{
        System.out.println(getName()+ "余额不足,余额"+ c.getBlance() +",需要取走" +money );
      }
    }
    解决问题:
    方法一:
    当发生以上情况时,java给我们提供了同步代码块
    synchronized(obj){
    //obj表示同步监视器,是同一个同步对象
    /**.....
    TODO SOMETHING
    */
    }
    方法二:
    synchronized 返回值类型 方法名(参数列表){
    /**.....
    TODO SOMETHING
    */
    }
    同步方法的同步监听器其实的是 this
    /**
    * 账号
    *
    */
    public class Account {
    /**
    * 余额
    */
      private double blance;
      public Account(double blance) {
        this.blance = blance;
      }
      public double getBlance() {
        return blance;
      }
      public void setBlance(double blance){
        this.blance = blance;
      }
      public synchronized void draw(doubledrawMoney){
        if(getBlance() >= drawMoney) {
          System.out.println(Thread.currentThread().getName()+",取出" + drawMoney);
          try {
            Thread.sleep(1);
          } catch (InterruptedException e){
            e.printStackTrace();
          }
    //修改余额
          setBlance(getBlance() -drawMoney);
          System.out.println("余额= " +getBlance());
        }else{
          System.out.println("对不起余额不足");
        }
      }
    }
    /**
    * 取钱操作
    */
    public class DrawThread extendsThread{
    /**
    * 账号
    */
      private Account a;
    /**
    * 取的钱
    */
      public double drawMoney;
      public DrawThread(String name,Account a,double drawMoney){
        super(name);
        this.a = a;
        this.drawMoney = drawMoney;
      }
    /**
    * 当多条线程修改同一个共享资源数据的时候,将会导致数据安全问题
    */
      public void run(){
    /*   synchronized (a) {
          if(a.getBlance() >= drawMoney) {
            System.out.println(getName()+",取出" +drawMoney);
            try {
              Thread.sleep(1);
            } catch (InterruptedExceptione) {
              e.printStackTrace();
            }
    //修改余额
            a.setBlance(a.getBlance() -drawMoney);
            System.out.println("余额= " +a.getBlance());
          }else{
            System.out.println("对不起余额不足");
          }
        }*/
        a.draw(drawMoney);
      }
    }
    以上内容是课堂笔记之后是今天作业:做一个售票的程序
    要求三个窗口,一共50张票,随机售票,售完后求小明在每个窗口购票的几率:
    public class Dome {                         //主类
      public static void main(String[] args) {             //主方法
        Chuangkou c1 = new Chuangkou("窗口一");
        Chuangkou c2 = new Chuangkou("窗口二");
        Chuangkou c3 = new Chuangkou("窗口三");
        Thread t1 = new Thread(c1);               //新建三个线程
        Thread t2 = new Thread(c2);
        Thread t3 = new Thread(c3);
        t1.start();                         //三个线程同时运行
        t2.start();
        t3.start();
      }
    }
    
    public class Piao{                        //用来统计票数  
      public static String name = "目前只有这一种票";
      public static int piao = 1;
    }
    
    public class Chuangkou implements Runnable{           //窗口类
      public String name;
      static int a = 0;                        //定义三个静态变量用来统计每个窗口一共卖的票
      static int b = 0;
      static int c = 0;
      public Chuangkou(String name) {               //有参构造器
        this.name = name;
      }
      public void run(){                        //run方法
        while(Piao.piao<49){                    //如果票数等于49就停止
          try {
            Thread.sleep(1/10);                //经测试睡眠1/10的时间最有利于每个窗口的售票率
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          synchronized(Piao.name){                //同步,防止多个线程同时运行
            System.out.println(name+"卖出了第 "+Piao.piao+++"票");
          }
          switch(name){                      //统计窗口票数
            case "窗口一":a+=1;break;
            case "窗口二":b+=1;break;
            case "窗口三":c+=1;break;
          }
        }
        System.out.println(name+"售罄");
        if(Piao.piao == 50){                      //计算出小明购票几率
          System.out.println("小明买窗口一的票的概率为"+(a/50.0));
          System.out.println("小明买窗口二的票的概率为"+(b/50.0));
          System.out.println("小明买窗口三的票的概率为"+(c/50.0));
        }
    
      }
    }

    目前这个作业还有问题,不知为什么小明的效率有时会出现两次,有时不出现,希望大神指导一下。

  • 相关阅读:
    centos同步北京时间
    django-migrate一败再败
    Mac-无法进入mysql,你这样做就对了
    celery beat
    devops--django+ldap
    本地终端连接到远程服务器
    Mac--ModuleNotFoundError: No module named 'magic'
    一站式解决Mac--socket.gaierror: [Errno 8] nodename nor servname provided, or not known
    docker--常用指令
    Mac--管理mysql、redis服务的常用命令
  • 原文地址:https://www.cnblogs.com/qihongbao/p/6780071.html
Copyright © 2020-2023  润新知