重學Java併發編程—刨根問底搞懂建立線程到底有幾種方法?

Oracle官方文檔對建立線程的說明

  • Java SE 8 API文檔: docs.oracle.com/javase/8/do…java

    請查看java.lang.Thread的類說明文檔。api

    1. 將類聲明爲Thread的子類,並重寫run方法。

    官方原話:There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread.oracle

    /** * 實現線程的第一個方式 繼承Thread * @author yiren */
    public class MyThread extends Thread {
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " Thread running...");
        }
    
        public static void main(String[] args) throws IOException {
            new MyThread().start();
            System.in.read();
        }
    }
    
    
    
    複製代碼
    1. 實現Runnable接口,並實現run方法。

    官方原話:The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method.異步

/** * 實現線程的第二個方式 實現Runnable接口 * @author yiren */
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " Runnable running...");
    }

    public static void main(String[] args) throws IOException {
        new Thread(new MyRunnable()).start();
        System.in.read();
    }
}

複製代碼

兩種發方法的優缺點

    1. 從代碼的結構上去考慮,具體線程執行的任務是run方法中的代碼,至關於業務代碼,它應該和咱們線程的建立解耦。
    2. 若是繼承Thread,每次想建一個異步任務,咱們都須要去創建一個獨立的線程。而建立一個線程的消耗是比較大的,若是是使用Runnable,咱們就能夠很好得利用線程池之類的工具,這樣能夠大大減小建立和銷燬線程的損耗。節省資源。
    3. 因爲Java是單繼承,繼承了Thread的類事後,該類就沒法再繼承其餘類,大大限制了可擴展性。
/* What will be run. */
    private Runnable target;

		public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

		private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
		
		private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
        ......
        
        this.target = target;
        
      	......
    }

		@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
複製代碼
  • 兩種方法的本質(由上面的Thread類的源代碼可知)ide

    • 繼承Thread 是整個run()方法被重寫
    • 實現Runnable最終是Runnablerun方法被target.run()調用
  • 若是兩種方式都用會有什麼效果呢?微服務

    /** * @author yiren */
    public class MyThreadAndRunnable {
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " runnable running...");
                }
            }
            ) {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " thread running...");
                }
            };
    
            // 這個地方應該是執行重寫Thread類的run方法中的邏輯!
            thread.start();
        }
    }
    複製代碼
    • 很明顯,上面說了不重寫Threadrun()方法就是調用target.run(),若是重寫那也就沒有調用target.run()了。
  • 準確來講,建立線程只有一種方式,那就是構建Thread類,而實現線程的執行單元有兩種方法,就是上面說的兩種。工具

其餘說法:

  • 線程池建立線程學習

    /** * wrong 線程池建立 * * @author yiren */
    public class ThreadPoolExample {
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 1000; i++) {
                executorService.submit(() -> {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName());
                });
            }
        }
    }
    
    // Executors中的DefaultThreadFactory 
    static class DefaultThreadFactory implements ThreadFactory {
            private static final AtomicInteger poolNumber = new AtomicInteger(1);
            private final ThreadGroup group;
            private final AtomicInteger threadNumber = new AtomicInteger(1);
            private final String namePrefix;
    
            DefaultThreadFactory() {
                SecurityManager s = System.getSecurityManager();
                group = (s != null) ? s.getThreadGroup() :
                                      Thread.currentThread().getThreadGroup();
                namePrefix = "pool-" +
                              poolNumber.getAndIncrement() +
                             "-thread-";
            }
    
            public Thread newThread(Runnable r) {
                Thread t = new Thread(group, r,
                                      namePrefix + threadNumber.getAndIncrement(),
                                      0);
                if (t.isDaemon())
                    t.setDaemon(false);
                if (t.getPriority() != Thread.NORM_PRIORITY)
                    t.setPriority(Thread.NORM_PRIORITY);
                return t;
            }
        }
    複製代碼
    • 由上newThread()方法可知,即便是線程池,本質上仍是使用Thread的建立線程。
  • Callable和FutureTask建立線程大數據

  • 由上UML圖可知,實際上內部仍是使用的Thread和Runnable來實現的。this

  • 定時器Timer

    /** * @author yiren */
    public class TimerExample {
        public static void main(String[] args) {
            Timer timer = new Timer();
            // 每隔1s打印下本身的名字
            timer.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " timer running...");
                }
            }, 1000, 1000);
        }
    }
    複製代碼
    • 它的TimerTask其實也是實現了Runnable接口,能夠看下TimerTask這個抽象類
  • 匿名內部類、Lambda表達式 (本質也是同樣的)

    /** * 匿名內部類 * @author yiren */
    public class AnonymousInnerClassExample {
        public static void main(String[] args) {
            new Thread() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            }.start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            }).start();
    
        }
    }
    
    /** * lambda表達式 * @author yiren */
    public class LambdaExample {
        public static void main(String[] args) {
            new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
        }
    }
    複製代碼

關於我

  • 座標杭州,普通本科在讀,計算機科學與技術專業,20年畢業。
  • 主要作Java開發,會寫點Golang、Shell。對微服務、大數據比較感興趣,預備作這個方向。
  • 目前處於菜鳥階段,各位大佬輕噴,小弟正在瘋狂學習。
  • 歡迎你們留言交流!!!
相關文章
相關標籤/搜索