关于java:Java多线程之线程及其常用方法

5次阅读

共计 11161 个字符,预计需要花费 28 分钟才能阅读完成。

创立线程和罕用办法

过程与线程的概念

  • 过程

过程是程序执行时的一个实例。程序运行时零碎就会创立一个过程,并为它分配资源,而后把该过程放入过程就绪队列,过程调度器选中它的时候就会为它调配 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("主线程死亡,子线程也要陪着一块死!");
    }
}

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

正文完
 0