多线程基本操作(重点)必看

高并发系列文章

本文章来会议一下多线程中的基本操作,如有错误望指正。

一、创建线程

创建一个线程对象十分简单,只需要new Thread()就可以创建好一个线程对象。
然后调用start()方法即可开启线程。调用start()方法后线程会自动执行run()方
法。
注意:调用start()方法启动线程而不是直接调用run方法。

在这里插入图片描述

举例简单创建一个线程对象并调用:
public class ConcurrentTest {
    public static void main(String[] args) {
       //这里采用匿名内部类的写法
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"执行了");
            }
        };
        thread.setName("线程1");
        thread.start(); //切记使用start()方法开启线程而不是直接选择调用run()方法。
    }
}

接下来试着自定义线程

1.继承Thread

这种方式比较简单,我们只需要一个类去继承Thread类,然后重写run方法后开启线程即可。
注:我们知道java是只支持单继承,如果一个类继承了Thread类就会导致无法继承其它类,因此这种创建线程的方法不怎么使用。一般会采用第二种。

public class MyThread extends Thread {
   //重写方法
    @Override
    public void run() {
        System.out.println("可以执行");
    }

    public static void main(String[] args) {
      new MyThread().start(); //启动线程
    }
}

2.实现Runnable接口

//Thread有一个构造方法
public Thread(Runnable target);

这样的话我们就可以让一个类实现一个接口,然后让该类的实例作为Thread构造的一个参数即可

public class MyThread implements Runnable {

    @Override
    public void run() {
       //Thread.currentThread():当前线程
       //getName():获取当前线程的名字
        System.out.println(Thread.currentThread().getName()+"正常执行");
    }

    public static void main(String[] args) {
      //此时传递的name是线程的名字,也可以不传递name
        new Thread(new MyThread(),"线程2").start();
    }
}

二.中断线程(interrupt)

线程的终止即停掉线程

1.原始终止

Thread中的stop()方法可用于线程的终止,但是该方法已经不被推荐使用。stop()
方法太过于暴力了,它不管线程处于什么样的状态直接终止线程。类似于你的文档还
没有保存直接就给你断电了,是不是超级崩溃!正确的做法是在你断电前提醒我一下
以便我能即时的保存一下。
stop()方法代码演示:
--------------------------------------------------------------------
public class MyThread implements Runnable {
   //sleep线程睡眠后期会详细解释
    @Override
    public void run() {
        for (int i = 1;true;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程打印出"+i);
        }
    }

    public static void main(String[] args) {
         //创建线程
        Thread thread = new Thread(new MyThread());
        try {
            //线程开启
            thread.start();
            //线程睡眠,确保主线程后执行
            Thread.sleep(5000);
            thread.stop();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
//结果:打印到4时程序结束

再次提醒,该方法即将被废弃,不建议(不要)使用该方法结束线程。

2.中断线程

我们知道stop()方法的弊端后了解正确的中断线程方式应给是提前给线程打招呼,线程接收到信息后要不要终止自行决定(并不是接收中断请求就立马中断,那将和stop()方法就没有多大的差别了)

Thread提供的线程中断的三个方法(十分容易混淆)

void   interrupt()       //中断线程
static boolean    interrupted() //测试当前线程是否已经中断
boolean  isinterrupted()    //测试线程是否已经中断
线程中断返回true,否则返回false

用法详细解释

interrupt()方法作为一个实例方法
通知目标线程中断(将线程的中断标志设置为true)
如果线程在调用Object类的wait()…方法或者目标线程的join(…)、sleep(…)方法时
会抛出InterruptedException,并将中断状态清除(中断状态设为false)!!!

isinterrupted()方法是一个实例方法,判断当前线程是否中断(查看中断标志true(中断)\false(未中断))


interrupted()方法是一个静态方法,也用来判断当前线程是否中断,但是和isinterrupted不同的是,它会清除中断状态(置为false)。
换句话说:中断状态可以由interrupted清除,连续调用两次interrupt方法会返回false.

切记:
Thread.interrupted()方法以及sleep发生异常抛出的interruptException异常都会清除中断标记。

  //调用线程的interrupt()方法设置中断状态为true
   if(Thread.interrupted()){  //此时的中断标记为true,但是之后会清除中断标记(置中断状态为false)
        if(!Thread.interrupted()){
          System.out.println("线程中断标记为false");
                                 }
           break;
                          }

代码演示:
//interrupt()中断线程演示
public class MyThread implements Runnable {
    @Override
    public void run() {
       while (true){   //定义一个死循环,使用线程中断来结束循环。
           System.out.println("正在执行");
           if(Thread.interrupted()){ //判断该线程是否是中断线程
               break;
           }
       }
    }

    public static void main(String[] args) {
     Thread thread = new Thread(new MyThread());
        try {
            thread.start();
            Thread.sleep(2000);
            thread.interrupt();//线程中断,设置中断状态为true
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

使用变量的方式来中断线程

public class MyThread implements Runnable {
    //使用volatile关键字可以做到变量的可见性,后续会详细的解释
    static volatile boolean flag = false;
     @Override
    public void run() {
       while (true){
           System.out.println("正在执行");
           if(flag){
               break;
           }
       }
    }

    public static void main(String[] args) {
     new Thread(new MyThread()).start();
        try {
            Thread.sleep(2000);//主线程睡眠确保另一个线程先执行
            flag = true; //修改变量的值
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

注意:
使用变量的方式去中断线程,该变量需要volatile关键字修饰,具体原因和java内存模型有关,可以看一下我的另一篇博客Java内存模型

两种方式有什么区别:

如果一个线程中有sleep休眠的话,我们就需要使用interrupt中断线程。
调用interrupt()方法后,sleep会抛出interruptException异常。并且会清除中断标记。切记抛出异常后会清除中断标记。

三、线程等待与通知

线程等待wait/通知notify
线程的等待与通知是jdk中Object类中的方法,也就是说所有的对象都有这两个方法
在这里插入图片描述线程的等待与通知

一个对象调用wait方法后,在其他线程调用该对象的notify或notifyAll方法之前
当前线程处于等待状态。简单来说在线程A中调用obj.wait()方法后,线程A就会停
止执行处于等待状态,在其他线程调用obj.notify()或obj.notifyAll()之后,
当前线程A才可以继续执行。

在这里插入图片描述解释:

一个线程调用object.wait()方法后,该线程就会加入该对象的等待队列。该队列中
可能有多个线程。因为可能好多线程都需要该对象。如果该对象调用notify后,则会
随机!随机!随机!选择一个线程执行。这里是随机的,也就是说不是先进来的线程
就可以优先执行。而该对象还有一个方法是notifyAll方法,该方法会唤醒队列中的
所有线程。

wait与notify的工作机制:

对象的wait()和notify()不能随便使用,必须包含在synchronized语句块中,因为调用这两个方法之前必须先获取该对象独享的监视器(锁)。

流程:在调用该对象的wait()方法之前先获取该对象的监视器(锁),正常调用wait()方法后会释放掉该对象的监视器。原因有:①不会因为该线程的休眠而导致需要使用该对象的线程不能工作②后续执行notify()方法前也需要获取该对象的监视器。此后一个线程执行该对象的notify方法,随机唤醒一个等待线程。这里注意的是,唤醒的线程不是立即执行后续的代码,而是先去获取该对象在wait之前的那个监视器,获取到监视器后才执行后续的代码

在这里插入图片描述代码演示:

//模拟等待线程
public class MyThreadA extends Thread {
    private Object object;
    public MyThreadA(Object object){
        this.object = object;
    }
    @Override
    public void run() {
       while(true){
           synchronized (object){
               System.out.println(Thread.currentThread().getName()+"开始等待,线程暂停");
               try {
                   object.wait();
                   System.out.println(Thread.currentThread().getName()+"继续执行");
                   break;
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }
    }
}
-----------------------------------------------------------------
//模拟唤醒线程
public class MyThreadB extends Thread {

    private Object object;
    public MyThreadB(Object object){
        this.object = object;
    }

    @Override
    public void run() {
        synchronized (object){
            System.out.println(Thread.currentThread().getName()+"开始执行,两秒后唤醒");
            try {       
                object.notify();
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        MyThreadA myThreadA = new MyThreadA(object);
        myThreadA.setName("等待线程A");
        MyThreadB myThreadB = new MyThreadB(object);
        myThreadB.setName("唤醒线程B");
        myThreadA.start();
        Thread.sleep(100);
        myThreadB.start();
    }
}

控制台打印:
    等待线程A开始等待,线程暂停
    唤醒线程B开始执行,两秒后唤醒
    ....等待三秒
    等待线程A继续执行

控制台打印信息证明:线程B调用notify()方法后线程A并不会立刻执行。而是要先获取该对象的锁

wait方法和sleep方法都会让线程处于暂停状态,两者的区别在于:
① wait方法可以被notify方法唤醒
② wait方法会主动释放对象的锁,而sleep不会主动释放锁

四、线程挂起与执行

线程挂起(suspend)和继续执行(resume) 了解

线程中的suspend和resume是成反对存在,该方法已过时。因为当一个线程调用
suspend方法时,该线程所占用的锁对象是不会释放的,直到该线程调用resume
方法。也就是说在该线程挂起的同时,其他需要该对象锁的线程是不能执行的。
这样一个系统是十分不安全的。因此该方法已不在推荐使用。
public class MyThreadA extends Thread {
    private Object object;
    public MyThreadA(Object object){
        this.object = object;
    }
    @Override
    public void run() {
           synchronized (object){
               this.suspend();
               System.out.println(Thread.currentThread().getName()+"获取锁");
       }
    }
}

public class MyThreadB extends Thread {
    private Object object;
    public MyThreadB(Object object){
        this.object = object;
    }
    @Override
    public void run() {
        System.out.println("尝试去获取锁");
            synchronized (object){
            System.out.println(Thread.currentThread().getName()+"获取锁");
        }
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        MyThreadA myThreadA = new MyThreadA(object);
        myThreadA.setName("线程A");
        MyThreadB myThreadB = new MyThreadB(object);
        myThreadB.setName("线程B");
        myThreadA.start();
        myThreadB.start();
    }
}
//我们发现当线程A获取锁对象后,挂起状态时,此时不会释放锁。
//导致线程B因不能获取锁对象而迟迟不能运行...

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

等待线程结束业务逻辑:

一个线程的输入依赖另一个或多个线程的输出,表明该线程需要依赖的线程执行结束之后才能继续执行。
jdk中提供的方法join就是解决这种逻辑的。

void  join()   //等待该线程终止
void  join(long time) //等待该线程终止,等待时间最长为time毫秒

简单说:ThreadA需要在线程B结束后执行,就可以在线程A中调用ThreadB.join()方法。
举例:

public class MyThreadC implements Runnable {
    static long start = 0;
    @Override
    public void run() {
        try {
            start = System.currentTimeMillis();
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyThreadC());
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("相差时间为"+(System.currentTimeMillis()-start)/1000);
    }
}
控制台打印:相差时间为2

如果将thread.join() ==>替换为 thread.join(1000);//表明等待该线程终止的最长时间为1秒。1秒后主线程获取资源直接执行不在等待线程C的结束。
控制台打印:相差时间为1

线程的谦让yield

static void  yield() //暂停当前线程,执行其它线程
线程yield()方法会让当前线程让出cpu,但是需要注意的是并不代表接下来该线程
不抢占cpu资源,它还是会和其他线程竞争cpu资源的。该方法经常使用在线程优先级低
不重要的线程过多的占用cpu资源。可以调用该方法yield();

说明

多线程的知识点比较碎,需要理解的比较多。多多揣摩一下。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
《Linux多线程服务端编程:使用muduo C++网络库》主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread。 目 录 第1部分C++ 多线程系统编程 第1章线程安全的对象生命期管理3 1.1当析构函数遇到多线程. . . . . . . . . . . . . . . . .. . . . . . . . . . . 3 1.1.1线程安全的定义. . . . . . . . . . . . . . . . .. . . . . . . . . . . 4 1.1.2MutexLock 与MutexLockGuard. . . . . . . . . . . . . . . . . . . . 4 1.1.3一个线程安全的Counter 示例.. . . . . . . . . . . . . . . . . . . 4 1.2对象的创建很简单. . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . 5 1.3销毁太难. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . 7 1.3.1mutex 不是办法. . . . . . . . . . . . . . . . . . . .. . . . . . . . 7 1.3.2作为数据成员的mutex 不能保护析构.. . . . . . . . . . . . . . 8 1.4线程安全的Observer 有多难.. . . . . . . . . . . . . . . . . . . . . . . . 8 1.5原始指针有何不妥. . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . 11 1.6神器shared_ptr/weak_ptr . . . . . . . . . .. . . . . . . . . . . . . . . . 13 1.7插曲:系统地避免各种指针错误. . . . . . . . . . . . . . . . .. . . . . . 14 1.8应用到Observer 上.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 1.9再论shared_ptr 的线程安全.. . . . . . . . . . . . . . . . . . . . . . . . 17 1.10shared_ptr 技术与陷阱. . . .. . . . . . . . . . . . . . . . . . . . . . . . 19 1.11对象池. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 21 1.11.1enable_shared_from_this . . . . . . . . . . . . . . . . . . . . . . 23 1.11.2弱回调. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . 24 1.12替代方案. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . 26 1.13心得与小结. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . 26 1.14Observer 之谬. . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 第2章线程同步精要 2.1互斥器(mutex). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 2.1.1只使用非递归的mutex . . . . . . . . . . . . . .. . . . . . . . . . 33 2.1.2死锁. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . 35 2.2条件变量(condition variable). . . . . . . . . .

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值