Fork me on GitHub

线程和进程相关说明

线程上下文切换:会存储上一个执行线程的状态,比如栈帧信息,程序计数器信息等,切换会耗费计算机资源的,所以并不是线程越多,执行效率就越高,如果线程数大于CPU的核心数,切换就会越频繁

线程自己调用了sleep,yield,wait,join,park,synchronized,lock等方法,需要保存线程状态

创建线程

  • 重写Thread的run()方法
  • 将线程和线程要执行的东西分离,使用接口Runnable
  • 用Future附加更多功能

Callable和Runnable

  • Runnable,run()方法是void,所以没有任何返回结果
  • Callable位于java.util.concurrent包,call()方法能返回结果
  • 这两者是没有比较的,根本就是不同的东西,Runnable是线程执行的内容,Callalbe只是说明该线程最后会返回数据,而返回的数据需要用Future去获取
FutureTask<Integer> future = new FutureTask<Integer>(callable);
// FutureTask是实现Runnable接口的,只是又实现了Future接口
new Thread(future, "future").start();

future.get(); // 会返回future线程执行完毕后返回的结果,是一个阻塞方法,可以用另一个线程去获取


// 下面是一个例子说明Runnable和Callable, Future的使用
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        log.debug("Runnable执行操作,普通线程");
    }
};

new Thread(runnable, "normal_thread").start();

Callable<Integer> callable = new Callable<Integer>() {
    public Integer call() throws Exception {
        log.debug("Callable标记操作,返回数据线程");
        sleep(2);
        return new Random().nextInt(100);
    }
};

// FutureTask implements Runnable, Future<V>
FutureTask<Integer> future = new FutureTask<Integer>(callable);
new Thread(future, "callable_thread").start();

new Thread(()->{
    try {
        log.debug("得到线程结果" + future.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}, "future_thread").start();

可以理解为,Callable和Future只是增强了Runnable接口,Callable标记这个线程会有返回,而Future就是去接收这个返回

常见方法

  • start()方法,启动线程
  • run()方法,启动的线程执行的操作,无返回的Runnable和又返回的Callable,Future最后都执行的是Runnable的run()方法
  • join()方法,等待线程运行结束,哪个线程调用join,哪个等待哪个线程结束,带参数的等待,线程还没结束,则继续后面的代码,如果线程提前结束,则join会提前结束
Thread t1 = new Thread(() -> {
    log.debug("t1 start");
    // sleep(1); // join(),等待结束
    // sleep(4); // join(2000),等待2s,之后继续执行
    sleep(3);  // join(7000),等待7s,但线程提前结束,join()也结束
    log.debug("t1 end");
}, "t1_thread");

Thread t2 = new Thread(() -> {
    log.debug("t2 start");

    try {
        // t1.join();  // 等待t1线程执行结束,结束之后才会继续执行后面的代码
        // t1.join(2000); // 等待t1线程2s,2s之后继续执行后面的代码,不管t1是否执行完成,都会继续后面的代码
        t1.join(7000); // 等待t1线程7s,但线程提前结束,则join()也会提前结束
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    log.debug("t2 end");
}, "t2_thread");
t1.start();
t2.start();
  • interrupt(),标记打断状态

  • static interrupted(),判断当前线程是否被打断,会清除打断标记

  • isInterrupted(),判断当前线程是否被打断,不会清除打断标记

  • yield(),让出线程的使用权

打断线程

调用该方法并不会打断线程,只是会标记该线程是否打断

  • 打断阻塞的线程:调用sleep,wait,join的线程会进入阻塞状态,打断阻塞的线程,会清除打断标记,也就是isInterupted为false,会抛出异常(InterruptedException)
  • 打断正常的线程,清除标记设置为true
  • 打断park线程,LockSupport.park()会让线程进入park线程,打断标记为true时,调用LockSupport.park()不会进入park状态
Thread t1 = new Thread(() -> {
    log.debug("t1 thread is running..");
    while(true){

    }
}, "t1");

t1.start();
t1.interrupt();  // 如果线程没有启动(未调用start()方法),打断标记为false
log.debug("t1 thread interrupt normal: " + t1.isInterrupted()); // true

Thread t2 = new Thread(() -> {
    log.debug("t2 thread is running..");
    while(true){
        sleep(3);
        log.debug("t2 running");
    }
}, "t2");

t2.start();
t2.interrupt();
log.debug("t2 thread interrupt with no sleep: " + t2.isInterrupted()); // true,t2线程启动后打断,但t2线程还没进入sleep就打断,所以返回false

Thread t3 = new Thread(() -> {
    log.debug("t3 thread is running..");
    while(true){
        sleep(3);
        log.debug("t3 running");
    }
}, "t3");

t3.start();
sleep(1);  // 保证t3线程进入sleep
t3.interrupt();
log.debug("t3 thread interrupt with real sleep: " + t3.isInterrupted()); // false,t3线程已经进入sleep,所以打断标记为false

Thread t4 = new Thread(()->{
    log.debug("t4 thread execute");
}, "t4");
// t4.start();
sleep(1);
t4.interrupt();
log.debug("t4 thread has terminated: " + t4.isInterrupted()); // false,还未启动,所以是 false

虽然调用了interrupt()方法,t1,t2,t3线程依然会继续执行,该方法做的事仅仅是设置打断标记,设置成功与否还要分情况,并不会真正停止线程,而是要求线程自己去判断是否需要停止,这么做的理由和stop()方法一样,如果是外部线程能终止一个线程,可能导致终止的线程锁死共享资源永远不会释放,所以只是设置打断标记,然后由线程自己去监控是否终止线程,就是两阶段终止模式。

线程中的两阶段终止模式

stop()会真正的终止线程,如果此线程锁住了共享资源,则永远不会释放锁了,所以最好不要使用这个API,使用两阶段终止模式,让线程自己终止线程。

Thread t1 = new Thread(() -> {
    while (true){
        if(Thread.currentThread().isInterrupted()){// 执行阶段
            log.debug("执行收尾工作,释放资源等操作");
            break;  // 由线程自己来结束,而不是外部线程,就是避免资源锁死的情况
        }
        log.debug("模拟线程的各种操作");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 保证打断标记设置成功
            e.printStackTrace();
        }
    }
}, "t1");

t1.start();
sleep(3);
t1.interrupt();  // 准备阶段,标记线程被打断,但不是真正终止线程

线程状态

  • 操作系统的概念:初始,可运行,运行,阻塞,终止,操作系统将线程分为这5个状态
  • java中的状态:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,java中定义了6中状态(java.lang.Thread.State)
    • NEW,创建了线程,但未启动
    • RUNNABLE,其他状态以外的状态,比如等待IO,在操作系统看属于阻塞,在java看属于RUNNABLE状态
    • BLOCKED,等待锁的状态
    • WAITING,等待其他线程,比如join,park,wait
    • TIMED_WAITING,等待其他线程,只是有时间限制,比如sleep,join(1000),wait(1000)
    • TERMINATED,线程结束
      经过测试,线程处于RUNNABLE和BLOCKED状态的时候,执行interrupt()方法会将设置打断标记为true,其他时候不会设置

守护线程

没有其他线程运行的时候,守护线程就会结束。

public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        for (; ; ) {
            log.debug("t1 daemon thread execute");
            sleep(1);
        }
    }, "t1");
    t1.setDaemon(true);
    t1.start();

    Thread t2 = new Thread(()->{
        sleep(1);
        log.debug("t2 thread finish");
    }, "t2");
    t2.start();
}

虽然t1线程是死循环,但它是一个守护线程,当t2线程结束的时候,t1也会结束。

synchronized使用对象锁保证临界区内代码的原子性

能锁住对象,只能是对象,也就是在堆中有空间。synchronized修饰方法时,锁住的是this对象。

面向对象的改进

线程安全的类。共享资源的对象化。

@Slf4j
public class ThreadSafeTest {
    private static int lockCount = 0;

    public static void main(String[] args) {
        useLockCount();
        useLockObject();
    }

    private static void useLockCount() {
        Thread incr = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                synchronized (ThreadSafeTest.class) {
                    lockCount++;
                }
            }
        }, "incr");

        Thread decr = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                synchronized (ThreadSafeTest.class) {
                    lockCount--;
                }
            }
        }, "decr");

        decr.start();
        incr.start();

        try {
            incr.join();
            decr.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.debug("lockCount is: " + lockCount); // 一定为0,使用锁保证资源的安全
    }


    // 两个线程同时访问lockCount,需要保证安全,可以将lockCount用对象的形式实现
    private static void useLockObject() {
        Room room = new Room();
        Thread incr = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                room.incr();
            }
        }, "incr");

        Thread decr = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                room.desc();
            }
        }, "decr");

        decr.start();
        incr.start();

        try {
            incr.join();
            decr.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.debug("useLockObject is: " + room.getCount()); // 一定为0,使用锁保证资源的安全
    }

    // 加锁的行为对象化
    // lockCount的对象化
    public static class Room {
        private static int count = 0;

        public synchronized void incr() {
            count++;
        }

        public synchronized void desc() {
            count--;
        }

        public synchronized int getCount() {
            return count;
        }
    }
}

线程安全的类就是这么提出来的,像String, StringBuffer, Vector, Hashtable就是线程安全的类。

posted @ 2021-10-11 10:47  Catalinas  阅读(43)  评论(0编辑  收藏  举报