创立线程和罕用办法

过程与线程的概念

  • 过程

过程是程序执行时的一个实例。程序运行时零碎就会创立一个过程,并为它分配资源,而后把该过程放入过程就绪队列,过程调度器选中它的时候就会为它调配CPU工夫,程序开始真正运行。

  • 线程

线程是一条执行门路,是程序执行时的最小单位,它是过程的一个执行流,是CPU调度和分派的根本单位,一个过程能够由很多个线程组成,线程间共享过程的所有资源,每个线程有本人的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就容许多个线程同时(并发)运行。
一个正在运行的软件(如迅雷)就是一个过程,一个过程能够同时运行多个工作( 迅雷软件能够同时下载多个文件,每个下载工作就是一个线程), 能够简略的认为过程是线程的汇合。

为什么要应用多线程
多线程能够进步程序的效率。
理论生存案例:村长要求气冲冲在一个小时内打100桶水,能够气冲冲一个小时只能打25桶水,如果这样就须要4个小时能力实现工作,为了在一个小时可能实现,气冲冲就请美洋洋、懒洋洋、沸洋洋,来帮忙,这样4只羊同时干活,在一小时内实现了工作。本来用4个小时实现的工作当初只须要1个小时就实现了,如果把每只羊看做一个线程,多只羊即多线程能够进步程序的效率。

并发编程的概念

  • 程序编程
public class Main {    // 程序编程:当吃饭吃不完的时候,是不能喝酒的,只能吃完晚能力喝酒    public static void main(String[] args) throws Exception {        // 先吃饭再喝酒        eat();        drink();    }    private static void eat() throws Exception {        System.out.println("开始吃饭...\t" + new Date());        Thread.sleep(5000);        System.out.println("完结吃饭...\t" + new Date());    }    private static void drink() throws Exception {        System.out.println("开始喝酒...\t" + new Date());        Thread.sleep(5000);        System.out.println("完结喝酒...\t" + new Date());    }}

  • 并发编程
public class Main {    public static void main(String[] args) {        // 一边吃饭一边喝酒        new EatThread().start();        new DrinkThread().start();    }}class EatThread extends Thread{    @Override    public void run() {        System.out.println("开始吃饭?...\t" + new Date());        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("完结吃饭?...\t" + new Date());    }}class DrinkThread extends Thread {    @Override    public void run() {        System.out.println("开始喝酒?️...\t" + new Date());        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("完结喝酒?...\t" + new Date());    }}

并发编程,一边吃饭一边喝酒总共用时5秒,比程序编程更快,因为并发编程能够同时运行,而不用等后面的代码运行完之后才容许前面的代码。
同一个时刻一个CPU只能做一件事件,即同一时刻只能一个线程中的局部代码,如果有两个线程,Thread-0和Thread-1,刚开始CPU说Thread-0你先执行,给你3毫秒工夫,Thread-0执行了3毫秒工夫,然而没有执行完,此时CPU会暂停Thread-0执行并记录Thread-0执行到哪行代码了,过后的变量的值是多少,而后CPU说Thread-1你能够执行了,给你2毫秒的工夫,Thread-1执行了2毫秒也没执行完,此时CPU会暂停Thread-1执行并记录Thread-1执行到哪行代码了,过后的变量的值是多少,此时CPU又说Thread-0又该你,这次我给你5毫秒工夫,去执行吧,此时CPU就找出上次Thread-0线程执行到哪行代码了,过后的变量值是多少,而后接着上次继续执行,后果用了2毫秒就Thread-0就执行完了,就终止了,而后CPU说Thread-1又轮到你,这次给你4毫秒,同样CPU也会先找出上次Thread-1线程执行到哪行代码了,过后的变量值是多少,而后接着上次持续开始执行,后果Thread-1在4毫秒内也执行完结了,Thread-1也完结了终止了。CPU在来回扭转线程的执行机会称之为线程上下文切换。

线程的状态
创立(new)状态: 筹备好了一个多线程的对象,即执行了new Thread(); 创立实现后就须要为线程分配内存
就绪(runnable)状态: 调用了start()办法, 期待CPU进行调度
运行(running)状态: 执行run()办法
阻塞(blocked)状态: 临时进行执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程应用
死亡(terminated)状态: 线程销毁(失常执行结束、产生异样或者被打断interrupt()都会导致线程终止)

创立线程的形式
1.子类继承Thread类,重写Thread类run办法,new Thread子类创立线程对象,调用线程对象的start()办法。

public class Main {    public static void main(String[] args) {        new MyThread().start();    }}class MyThread extends Thread {    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());    }}

2.实现类实现Runnable接口,重写Runnable接口run()办法,实例化实现类并将此实例作为Thread类的target来创立线程对象,调用线程对象的start()办法。

public class Main {    public static void main(String[] args) {         // 将Runnable实现类作为Thread的结构参数传递到Thread类中,而后启动Thread类        MyRunnable runnable = new MyRunnable();        new Thread(runnable).start();    }}class MyRunnable implements Runnable {    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());    }}

3.实现类实现Callable接口,重写Callable带有返回值的call()办法,用FutureTask包装Callable实现类的实例,将FutureTask的实例作为Thread类的target来创立线程对象,调用线程对象的start()办法

public class Main {    public static void main(String[] args) throws Exception {        // 将Callable包装成FutureTask,FutureTask也是一种Runnable        MyCallable callable = new MyCallable();        FutureTask<Integer> futureTask = new FutureTask<>(callable);        new Thread(futureTask).start();        // get办法会阻塞调用的线程        Integer sum = futureTask.get();        System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);    }}class MyCallable implements Callable<Integer> {    @Override    public Integer call() throws Exception {        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");        int sum = 0;        for (int i = 0; i <= 100000; i++) {            sum += i;        }        Thread.sleep(5000);        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");        return sum;    }}

三种形式比拟:
1.Thread: 继承形式, 不倡议应用, 因为Java是单继承的,继承了Thread就没方法继承其它类了,不够灵便
2.Runnable: 实现接口,比Thread类更加灵便,没有单继承的限度
3.Callable: Thread和Runnable都是重写的run()办法并且没有返回值,Callable是重写的call()办法并且有返回值并能够借助FutureTask类来判断线程是否曾经执行结束或者勾销线程执行
4.当线程不须要返回值时应用Runnable,须要返回值时就应用Callable

罕用办法
1.Thread.currentThread()

public static void main(String[] args) {    Thread thread = Thread.currentThread();    // 线程名称    String name = thread.getName();    // 线程id    long id = thread.getId();    // 线程曾经启动且尚未终止    // 线程处于正在运行或筹备开始运行的状态,就认为线程是“存活”的    boolean alive = thread.isAlive();    // 线程优先级    int priority = thread.getPriority();    // 是否守护线程    boolean daemon = thread.isDaemon();        // Thread[name=main,id=1,alive=true,priority=5,daemon=false]    System.out.println("Thread[name=" + name + ",id=" + id + ",alive=" + alive + ",priority=" + priority + ",daemon=" + daemon + "]");}

2.sleep() 与 interrupt()
sleep(): 睡眠指定工夫,即让程序暂停指定工夫运行,工夫到了会继续执行代码,如果工夫未到就要醒须要应用interrupt()来随时唤醒
interrupt(): 唤醒正在睡眠的程序,调用interrupt()办法,会使得sleep()办法抛出InterruptedException异样,当sleep()办法抛出异样就中断了sleep的办法,从而让程序持续运行上来

public static void main(String[] args) throws Exception {    Thread thread0 = new Thread(()-> {        try {            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t太困了,让我睡10秒,两头有事叫我,zZZ。。。");            Thread.sleep(10000);        } catch (InterruptedException e) {            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t被叫醒了,又要持续干活了");        }    });    thread0.start();    // 这里睡眠只是为了保障先让下面的那个线程先执行    Thread.sleep(2000);    new Thread(()-> {        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t醒醒,醒醒,别睡了,起来干活了!!!");        // 无需获取锁就能够调用interrupt        thread0.interrupt();    }).start();}

3.wait() 与 notify()
wait、notify和notifyAll办法是Object类的final办法,这些办法不能被子类重写。因而在程序中能够通过this或者super来调this.wait(), super.wait()。
wait(): 导致线程进入期待阻塞状态,会始终期待直到它被其余线程通过notify()或者notifyAll唤醒。该办法只能在同步办法或同步块外部调用。如果以后线程不是锁的持有者,该办法抛出一个IllegalMonitorStateException异样。wait(long timeout): 工夫到了主动执行,相似于sleep(long millis)
notify(): 该办法只能在同步办法或同步块外部调用, 随机抉择一个(留神:只会告诉一个)在该对象上调用wait办法的线程,解除其阻塞状态
notifyAll(): 唤醒所有的wait对象

wait()是让程序暂停执行,线程进入以后实例的期待队列,这个队列属于该实例对象,所以调用notify也必须应用该对象来调用,不能应用别的对象来调用。调用wait和notify必须应用同一个对象来调用。

留神:

  • Object.wait()和Object.notify()和Object.notifyall()必须写在synchronized办法外部或者synchronized块外部
  • 让哪个对象期待wait就去告诉notify哪个对象,不要让A对象期待,后果却去告诉B对象,要操作同一个对象
public class WaitNotifyTest {    public static void main(String[] args) throws Exception {        WaitNotifyTest waitNotifyTest = new WaitNotifyTest();        new Thread(() -> {            try {                waitNotifyTest.printFile();            } catch (InterruptedException e) {                e.printStackTrace();            }        }).start();        new Thread(() -> {            try {                waitNotifyTest.printFile();            } catch (InterruptedException e) {                e.printStackTrace();            }        }).start();        new Thread(() -> {            try {                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t睡觉1秒中,目标是让下面的线程先执行,即先执行wait()");                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            waitNotifyTest.notifyPrint();        }).start();    }    private synchronized void printFile() throws InterruptedException {        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t期待打印文件...");        this.wait();        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印完结。。。");    }    private synchronized void notifyPrint() {        this.notify();        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t告诉实现...");    }}


notifyAll()

4.sleep() 与 wait()
Thread.sleep(long millis): 睡眠时不会开释锁

package com.example.demo;import java.util.Date;public class Main {    public static void main(String[] args) throws InterruptedException {        Object lock = new Object();        new Thread(() -> {            synchronized (lock) {                for (int i = 0; i < 5; i++) {                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);                    try { Thread.sleep(1000); } catch (InterruptedException e) { }                }            }        }).start();        Thread.sleep(1000);        new Thread(() -> {            synchronized (lock) {                for (int i = 0; i < 5; i++) {                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);                }            }        }).start();    }}

因main办法中Thread.sleep(1000)所以下面的线程Thread-0先被执行,当循环第一次时就会Thread.sleep(1000)睡眠,因为sleep并不会开释锁,所以Thread-1得不到执行的机会,所以直到Thread-0执行结束开释锁对象lock,Thread-1能力拿到锁,而后执行Thread-1;

object.wait(long timeout): 会开释锁

public class Main {    public static void main(String[] args) throws InterruptedException {        Object lock = new Object();        new Thread(() -> {            synchronized (object) {                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t期待打印文件...");                try {                    object.wait(5000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印完结。。。");            }        }).start();         // 先下面的线程先执行        Thread.sleep(1000);        new Thread(() -> {            synchronized (object) {                for (int i = 0; i < 5; i++) {                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);                }            }        }).start();    }}

sleep与wait的区别
sleep在Thread类中,wait在Object类中
sleep不会开释锁,wait会开释锁
sleep应用interrupt()来唤醒,wait须要notify或者notifyAll来告诉

5.join()

public class JoinTest {    public static void main(String[] args) {        new Thread(new ParentRunnable()).start();    }}class ParentRunnable implements Runnable {    @Override    public void run() {        // 线程处于new状态        Thread childThread = new Thread(new ChildRunable());        // 线程处于runnable就绪状态        childThread.start();        try {            // 当调用join时,parent会期待child执行结束后再持续运行            // 将某个线程退出到以后线程            childThread.join();        } catch (InterruptedException e) {            e.printStackTrace();        }        for (int i = 0; i < 5; i++) {            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "父线程 running");        }    }}class ChildRunable implements Runnable {    @Override    public void run() {        for (int i = 0; i < 5; i++) {            try { Thread.sleep(1000); } catch (InterruptedException e) {}            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "子线程 running");        }    }}

程序进入主线程,运行Parent对应的线程,Parent的线程代码分两段,一段是启动一个子线程,一段是Parent线程的线程体代码,首先会将Child线程退出到Parent线程,join()办法会调用join(0)办法(join()办法是一般办法并没有加锁,join(0)会加锁),join(0)会执行while(isAlive()) { wait(0);} 循环判断线程是否处于活动状态,如果是持续wait(0)晓得isAlive=false完结掉join(0), 从而完结掉join(), 最初回到Parent线程体中继续执行其它代码。

6.yield()
交出CPU的执行工夫,不会开释锁,让线程进入就绪状态,期待从新获取CPU执行工夫,yield就像一个坏蛋似的,当CPU轮到它了,它却说我先不急,先给其余线程执行吧, 此办法很少被应用到

public class Main {    public static void main(String[] args) {        new Thread(new Runnable() {            int sum = 0;            @Override            public void run() {                long beginTime=System.currentTimeMillis();                for (int i = 0; i < 99999; i++) {                    sum += 1;                    // 去掉该行执行用2毫秒,加上271毫秒                    Thread.yield();                }                long endTime=System.currentTimeMillis();                System.out.println("用时:"+ (endTime - beginTime) + " 毫秒!");            }        }).start();    }}

sleep(long millis) 与 yeid()
sleep(long millis): 须要指定具体睡眠的工夫,不会开释锁,睡眠期间CPU会执行其它线程,睡眠工夫到会立即执行
yeid(): 交出CPU的执行权,不会开释锁,和sleep不同的时当再次获取到CPU的执行,不能确定是什么时候,而sleep是能确定什么时候再次执行。两者的区别就是sleep后再次执行的工夫能确定,而yeid是不能确定的
yield会把CPU的执行权交出去,所以能够用yield来控制线程的执行速度,当一个线程执行的比拟快,此时想让它执行的略微慢一些能够应用该办法,想让线程变慢能够应用sleep和wait,然而这两个办法都须要指定具体工夫,而yield不须要指定具体工夫,让CPU决定什么时候能再次被执行,当放弃到下次再次被执行的两头工夫就是间歇期待的工夫

7.setDaemon(boolean on)
线程分两种:
1.用户线程:如果主线程main进行掉,不会影响用户线程,用户线程能够持续运行。
2.守护线程:如果主线程死亡,守护线程如果没有执行结束也要跟着一块死(就像皇上死了,带刀侍卫也要一块死),GC垃圾回收线程就是守护线程

public class Main {    public static void main(String[] args) {        Thread thread = new Thread() {            @Override            public void run() {                IntStream.range(0, 5).forEach(i -> {                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(Thread.currentThread().getName() + "\ti=" + i);                });            }        };        thread.start();        for (int i = 0; i < 2; i++) {            System.out.println(Thread.currentThread().getName() + "\ti=" + i);        }        System.out.println("主线程执行完结,子线程依然继续执行,主线程和用户线程的生命周期各自独立。");    }}

public class Main {    public static void main(String[] args) {        Thread thread = new Thread() {            @Override            public void run() {                IntStream.range(0, 5).forEach(i -> {                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(Thread.currentThread().getName() + "\ti=" + i);                });            }        };        thread.setDaemon(true);        thread.start();        for (int i = 0; i < 2; i++) {            System.out.println(Thread.currentThread().getName() + "\ti=" + i);        }        System.out.println("主线程死亡,子线程也要陪着一块死!");    }}

参考:
多线程(一):创立线程和线程的罕用办法