背景:只使用单个线程完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需要多线程呢?

多线程程序的有点:
1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2.提高计算机系统CPU的利用率。
3.改善程序结构。将即长又复杂的进程分为多个线程,独立运行,利于理解和修改。

并发和并行:

继承Thread类时,只能创建不同的类,线程类间的资源不可共享,而实现Runnable接口后,可以共享线程资源。
并发是两个任务可以在重叠的时间段内启动,运行和完成。并行是任务在同一时间运行,例如,在多核处理器上。
并发是独立执行过程的组合,而并行是同时执行(可能相关的)计算。
并发是一次处理很多事情,并行是同时做很多事情。
应用程序可以是并发的,但不是并行的,这意味着它可以同时处理多个任务,但是没有两个任务在同一时刻执行。
应用程序可以是并行的,但不是并发的,这意味着它同时处理多核CPU中的任务的多个子任务。
一个应用程序可以即不是并行的,也不是并发的,这意味着它一次一个地处理所有任务。
应用程序可以即是并行的也是并发的,这意味着它同时在多核CPU中同时处理多个任务。
并行才是我们通常认为的那个同时做多件事情,而并发则是在线程这个模型下产生的概念
并发表示同时发生了多件事情,通过时间片切换,哪怕只有单一的核心,也可以实现“同时做多件事情”这个效果。

/**
 * 模拟火车站售票窗口,开启三个窗口,总票数为100张
 */
class Window extends Thread {
    // 使用static声明的变量是共用的
    static int ticket = 10000;

    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--);
            } else {
                break;
            }
        }
    }
}
public class TestWindow {

    public static void main(String[] args) {

        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();

        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        w1.start();
        w2.start();
        w3.start();
    }
}

上面的 代码如果ticket变量不是静态的(static修饰的),就会出现问题。使用static声明的变量是共用的。

现在用实现Runnable接口的类来实现该功能。

// 使用实现Runnable接口的方式,售票
class Window1 implements Runnable {
    // 实现接口不用定义为静态的
    int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--);
            } else {
                break;
            }
        }
    }
}

public class TestWindow1 {

    public static void main(String[] args) {

        // 我就new了一个对象,只不过把这个对象放到了三个构造器中创建了三个线程
        Window1 window1 = new Window1();

        Thread thread1 = new Thread(window1);
        Thread thread2 = new Thread(window1);
        Thread thread3 = new Thread(window1);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

对比两种创建线程的方式,那个比较好? 只要是多线程,一定和 Runnable 接口相关的。
分析可得实现的方式优于继承的方式。
原因:
1.实现的方式避免了java单继承的局限性。
2.如果多个线程要操作一份资源数据,更适合使用实现的方式。

多线程的创建步骤:
1.创建一个实现Runnable接口的类
2.实现接口的抽象方法
3.创建一个Runnable接口实现类的对象
4.将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,此对象即为一个线程
5.启动,调用start()方法,并执行run()

守护线程:
Java中的线程分为两类:一种是守护线程,一种是用户线程。
如果设置一个线程是守护线程,则在JVM进程中所有非守护线程结束生命周期后,所有守护线程才会结束生命周期。所以守护线程一般用于后台执行的任务,比如当我关闭游戏客户端的时候,我希望所有数据传输线程都结束,那么我就可以把数据传输相关的线程设置为守护线程。
Java用setDeamon(true)将线程设置为守护线程只能发生在start之前。
Java垃圾回收就是一个典型的守护线程。若JVM中都是守护线程,当前JVM将会自动退出。

public class DeamonThread {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println("working...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
        TimeUnit.SECONDS.sleep(3);
        System.out.println("main线程结束生命周期。");

    }
}

yield()方法
Thread的yield是一个静态方法,API中说当一个线程调用yield方法时,该线程将倾向于让出CPU和其他资源,自己从running状态转换为runnable状态,但是到底让不让出资源,还是CPU调度说了算。

一下代码如果注释掉Thread里面的条件判断,则有时打印0,1,有时打印1,0。若把注释去掉,更倾向打印0,1(感觉没什么用)

public class Yield {

    public static void main(String[] args) {
        IntStream.range(0, 2).mapToObj(Yield::create).forEach(Thread::start);
    }

    private static Thread create(int index) {
        return new Thread(() -> {
            //if (index == 0)
            //   Thread.yield();
            System.out.println(index);
        });
    }
}

priority
对于Java多线程而言,设置优先级对线程的执行并没有可见的影响。
setPriority对CPU也是一种hint,如果CPU忽略了它,那这个优先级就没用了。

public class Priority {
    private static int index = 1;
    private static int max = 100;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            printT("t1");
        });
        Thread t2 = new Thread(() -> {
            printT("t2");
        });
        t1.setPriority(1);
        t2.setPriority(10);
        t2.start();
        t1.start();
    }
    private static void printT(String T) {
        while (index <= max) {
            synchronized (Thread.class) {
                if (index > max) break;
                System.out.println(index + "\t" + T);
                index++;
            }
        }
    }
}

线程的优先级不大于它所在线程租的优先级,如果设置优先级大于线程租优先级,将会设置成线程组优先级。

interrupt()方法
当线程正在执行可被中断的方法时,会尝试捕获InterruptedException,在执行该方法时,一旦被其他线程执行该线程的interrupt方法,就会抛出异常。
如果该线程没有执行可被中断的线程或者线程已经死亡,则线程会忽略该中断。

public class Interrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.MINUTES.sleep(1);
            } catch (InterruptedException e) {
                System.out.println("Fuck! I am be interrupt!");
            }
        });
        t1.start();
        TimeUnit.MICROSECONDS.sleep(4);
        t1.interrupt();
    }
}

isInterrupted()方法:
判断线程是否被打断过,如果之前执行了interrupt方法,则返回true。该方法不会对interrupt状态做修改。

interrupted()方法:
判断线程是否被打断过,返回true或false,同时重置interrupt。
join()
在一个线程中执行另一个线程的join()方法,此时,本线程将会进入BLOCKED状态,直到另一个线程执行结束后,才会继续执行本线程。

public class Join {
    
    public static void main(String[] args) throws InterruptedException {
        List<Thread> threads = IntStream.range(0, 2).mapToObj(Join::create).collect(Collectors.toList());
        threads.forEach(Thread::start);
        for (Thread thread : threads) {
            thread.join();
        }
        for (int k = 0; k < 20; k++) {
            System.out.println("线程 main : " + k);
        }
    }

    public static Thread create(int i) {
        return new Thread(() -> {
            for (int k = 0; k < 20; k++) {
                System.out.println("线程" + i + " : " + k);
            }
        });
    }
}

当线程0和1完全打印结束后,才会打印main线程。
join是个很有用的方法,比如Task任务的拓扑处理,先执某些线程之后才能执行此线程。

线程的生命周期:

JDK中用Thread.State枚举表示了线程的几种状态

要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历一下五种状态:

1.新建:当一个Thread类或其子类的对象被声明并创建,新生的线程对象处于新建状态。

2.就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件

3.运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能。

4.阻塞:在某周特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态

5.死亡:线程完成了它的全部工作或线程被提前强制性的中止

Java 多线程分支合并 java多线程并行处理_System