并发线程基础第一篇

目录

线程创建的方式

方式一

方式二

方式三

线程运行的原理

栈与栈帧

 线程上下文切换(Thread Context Switch)

线程常见方法

start

sleep

yield

线程优先级(priority:1,5,10)

join

为什么需要join方法,示例:

应用之同步

join(Long time):有时间限制的等待,超过这个等待时间,如果等待的线程还未执行结束,不会继续等待。

interrupt

常见方法-过时方法

主线程与守护线程


线程创建的方式

方式一

public static void main(String[] args) {
        //我们创建的线程
        Thread t = new Thread(){
            @Override
            public void run(){//重写run方法
                log.debug("running");
            }
        };
        t.setName("线程1");//给自己线程起名字
        t.start();//创建完之后启动线程

        log.debug("run");
    }

方式二

使用Runnable配合Thread

Thread代表线程,Runnable可运行的任务(线程要执行的代码)

 public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                log.debug("第二种创建线程的方法");//要执行的任务
            }
        };
        Thread t = new Thread(runnable);//创建一个线程对象,把要执行的任务丢到里面
        t.start();//启动线程

        log.debug("run");
    }

方式三

FutureTask配合Thread,用来处理有返回结果的情况

public static void main(String[] args) throws ExecutionException, InterruptedException {

        FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.debug("running");
                Thread.sleep(1000);
                return 100;
            }
        });
        Thread t = new Thread(task);
        t.start();//启动我们的线程
        log.debug("{}",task.get());//打印我们线程返回的结果,因为我们的线程睡了1秒钟,主线程会阻塞1秒钟

        log.debug("run");//主线程
    }

线程运行的原理

栈与栈帧

我们都知道JVM中由堆,栈,方法区组成。其中栈内存就是给线程用,每个线程启动后,虚拟机就会为其分配一块栈内存。

每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存。

每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。

举一个例子:栈帧图解

public class ThreadEx {
    //main主线程
    public static void main(String[] args) {
        method1(10);
    }
    public static void method1(int x) {
        int y = x+1;
        Object m =method2();
        System.out.println(m);
    }
    public static Object method2() {
        Object n = new Object();
        return n;
    }
}

 线程上下文切换(Thread Context Switch)

因为下面一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码:

  • 线程的cpu时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了sleep,yield,wait,join,park,synchronized,lock等方法

当Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器,它的作用就是记住下一条jvm指令的执行地址是线程私有的。

Context Switch频繁发生时,会影响性能。

线程常见方法

start

启动线程,前面我们已展示过,注意,一个线程只能一次,只能调用一次start()方法。

sleep

  • 调用sleep会让当前线程从Running进入Timed Watting状态(阻塞状态)
  • 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptException
  • 睡眠结束后的线程未必会立刻得到执行

示例

new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);//调用sleep方法,使当前线程进入休眠状态
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log.debug("我是sleep方法,我睡了两秒钟");
            }
        }).start();
        log.debug("我是主线程");
    }

20:48:08.706 [main] DEBUG com.example.com.yunlong.test1.Test1 - 我是主线程
20:48:10.710 [Thread-0] DEBUG com.example.com.yunlong.test1.Test1 - 我是sleep方法,我睡了两秒钟

唤醒正在睡眠的线程

public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);//调用sleep方法,使当前线程进入休眠状态
                } catch (InterruptedException e) {
                    log.debug("我被唤醒了");
                    throw new RuntimeException(e);
                }
                log.debug("我是sleep方法,我睡了两秒钟");
            }
        });
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();//唤醒正在睡眠的t1线程
        log.debug("我是主线程");
    }

yield

  • 调用yield会让当前线程从running进入runnable就绪状态,提示线程调度器让出当前线程对cpu的使用,然后调度执行其他线程
  • 具体的实现依赖操作系统的任务调度器(想让出cpu,但是没有其他可执行的线程,没有让出去)

线程优先级(priority:1,5,10)

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  • 如果cpu比较忙,那么优先级高的线程会获得更多的时间片,但cpu闲时,优先级几乎没作用

join

等待线程运行结束。

为什么需要join方法,示例:

@Slf4j
public class Test2 {
    static int n;
    public static void main(String[] args) throws InterruptedException {
        method();
    }
    public static void method() throws InterruptedException {
        Thread t = new Thread(()-> {
                try {
                    Thread.sleep(1000);//当前线程睡一秒钟
                    n =10;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
        });
        t.start();
        t.join();//此方法就是让当前线程等待t线程执行完,再执行之后的代码
        log.debug("{}",n);//如果没有join方法,打印结果为0;加了join方法,打印结果为10
    }
}

应用之同步

以调用角度来讲,如果

  •         需要等待结果返回,才能继续运行就是同步
  •         不需要等待结果返回,就能继续运行就是异步

我们刚刚代码示例:

join(Long time):有时间限制的等待,超过这个等待时间,如果等待的线程还未执行结束,不会继续等待。

interrupt

  • 打断sleep,wait,join的阻塞线程,线程的isInterrupted会被清楚(false);(可以在catch语句块重置打断标记,使得isInterrupted为true)
  • 打断正常执行的线程,该线程的isInterrupted打断标记不会被清除为true;
  • park的线程被打断,也会设置打断标记true;

示例:


@Slf4j
public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    log.debug("我要进入睡眠了");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    log.debug("我被打断了");
                    boolean interrupted = Thread.currentThread().isInterrupted();
                    log.debug("获取t1的被打断标记{}",interrupted);
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        Thread.sleep(1000);
        log.debug("开始打断");
        t1.interrupt();
    }
}

22:45:31.828 [main] DEBUG com.example.com.yunlong.test1.Test3 - 开始打断
22:45:31.828 [Thread-0] DEBUG com.example.com.yunlong.test1.Test3 - 我被打断了
22:45:31.828 [Thread-0] DEBUG com.example.com.yunlong.test1.Test3 - 获取t1的被打断标记false

示例:

@Slf4j
public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    boolean interrupted = Thread.currentThread().isInterrupted();
                    if(interrupted){
                        log.debug("获取t1的否是被打断标记{}",interrupted);
                        break;
                    }
                }
            }
        });
        t1.start();
        Thread.sleep(1000);
        log.debug("开始打断");
        t1.interrupt();
    }
}
22:42:46.282 [main] DEBUG com.example.com.yunlong.test1.Test3 - 开始打断
22:42:46.293 [Thread-0] DEBUG com.example.com.yunlong.test1.Test3 - 获取t1的否是被打断标记true

注意点:isInterrupted()调用之后,不会清除打断标记;interrupted()调用之后,会清楚打断标记;

常见方法-过时方法

  • stop():停止线程运行
  • suspend():挂起(暂停)线程
  • resume():恢复线程运行

原因:容易破坏同步代码块,造成线程死锁

主线程与守护线程

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

示例

@Slf4j
public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()-> {
                while (true){
                    if(Thread.currentThread().isInterrupted()){
                        break;
                    }
                }
        });
        t.setDaemon(true);//设置t线程为守护线程,当主线程(非守护线程)结束的时候,守护线程也结束
        t.start();
        Thread.sleep(1000);
        log.debug("主线程结束");
    }
}

注意:垃圾回收器线程就是一种守护线程,Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等它们处理完当前请求

  • 50
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值