Concurrent Program-01:进程与线程_Java多线程

一、进程和线程

1. 进程和线程

在这里插入图片描述在这里插入图片描述

2. 并行和并发

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

3. 应用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、Java多线程

1. 创建和运行线程

1、方法一:直接使用Thread
在这里插入图片描述2、方法二:使用Runnable配合使用Thread
在这里插入图片描述
在这里插入图片描述3、Lambda表达式来简化代码:

看一下Runnable接口中的抽象方法:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

如果一个接口中有@FunctionalInterface这个注解,就说明可以使用Lambda表达式来简化代码,如果一个接口中有多个抽象方法,那样没有办法使用Lambda表达式简化。
选中Runnable按住alt+enter可以将手动创建的任务改成Lambda表达式。
在这里插入图片描述4、两种创建线程方法的关系:

继承Thread方法,重写了run()方法,会直接执行子类的run()方法。
实现Runnable接口,实现了Thread类中的run()方法。

//定义一个Runnable接口对象
private Runnable target;

//如果传入了Runnable接口对象target
public Thread(Runnable target) {
      init(null, target, "Thread-" + nextThreadNum(), 0);
  }
//如果target!=null,就调用Runnable接口中的方法
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

在这里插入图片描述5、方法三:使用FutureTask配合使用Thread

看一下底层源码,间接实现了Runnable接口,可以返回任务的执行结果

public class FutureTask<V> implements RunnableFuture<V> {
	........
}

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

在这里插入图片描述

2. 查看进程和线程的方法

在这里插入图片描述

3. 线程运行的原理

在这里插入图片描述

4. start & run

直接调用 run 是在主线程中执行了 run,没有启动新的线程
使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

@Slf4j(topic = "c.Test5")
public class Test5 {

    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };

        System.out.println(t1.getState());//NEW
        t1.start();
        System.out.println(t1.getState());//RUNNABLE
    }
}

5. sleep & yield

在这里插入图片描述 (1)调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞):

@Slf4j(topic = "c.Test6")
public class Test6 {

    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
            	//让t1线程休眠
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        t1.start();
        //还没等到t1休眠主线程就执行了这行代码,所以结果为Runnable
        log.debug("t1 state: {}", t1.getState());//RUNNABLE
		
		//在main方法中调用sleep()方法是让主线程休眠
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //当主线程休眠0.5s后,t1线程肯定进入了休眠状态,打印结果为TIMED_WAITING
        log.debug("t1 state: {}", t1.getState());//TIMED_WAITING
    }
}

(2)其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException:

@Slf4j(topic = "c.Test7")
public class Test7 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("enter sleep...");
                //让t1线程睡眠2s
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    log.debug("wake up...");
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        //主线程睡眠1s,sleep()方法写在哪个线程中就让哪个线程睡眠
        Thread.sleep(1000);
        log.debug("interrupt...");
        
        /**
         * 调用线程t1的interrupt方法,将睡眠中的线程t1唤醒
         * 唤醒后就会抛出InterruptedException,然后执行catch代码块
         * 如果没有唤醒,过了睡眠时间也会醒,但是可以使用interrupt()方法打断
         */
        t1.interrupt();
    }
}

(3)睡眠结束后的线程未必会立刻得到执行,以为未必能的大CPU的使用权。
(4)建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性。

@Slf4j(topic = "c.Test8")
public class Test8 {

    public static void main(String[] args) throws InterruptedException {
        log.debug("enter");
        //睡眠1s,可以选择时间单位
        TimeUnit.SECONDS.sleep(1);
        log.debug("end");
    }
}

6. 线程优先级

在这里插入图片描述演示yield和线程优先级:

@Slf4j(topic = "c.Test9")
public class Test9 {

    public static void main(String[] args) {
        Runnable task1 = () -> {
            int count = 0;
            for (;;) {
                System.out.println("---->1 " + count++);
            }
        };
        Runnable task2 = () -> {
            int count = 0;
            for (;;) {
                //得到CPU的时间片之后,让时间片让给其他线程,那么这个线程得到的时间片就少
                //Thread.yield();
                System.out.println("              ---->2 " + count++);
            }
        };
        Thread t1 = new Thread(task1, "t1");
        Thread t2 = new Thread(task2, "t2");
        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
    }
}

7. join

1、join()方法的作用:等待线程指向结束

因为主线程和线程 t1 是并发执行的,t1 线程需要 1 秒之后才能算出 r=10
而主线程一开始就要打印 r 的结果,所以只能打印出 r=0:

@Slf4j(topic = "c.Test10")
public class Test10 {
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            sleep(1);
            log.debug("结束");
            r = 10;
        },"t1");
        t1.start();
        log.debug("结果为:{}", r);//r=0
        log.debug("结束");
    }
}

用 join,加在 t1.start() 之后,输出r=10:

@Slf4j(topic = "c.Test10")
public class Test10 {
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            sleep(1);
            log.debug("结束");
            r = 10;
        },"t1");
        t1.start();
        //等待调用join()方法的线程运行结束,这里会等待t1线程运行结束再往下执行
        t1.join();
        log.debug("结果为:{}", r);
        log.debug("结束");
    }
}

2、join()方法的应用之同步:

以调用方角度来讲,如果需要等待结果返回,才能继续运行就是同步,不需要等待结果返回,就能继续运行就是异步。

@Slf4j(topic = "c.TestJoin")
public class TestJoin {
    static int r1 = 0;
    static int r2 = 0;

    public static void main(String[] args) throws InterruptedException {
        test2();
    }
    private static void test2() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            sleep(1);
            r1 = 10;
        });
        Thread t2 = new Thread(() -> {
            sleep(2);
            r2 = 20;
        });
        t1.start();
        t2.start();
        long start = System.currentTimeMillis();
        log.debug("join begin");
        //t1线程等待1s
        t1.join();
        log.debug("t2 join end");
        //t2线程等待1s,因为t1线程执行时,t2线程已经等待了1s
        t2.join();
        log.debug("t1 join end");
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }
}

在这里插入图片描述
让t1线程和t2线程的等待顺序调整一下:

@Slf4j(topic = "c.TestJoin")
public class TestJoin {
    static int r1 = 0;
    static int r2 = 0;

    public static void main(String[] args) throws InterruptedException {
        test2();
    }
    private static void test2() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            sleep(1);
            r1 = 10;
        });
        Thread t2 = new Thread(() -> {
            sleep(2);
            r2 = 20;
        });
        t1.start();
        t2.start();
        long start = System.currentTimeMillis();
        log.debug("join begin");
        //t2线程等待2s
        t2.join();
        log.debug("t2 join end");
        //t1线程不用等待,早就醒了
        t1.join();
        log.debug("t1 join end");
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }
}

在这里插入图片描述
在这里插入图片描述
3、有时效的join():

@Slf4j(topic = "c.TestJoin")
public class TestJoin {
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test3();
    }
    public static void test3() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            sleep(2);
            r1 = 10;
        });

        long start = System.currentTimeMillis();
        t1.start();

        // 线程执行结束会导致 join 结束
        log.debug("join begin");
        //等待时间大于线程睡眠线程
        t1.join(3000);
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }
}

8. interrupt

0、如何理解interrupt:

很多人认为Java设置线程中断就是表示线程停止了,不往前执行了:

Thread.currentThread().interrupt()

其实不是这样的,线程中断只是一个状态而已,true表示已中断,false表示未中断

//获取线程中断状态,如果中断了返回true,否则返回false
Thread.currentThread().isInterrupted()

该如何让线程真正停止不往前执行呢?真正让线程停止下来(阻塞),Java提供了一个较为底层的并发工具类:LockSupport,该类常用的方法有两个:

park(Object blocker) 表示阻塞指定线程,参数blocker当前线程对象
unpark(Thread thread) 唤醒指定线程,参数thread指定线程对象 

1、打断sleep,wait,join的线程,这几个线程会让线程处于阻塞状态,打断他们后会清空打断状态。

设置线程中断不影响线程的继续执行,但是线程设置中断后,线程内调用了wait、jion、sleep方法中的一种, 立马抛出一个 InterruptedException,且中断标志被清除,重新设置为false。

@Slf4j(topic = "c.Test11")
public class Test11 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("sleep...");
            try {
                Thread.sleep(5000); // wait, join
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");

        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
        //线程中断只是一个状态而已,true表示已中断,false表示未中断,清除了打断状态
        log.debug("打断标记:{}", t1.isInterrupted());//false
    }
}

2、打断正常状态的线程,不会清空打断状态:

@Slf4j(topic = "c.Test12")
public class Test12 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while(true) {
                boolean interrupted = Thread.currentThread().isInterrupted();
                //interrupted为true时,退出循环
                if(interrupted) {
                    log.debug("被打断了, 退出循环");
                    break;
                }
            }
        }, "t1");
        t1.start();
        //让主线程睡眠1s
        Thread.sleep(1000);
        log.debug("interrupt");
        //打断t1线程
        t1.interrupt();
    }
}

3、打断park线程,不会清空打断状态:

打断 park 线程, 不会清空打断状态,park线程会让线程处于阻塞状态,不再向下执行:

@Slf4j(topic = "c.Test14")
public class Test14 {

    public static void main(String[] args) throws InterruptedException {
        test3();
    }
    private static void test3() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("park...");
            LockSupport.park();
            log.debug("unpark...");
            log.debug("打断状态:{}", Thread.currentThread().isInterrupted());//true
        }, "t1");
        t1.start();

        sleep(1);
        t1.interrupt();
    }
}

打断标记为true时,park就会失效,此时需要将打断标记设置为false:

@Slf4j(topic = "c.Test14")
public class Test14 {
    public static void main(String[] args) throws InterruptedException {
        test3();
    }
    private static void test3() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("park...");
            LockSupport.park();
            log.debug("unpark...");
            //将打断标记设置为false,让park生效
            log.debug("打断状态:{}", Thread.interrupted());
            LockSupport.park();
        }, "t1");
        t1.start();

        sleep(1);
        t1.interrupt();
    }
}

9. 不推荐的方法

下面的方法不会释放锁,造成线程死锁,所以不推荐使用:
在这里插入图片描述

10. 主线程和守护线程

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束

@Slf4j(topic = "c.Test15")
public class Test15 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    break;
                }
            }
            log.debug("结束");
        }, "t1");
        //只要主线程的代码执行结束了,即使守护线程中的代码没有执行结束,java进程也结束
        t1.setDaemon(true);
        t1.start();

        Thread.sleep(1000);
        log.debug("结束");
    }
}

11. 线程的5种状态

可运行状态即就绪状态,不能直接进入阻塞状态,只用运行状态才会进入阻塞状态:
在这里插入图片描述
在这里插入图片描述

12. 线程的6种状态

在这里插入图片描述在这里插入图片描述

发布了665 篇原创文章 · 获赞 115 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/qq_42764468/article/details/104974832