线程

线程与进程之间的关系及为什么需要多线程放到进程相关文章中进行详解

线程的状态

查询Thread的源码可以看到,内部有一个State的枚举类,内部有线程的六个状态

public enum State {
        //线程新生
        NEW,
        //运行
        RUNNABLE,
        //阻塞
        BLOCKED,
        //等待 死死的等 需要别人唤醒 
        WAITING,
        //超时等待 自动启动
        TIMED_WAITING,
        //终止
        TERMINATED;
    }
状态的互相切换

img

创建线程

继承Thread类
public class Start {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread("thread#1");
        MyThread thread2 = new MyThread("thread#2");
        thread1.start();
        thread2.start();
    }
}

class MyThread extends Thread{
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "print:"+ i);
        }
    }
}
实现Runnable接口
  • 实现Runnable接口的类
public class Start {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        new Thread(thread1).start();
        new Thread(thread2).start();
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "print:"+ i);
        }
    }
}
  • 匿名内部类
public class Start {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "print:"+ i);
                }
            }
        },"A").start();
        //使用lambda表达式
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "print:"+ i);
            }
        },"B").start();
    }
}
实现Callable接口

image-20200423185934102.png

我们从文档可以看出,Callable接口与Runnable接口的不同:

  • Callable有返回值
  • Callable可以抛出异常
  • Runnable是会执行run()方法,而Callable是执行call()方法

那么Callable接口怎样才能开启线程呢?

  • 我们从Thread的构造方法中可以发现,只能接收Runnable接口,那我们从Runnable找起

image-20200423190854562.png

可以发现Runnable有个FutureTask的实现类,这个类可以使Runnable接口返回结果,也可以执行Callable接口

image-20200423191028916.png

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //new Thread(new Runnable() ).start();
        //    V 就是返回值的类型
        //new Thread(new FutureTask<V>(callable)).start;

        Mythread mythread = new Mythread();
        FutureTask futureTask = new FutureTask(mythread);//适配类

        new Thread(futureTask, "A").start();
        new Thread(futureTask, "B").start();//由于结果会缓存,提高效率,所以只会打印一个call
        Integer o = (Integer)futureTask.get(); //获取返回结果,这个get 方法可能会产生阻塞,会等待线程处理结果的方程。
        //或者使用异步通信 来处理
        System.out.println(o);
    }
}
class Mythread implements Callable<Integer>{
    @Override
    public Integer call() {
        System.out.println("call()");
        return 1024;
    }
}

线程等待(sleep,wait)

两个都可以让线程陷入等待,他们的区别如下:

  • 来自不同的类

    • wait来自Object类 sleep来自Thread类
  • 关于锁的释放

    • wait会释放锁 sleep不会释放锁
  • 使用范围是不同的

    • wait必须在同步代码块中, sleep可以在任何地方
  • 是否需要捕获异常

    • wait不需要捕获异常 sleep需要捕获异常

线程切换(wait,notify)

利用一个常见的生产者消费者模式来演示wait(),notify()

public class PC {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(()->{
            for (int i = 0; i < 50; i++) {
                try {
                    data.produce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 50; i++) {
                try {
                    data.consume();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}
class Data{
    private static int number = 0;
    void produce() throws InterruptedException {
            synchronized (this) {
                if (number != 0) {
                    this.wait();
                }
                number++;
                System.out.println(Thread.currentThread().getName() + "生产" + number);
                this.notify();
            }
    }

    synchronized void consume() throws InterruptedException {
        synchronized (this){
            if (number == 0){
                this.wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "消费" + number);
            this.notify();
        }
    }
}

以上代码可以顺利让线程交替执行,但是有一个地方在多线程的时候会出现BUG。

image-20200423162646185.png

在有着更多的线程时,要注意wait条件的一个判断。

线程礼让(yield)

1、礼让线程,让当前正在执行的线程暂停,但不阻塞

2、将线程从运行状态转为就绪状态

3、让CPU重新调度,礼让不一定成功

public class Yield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield, "A").start();
        new Thread(myYield, "B").start();
    }
}

class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":线程执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + ":线程停止");
    }
}

该代码会出现礼让成功与礼让不成功的情况,可以自己尝试一下。

线程强制插入(join)

等待此线程执行完之后,再执行其他线程

public class JoinTest implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("插队开始。。。" + i); 
        }
    }
    public static void main(String[] args) throws InterruptedException {
        JoinTest joinTest = new JoinTest();
        Thread thread = new Thread(joinTest);
        thread.start();

        for (int i = 0; i < 1000; i++) {
            if (i == 200){
                thread.join();
            }
            System.out.println("main..." + i);
        }
    }
}

线程停止

1、使用线程标志位让线程停止下来

public class StopTest implements Runnable {
    private boolean flag = false;
    @Override
    public void run() {
        int i = 0;
        while (!flag){
            System.out.println("run..." + i++);
        }
    }
    public static void main(String[] args) throws InterruptedException {
        StopTest stopTest = new StopTest();
        Thread thread = new Thread(stopTest);
        thread.start();

        for (int i = 0; i < 15; i++) {
            System.out.println("main"+i);
            Thread.sleep(10);
            if (i == 8){
                stopTest.stop();
                System.out.println("线程停止");
            }
        }
    }

    public void stop(){
        this.flag = true;
    }
}

2、使用interrupt方法中断线程

  • 利用后两种可以判断状态(就是下面图片中的后两种方法)

    • 处于中断状态时,可以停止线程的逻辑操作(类似于标志位)
  • 线程停止-抛异常法

    • 先sleep,后interrupt

      • 若线程处于阻塞状态,调用interrupt后,会直接抛出异常
    • 先interrupt后sleep

      • 处于中断状态后,进入sleep等阻塞状态时,会抛出异常并且中断

image-20200507235619393.png

image-20200507235605690.png

3、使用stop()方法(弃用)

为什么弃用stop:

  1. 调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
  2. 调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。

线程优先度

  • Thread.setPriority(Integer num);

    线程优先度从1-10,但是仍然还是CPU说的算

线程状态监控

  • Thread.getState();

JathonW
1 声望2 粉丝

精致唯一