【并发基础】理解线程的各种基本操作

新建-启动 线程

1,最朴素的方法就是 new一个线程对象 然后把它 start() 启动起来。
这里要注意:如果你只是start()。而没有重写run()方法。那么run里面默认是空的。
它什么都没有做。意味着并没有实质上新建一个线程。而是一个普通的方法调用。

2,最常用的方法是这样:

 /*
 * 创建线程最常用的方法,实现runnable接口,构造线程时,它传入一个runnable接口的实例,
 * */
public class CreateThread implements Runnable{
    public static void main(String[] args) {
        
        Thread t1=new Thread(new CreateThread());
        t1.start();
    }

    @Override
    public void run(){
        System.out.println("the thread is running");
    }

}

终止线程

thread 提供了一个stop()的方法。
这个方法很暴力 不推荐,因为它可能会造成数据错误,比如一个线程在写“人类高质量男性”的时候突然被stop(),
它可能只写了“人类高”。 后来的读线程 读到的就是错误数据。

那么需要停止一个线程的正确方法是:自定义
volatile boolean stopme = false;
public void stopme(){
stopme = true;
}

当这个方法被调用时 指示线程退出。新手会好奇

这和直接调用stop()有什么区别吗?

有区别! 你的线程run的内容可能很长 你直接调用stop 鬼知道它运行到什么地方了 有可能读写数据正在进行,这时候瞬间你强行让它停下来,会导致数据安全问题。

而上面这种方法 在线程中你可以在一个合适的时间点 优雅的推出线程

线程中断 Interrupt

这里的中断远远不止让线程停止运行那么简单。
而是给目标线程发送一个通知,告知目标线程有人希望你退出! 至于目标线程接到通知后如何处理,则完全由目标线程自行决定。
而不是你让他退出 他就立刻无条件退出了,这样就又回到了stop()方法相似的风险上面。

所以当你有一个线程T1, 你在主线程里面调用T1.Interrupt() 意思是

主线程说 T1 你要停下来!

这时候T1会鸟你吗? 不会

但是这时候在T1里面 isInterrupted方法 会从false变为ture

这时候你可以在T1里面自己决定什么时候中断

比如像下面这个判断一下标记是不是true。

public class InterruptThread {
    public static void main(String[] args) throws InterruptedException{
         Thread t1 = new Thread(){
             @Override
             public void run(){
                while (true){
                    //-----------------------------------
                    //这段代码判断是否调用了中断如果是则退出循环体 定位了中断标志位
                    //如果什么都不做 只有   t1.interrupt();设一个中断标志位是没有用的
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("中断了!");
                        break;
                    }
                    //-----------------------------------
                    Thread.yield();
                }
             }
         };
     
         t1.start();
         Thread.sleep(2000);
         t1.interrupt();
    }
}

等待和通知:

wait()和notify() 是多线程之间协作的桥梁。特别的是这两个方法,不是在thread类中的,而是输出在object类,意味着任何对象可以调用这俩方法。 观察线程的生命周期图会发现,线程a中调用了obj.wait 那么线程a就会停止执行转为等待的状态。等到其他线程调用obj.notify()方法 等待状态就结束了。
这时obj对象就成了多个线程之间的通信手段。
注意:当一个线程调用了obj对象的notify()方法,这时候有很多个线程都调用了obj.wait处于等待的状态。
那么它会随机唤醒一个,你也不知道它会唤醒哪一个,是完全随机的。
所以类似的还有一个 notifyAll()。唤醒所等待的线程。

最重要的部分来了: 这两个方法并不是可以随意调用的,线程必须使用synchronized先取得obj对象的锁,或者换一种描述 先取得对象的监视器,才能执行wait,之后再释放这个监视器。
同理:而另一个线程需要先获得obj对象的锁 再执行notify 再释放锁。

package com.javaBasic.concurrency.basic;

public class WaitAndNotify {
    final static Object object =new Object();
    //创建线程T1
    public static class T1 extends Thread{
        public void run(){
            synchronized (object){
                System.out.println(System.currentTimeMillis()+"t1 start!");
                try {
                    System.out.println(System.currentTimeMillis()+"t1 wait for object");
                    object.wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis()+"t1 end!");
            }
            //出了  synchronized代码块 就等于释放了这把锁
        }
    }

    //创建线程T2
    public static class T2 extends Thread{
        public void run(){
            synchronized (object){
                //注意这里是唤醒1个线程 而不是唤醒T1线程 因为可能有多个线程处于等待状态 它会随机唤醒1个
                System.out.println(System.currentTimeMillis()+"t2 start! notify one thread");
                object.notify();
                System.out.println(System.currentTimeMillis()+"t2 end!");
                try {
                    Thread.sleep(2000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread t1 =new T1();
        Thread t2 =new T2();

        t1.start();
        t2.start();
    }
}

可以明显看到 t2 执行了notify之后t1 没有立即运行 t2故意休眠了2s,才释放锁。

1630292289510 t1 start!
1630292289511 t1 wait for object
1630292289511 t2 start! notify one thread
1630292289511 t2 end!
1630292291511 t1 end!

这里大家要理解sleep和wait的区别

sleep 是线程调用。 他是线程的方法

而wait是对象调用! 新手千万别眼瞎 把wait看成线程调用了wait方法

它是线程1获取了对象的锁 ,然后这个对象调wait方法, 这时候 它停止运行 并且把这对象锁释放

另外一个线程2获取这个锁 然后它在唤醒 线程1 唤醒的时候 它也得先释对象

等于把这个锁又还给了线程1 线程1 继续运行

等待线程结束 join() 和谦让yield()

很多情况下,线程之间的协作,和真实生活中的情况非常相:比如你去茶颜悦色买果茶,打包的那个员工必须要等制作的员工给你把饮料调好了之后才能打包。对应过来就是一个线程需要等到另一个线程完成后他才能工作。


public class JoinMain {
    public volatile static int i =0;
    public static class AddThread extends Thread{
        @Override
        public void run(){
            for(i=0;i<1000000;i++);
        }
    }

    public static void main(String[] args) throws InterruptedException{
        AddThread addThread = new AddThread();
        addThread.start();
        //如果没有这一行代码:join,那么i很可能比100000小很多 甚至可能是0;
        addThread.join();
        System.out.println(i);
    }
}

如果没有这一行代码:join,那么i很可能比100000小很多 甚至可能是0;
因为线程还没开始执行增加i的过程 主函数已经把它打印出来了。

这里就出现了一个很重要的知识点: 主函数main执行和线程里面的执行是相对独立的,有时候可能你主函数已经跑完了线程还没跑完

守护线程

必须再start()之前设置。当主程序执行完之后 整个程序也就随之结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值