A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

【传智播客郑州校区】线程精进指南

文/传智播客郑州中心就业服务部

多线程的起步
• 线程和进程的区别
– 线程是程序执行的一条路径, 一个进程中可以包含多条线程
• 多线程的并行和并发
– 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
– 我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
– 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。
• 在生活中的表现
– 一边听歌 ,一边看技术文章
– 使用迅雷下载电影
• 在JVM虚拟中的表现
– 使用虚拟机时的入口main方法是一个线程,以及我们的垃圾回收机制也是一个线程
• 在源码中的表现
– fuck the source code
[AppleScript] 纯文本查看 复制代码
 public class Thread implements Runnable {
   //省略部分源码
    ...
    //省略部分构造
    //线程是通过构造传入Runnable对象 
    public Thread(Runnable target) {
        //通过调用init方法 传入target
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
      
     private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
       
        ...
        //把tager赋给成员变量
        this.target = target;
      
        setPriority(priority);
    }
  //自己重写的Runable实现的方法 ,如果外部传入target则使用外部的
  @Override
  public void run() {
        if (target != null) {
            target.run();
        }
    }
  
}
• 分析源码可知如下结论
– 看Thread类的构造函数,传递了Runnable接口的引用
– 通过init()方法找到传递的target给成员变量的target赋值
– 查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法
  
线程的进击
• 重要的方法
– Thread.sleep(毫秒,纳秒) 线程睡眠(时间)
[AppleScript] 纯文本查看 复制代码
  public static void sleep(long millis, int nanos) throws InterruptedException {
                   //省略部分代码     
                   ...  
        sleep(millis);
 }
//调用本地sleep方法
 public static native void sleep(long millis) throws InterruptedException;
–setDaemon() 守护线程,当其他线程都结束时执行此线程
   
public final void setDaemon(boolean on) {
        checkAccess();
                  //通过调用isAlive判断是否有存活线程
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

/**
     * Tests if this thread is alive. A thread is alive if it has
     * been started and has not yet died.
     *
     * @return  <code>true</code> if this thread is alive;
     *          <code>false</code> otherwise.
     */
    public final native boolean isAlive();
• join() 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
[AppleScript] 纯文本查看 复制代码
 public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

   //通过wait(0)死循环 让线程永远处于等待状态 
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
• yield() 礼让线程 但不是绝对的,取决于cpu的时机
[AppleScript] 纯文本查看 复制代码
  /**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     */
    public static native void yield();
• 继承的Object方法
  wait() 方法导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法唤醒该线程,调用wait方法会释放对当前同步监视器的锁定  notify() 唤醒在此同步监视器上等待的单个线程,如果所有线程都在此同步监视器上等待,则会唤醒其中一个线程,选择是任意的,只有当前线程放弃对同步监视器的锁定后(即调用wait()方法),才可以执行被唤醒的线程  notifyAll() 唤醒在此同步监视器上等待的所有线程,只有当前线程放弃对同步监视器的锁定后,才可以执行被唤醒的线程
线程的进阶
• 1.什么情况下需要同步
– 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
– 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
• 2.同步代码块
– 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
– 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
[AppleScript] 纯文本查看 复制代码
 class Printer {
  public static void print1() {
          synchronized(Printer.class){                                
            //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
              System.out.print("黑");
              System.out.print("马");
              System.out.print("程");
              System.out.print("序");
              System.out.print("员");
              System.out.print("\r\n");
          }
      }
      /*
       * 非静态同步函数的锁是:this
       * 静态的同步函数的锁是:字节码对象
       */
      public static synchronized void print2() {        
          System.out.print("传");
          System.out.print("智");
          System.out.print("播");
          System.out.print("客");
          System.out.print("\r\n");
      }
}
线程安全问题
• 多线程并发操作同一数据时, 就有可能出现线程安全问题
• 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
[AppleScript] 纯文本查看 复制代码
 public class DemoSynchronized {        
        /**
         * @param args
         * 需求:铁路售票,一共100张,通过四个窗口卖完.
         */
        public static void main(String[] args) {
                TicketsSeller t1 = new TicketsSeller();
                TicketsSeller t2 = new TicketsSeller();
                TicketsSeller t3 = new TicketsSeller();
                TicketsSeller t4 = new TicketsSeller();
                
                t1.setName("窗口1");
                t2.setName("窗口2");
                t3.setName("窗口3");
                t4.setName("窗口4");
                t1.start();
                t2.start();
                t3.start();
                t4.start();
        }

}

class TicketsSeller extends Thread {
        private static int tickets = 100;
        static Object obj = new Object();
        public TicketsSeller() {
                super();
                
        }
        public TicketsSeller(String name) {
                super(name);
        }
        public void run() {
                while(true) {
                        synchronized(obj) {
                                if(tickets <= 0) 
                                        break;
                                try {
                                        Thread.sleep(10);//线程1睡,线程2睡,线程3睡,线程4睡
                                } catch (InterruptedException e) {
                                        
                                        e.printStackTrace();
                                }
                                System.out.println(getName() + "...这是第" + tickets-- + "号票");
                        }
                }
        }
}

传智播客·黑马程序员郑州校区地址
郑州市高新区长椿路11号大学科技园(西区)东门8号楼三层
联系方式 0371-56061160/61/62
来校路线:地铁一号线梧桐街站A口出


2 个回复

倒序浏览
忠实粉丝
回复 使用道具 举报
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马