线程启动与停止方式

196 阅读6分钟

CPU核心数和线程数的关系:

CPU核心数主要跟线程并行执行数量有关

由于CPU时间片轮转机制,所以线程数是可以超过CPU核心数

并行与并发:

并行表示同一时刻,线程同时运行

并发表示一个时间段内,线程的运行

线程 Thread

OS限制(一个进程最多能创建线程数):

Linux 1000个

windows 2000个

Thread 有两种启动方式:

1、扩展Thread 类;2、实现Runnable接口

官方解释: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.

​ The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method.

public class NewThread {
    //type 1:
    private static class MyThread extends Thread{
        @Override
        public void run() {
            //do my work
            System.out.println("I am extends Thread");
        }
    }
    //type 2:
    private static class MyThreadRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println("I am implements Runnable");
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new MyThread();
        Thread thread2 = new Thread(new MyThreadRunnable());
        thread1.start();
        thread2.start();
    }
}

Thread 和 Runnable 的区别

Thread类 是对线程的抽象

Runnable接口 对任务的抽象

stop()、interrupt()、isInterrupted()、interrupted()理解

stop() 为什么不建议使用?

因为stop() 方法是强制执行的,不会关系线程是否执行完,是否释放资源,如果线程持有锁的情况下被stop掉了,就会出现死锁情况,或者说文件下载过程中,还没来得及保存文件状态就被stop掉了,文件就损坏了。

interrupt() 中断线程的正确方式

调用interrupt()方法只是跟线程打了声招呼(设置标识位),示意该停止了,线程理不理会线程说了算(不是stop那么野蛮的)。线程中断不建议采用自定义的标识进行中断,例如在MyThread类里定义一个boolean cancle 标识用于对线程进行中断,如果线程sleep了,cancle标识将无法起作用。

概念:线程是协作式的,不是抢占式的

public class EndThread {
    private static class MyThread extends Thread{
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " interrupt flag = " + isInterrupted());
//            while(!isInterrupted()){ //线程协作,根据 isInterrupted 的值判断是否中断线程
//            while (true){ //不予理会中断请求
            while (!interrupted()){ // 中断后会重置中断标识为false
                System.out.println(threadName + " is running");
                System.out.println(threadName + " inner interrupt flag = "+isInterrupted());
            }
            System.out.println(threadName + " while end interrupt flag = " + isInterrupted());
        }
    }

    public static void main(String[] args) {
        Thread thread = new MyThread("endThread");
        thread.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        thread.interrupt();//协作式中断,请求中断
//        thread.stop();//抢占式中断,强制中断
    }
}

interrupt()的日志输出:image-20230330115750997.png

直接stop()的日志输出:image-20230330115520721.png

中断异常处理 InterruptException

因为stop() 方法是强制执行的,不会关系线程是否执行完,是否释放资源,如果线程持有锁的情况下被stop掉了,就会出现死锁情况,或者说文件下载过程中,还没来得及保存文件状态就被stop掉了,文件就损坏了。

所以 interrupt 就不允许上述情况的发生,这样当线程休眠的时候,外部线程给休眠线程发送了一个中断信号,休眠线程是能够检测到的,进而进行对应的资源释放操作


public class HasInterruptException {
    private static class MyThread extends Thread{
        public MyThread(String name) {
            super(name);
        }
        private int count = 0;
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " interrupt flag = " + isInterrupted());
            while(!isInterrupted()){ //线程协作,根据 isInterrupted 的值判断是否中断线程
                long st = System.currentTimeMillis();
                try {
                    Thread.sleep(160);
                } catch (InterruptedException e) {
                    System.out.println(threadName + " in InterruptedException interrupt flag = " + isInterrupted());
                    //资源释放,如果有锁要释放锁等操作
                    interrupt();//由于捕获到中断异常后 isInterrupted()被重置为false,所以需要再次interrupt()一下
                    e.printStackTrace();
                }
                System.out.println(threadName + (System.currentTimeMillis() - st) + " is running");
                System.out.println(threadName+ (++count) + " inner interrupt flag = "+isInterrupted());
            }
            System.out.println(threadName + count + " while end interrupt flag = " + isInterrupted());
        }
    }

    public static void main(String[] args) {
        Thread thread = new MyThread("HasInterruptEx");
        thread.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        thread.interrupt();//协作式中断,请求中断
//        thread.stop();//抢占式中断,强制中断
    }
}

日志信息:图片.png

由日志信息可看出线程是在休眠的时候检测到异常信息从而抛出异常结束线程

start() 和 run()的区别

public class StartAndRun {
    private static class MyThread extends Thread{
        public MyThread(String s) {
            super(s);
        }
        @Override
        public void run() {
            int count = 0;
            while (true){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + ++count);
            }
        }
    }
    public static void main(String[] args) {
        MyThread myThread = new MyThread("MyThread");
//        myThread.run();//直接调用就像普通的类一样,运行在当前线程(此处为主线程)
        myThread.start();//开启新线程来执行run()方法
    }
}

join() 方法

举例:排队打饭,给喜欢的人插队(join),那就需要等到喜欢的人打完饭(阻塞)才轮到我。

怎么让两个线程顺序执行?join()

public class UseJoin {
    private static class Goddess implements Runnable{
        private Thread boyFriendThread;

        public Goddess(Thread boyFriendThread) {
            this.boyFriendThread = boyFriendThread;
        }
        @Override
        public void run() {
            System.out.println("Goddess开始排队打饭");
            try {
                boyFriendThread.join();//万万没想到,女神让她男友插队了
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("Goddess打饭完成");
        }
    }
    private static class GoddessBoyfriend implements Runnable{
        @Override
        public void run() {
            System.out.println("GoddessBoyfriend开始排队打饭");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("GoddessBoyfriend打饭完成");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread lison = Thread.currentThread();

        GoddessBoyfriend goddessBoyfriend = new GoddessBoyfriend();
        Thread gbfThread = new Thread(goddessBoyfriend);

        Goddess goddess = new Goddess(gbfThread);
        Thread goddessThread = new Thread(goddess);

        System.out.println("lison开始排队打饭");
        goddessThread.start();
        gbfThread.start();
        
//        join() //默认一直等待,如果join(500),表示只等待500毫秒,之后就就不等了直接去打饭
        goddessThread.join();//让女神插队
        Thread.sleep(1000);
        System.out.println("lison打饭完成");
    }
}

守护线程

/**
 * 是否是守护线程,true 是守护线程。如果是守护线程的话,是不能够阻止 JVM 的退出的,级别很低
 * 如果是守护线程的话,当用户线程都退出后,也会跟着退出
 */
private boolean daemon = false;

可以通过 thread.setDaemon(true) 来把线程设置为守护线程,默认为false。

线程间的共享

多线程共享同一块内存空间,若不加以处理就会出现覆盖的情况,所以就引入了锁的概率

synchronized内置锁

用处和用法:同步块、方法

对象锁

类锁(实际就是class对象锁)

public class SynTest {
    private long count = 0;
    private Object obj = new Object();//作为一个锁对象

    public long incCount(){
        synchronized (this){
            count++;
            return count;
        }
    }

    /** 锁用在方法上,和incCount() 里面的synchronized (this) 是一样意思 */
    public synchronized long incCount2(){
        count++;
        return count;
    }

    public long incCount3(){
        synchronized (obj){
            count++;
            return count;
        }
    }

    public static void main(String[] args) {
        SynTest synTest = new SynTest();
//        testSynSame(synTest);
        testSynNotSame(synTest);
    }
    private static void testSynSame(SynTest synTest){
        // 这里表明 incCount()、incCount2() 锁的是同一个对象
        new Thread(() -> {
            for (int i=0; i<10000; i++) {
                System.out.println("thread1-" + synTest.incCount());
            }
        }).start();
        new Thread(() -> {
            for (int i=0; i<10000; i++)
                System.out.println("thread2-" + synTest.incCount2());
        }).start();
    }

    private static void testSynNotSame(SynTest synTest){
        // 这里表明 incCount()、incCount3() 锁的不是同一个对象
        new Thread(() -> {
            for (int i=0; i<10000; i++) {
                System.out.println("thread1-" + synTest.incCount());
            }
        }).start();
        new Thread(() -> {
            for (int i=0; i<10000; i++)
                System.out.println("thread3-" + synTest.incCount3());
        }).start();
    }
}

volatile:最轻量的同步机制,适合场景:一写多读

volatile 关键字,只能保证多线程的可见性,而不能像锁一样保证原子性

public class VolatileCache {
    private static class MyRunnable implements Runnable{
        //volatile修饰的变量,保证可见性
        public volatile boolean isRunning = true;
//        public boolean isRunning = true;//外部修改了都不知道的情况
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " is running ... ");
            while (isRunning){}
            System.out.println(Thread.currentThread().getName() + " running end");
        }
    }

    public static void main(String[] args) {
        System.out.println("main Thread is running");
        SleepTools.sleep(1);
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();
        SleepTools.sleep(1);
        myRunnable.isRunning = false;//没用volatile修饰时,监听不到变化的
        SleepTools.sleep(3);
        System.out.println("main Thread end");
    }
}

ThreadLocal辨析

每个线程都有变量的副本,实现了线程的隔离。