有资源网

搜索
有资源网 首页 编程语言 查看内容

java高并发系列 - 第6天:线程的基本操作

2019-7-26 01:07| 发布者: admin| 查看: 198| 评论: 0

摘要: 新建线程 新建线程很简单。只须要使用new关键字创建一个线程对象,然后调用它的start()启动线程即可。 Thread thread1 = new Thread1(); t1.start(); 那么线程start()之后,会干什么呢?线程有个run()方法,start()

新建线程

新建线程很简单。只须要使用new关键字创建一个线程对象,然后调用它的start()启动线程即可。

Thread thread1 = new Thread1();
t1.start();

那么线程start()之后,会干什么呢?线程有个run()方法,start()会创建一个新的线程并让这个线程执行run()方法。

这里须要留意,下面代码也能通过编译,也能正常执行。但是,却不能新建一个线程,而是在当火线程中调用run()方法,将run方法只是作为一个平常的方法调用。

Thread thread = new Thread1();
thread1.run();

以是,盼望大家留意,调用start方法和直接调用run方法的区别。

start方法是启动一个线程,run方法只会在当火线程中串行的执行run方法中的代码。

默认情况下, 线程的run方法什么都没有,启动一个线程之后马上就竣事了,以是如果你须要线程做点什么,须要把您的代码写到run方法中,以是必须重写run方法。

Thread thread1 = new Thread() {
            @Override
            public void run() {
                System.out.println("hello,我是一个线程!");
            }
        };
thread1.start();

上面是使用匿名内部类实现的,重写了Thread的run方法,而且打印了一条信息。我们可以通过继承Thread类,然后重写run方法,来自界说一个线程。但考虑java是单继承的,从扩展性上来说,我们实现一个接口来自界说一个线程更好一些,java中刚好提供了Runnable接口来自界说一个线程。

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

Thread类有一个非常紧张的构造方法:

public Thread(Runnable target)

我们在看一下Thread的run方法:

public void run() {
        if (target != null) {
            target.run();
        }
    }

当我们启动线程的start方法之后,线程会执行run方法,run方法中会调用Thread构造方法传入的target的run方法。

实现Runnable接口是比较常见的做法,也是保举的做法。

停止线程

一样平常来说线程执行完毕就会竣事,无需手动关闭。但是如果我们想关闭一个正在运行的线程,有什么方法呢?可以看一下Thread类中提供了一个stop()方法,调用这个方法,就可以立即将一个线程停止,非常方便。

package com.itsoku.chat01;

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;

/**
 * description
* time:2019/7/12 17:18
* author:微信公众号:路人甲Java,专注于java技术分享(带你玩转 爬虫、分布式事件、异步消息服务、使命调理、分库分表、大数据等),喜欢请关注! */ @Slf4j public class Demo01 { public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread() { @Override public void run() { log.info("start"); boolean flag = true; while (flag) { ; } log.info("end"); } }; thread1.setName("thread1"); thread1.start(); //当火线程休眠1秒 TimeUnit.SECONDS.sleep(1); //关闭线程thread1 thread1.stop(); //输出线程thread1的状态 log.info("{}", thread1.getState()); //当火线程休眠1秒 TimeUnit.SECONDS.sleep(1); //输出线程thread1的状态 log.info("{}", thread1.getState()); } }

运行代码,输出:

18:02:15.312 [thread1] INFO com.itsoku.chat01.Demo01 - start
18:02:16.311 [main] INFO com.itsoku.chat01.Demo01 - RUNNABLE
18:02:17.313 [main] INFO com.itsoku.chat01.Demo01 - TERMINATED

代码中有个死循环,调用stop方法之后,线程thread1的状态变为TERMINATED(竣事状态),线程制止了。

我们使用idea大概eclipse的时候,会发现这个方法是一个废弃的方法,也就是说,在将来,jdk大概就会移除该方法。

stop方法为何会被废弃而不保举使用?stop方法过于暴力,欺压把正在执行的方法制止了。

大家是否碰到过如许的场景:电力体系须要维修,此时咱们正在写代码,维修职员直接将电源关闭了,代码还没保存的,是不是很瓦解,这种方式就像直接调用线程的stop方法雷同。线程正在运行过程中,被欺压竣事了,大概会导致一些意想不到的后果。可以给大家发送一个关照,告诉大家保存一下手头的工作,将电脑关闭。

线程制止

在java中,线程制止是一种紧张的线程写作机制,从表面上明确,制止就是让目标线程制止执行的意思,现实上并非完全如此。在上面中,我们已经详细讨论了stop方法制止线程的坏处,jdk中提供了更好的制止线程的方法。严格的说,线程制止并不会使线程立即退出,而是给线程发送一个关照,告知目标线程,有人盼望你退出了!至于目标线程吸收到关照之后怎样处理,则完全由目标线程自己决定,这点很紧张,如果制止后,线程立即无条件退出,我们又会到stop方法的老题目。

Thread提供了3个与线程制止有关的方法,这3个方法轻易混淆,大家留意下:

public void interrupt() //制止线程
public boolean isInterrupted() //判定线程是否被制止
public static boolean interrupted()  //判定线程是否被制止,并扫除当前制止状态

interrupt()方法是一个实例方法,它关照目标线程制止,也就是设置制止标志位为true,制止标志位表现当火线程已经被制止了。isInterrupted()方法也是一个实例方法,它判定当火线程是否被制止(通过检查制止标志位)。末了一个方法interrupted()是一个静态方法,返回boolean类型,也是用来判定当火线程是否被制止,但是同时会扫除当火线程的制止标志位的状态。

while (true) {
            if (this.isInterrupted()) {
                System.out.println("我要退出了!");
                break;
            }
        }
    }
};
thread1.setName("thread1");
thread1.start();
TimeUnit.SECONDS.sleep(1);
thread1.interrupt();

上面代码中有个死循环,interrupt()方法被调用之后,线程的制止标志将被置为true,循环体中通过检查线程的制止标志是否为ture(this.isInterrupted())来判定线程是否须要退出了。

再看一种制止的方法:

static volatile boolean isStop = false;

public static void main(String[] args) throws InterruptedException {
    Thread thread1 = new Thread() {
        @Override
        public void run() {
            while (true) {
                if (isStop) {
                    System.out.println("我要退出了!");
                    break;
                }
            }
        }
    };
    thread1.setName("thread1");
    thread1.start();
    TimeUnit.SECONDS.sleep(1);
    isStop = true;
}

代码中通过一个变量isStop来控制线程是否制止。

通过变量控制和线程自带的interrupt方法来制止线程有什么区别呢?

如果一个线程调用了sleep方法,不绝处于休眠状态,通过变量控制,还可以制止线程么?大家可以思索一下。

此时只能使用线程提供的interrupt方法来制止线程了。

public static void main(String[] args) throws InterruptedException {
    Thread thread1 = new Thread() {
        @Override
        public void run() {
            while (true) {
                //休眠100秒
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("我要退出了!");
                break;
            }
        }
    };
    thread1.setName("thread1");
    thread1.start();
    TimeUnit.SECONDS.sleep(1);
    thread1.interrupt();
}

调用interrupt()方法之后,线程的sleep方法将会抛出InterruptedException非常。

Thread thread1 = new Thread() {
    @Override
    public void run() {
        while (true) {
            //休眠100秒
            try {
                TimeUnit.SECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (this.isInterrupted()) {
                System.out.println("我要退出了!");
                break;
            }
        }
    }
};

运行上面的代码,发现步调无法停止。为什么?

代码须要改为:

Thread thread1 = new Thread() {
    @Override
    public void run() {
        while (true) {
            //休眠100秒
            try {
                TimeUnit.SECONDS.sleep(100);
            } catch (InterruptedException e) {
                this.interrupt();
                e.printStackTrace();
            }
            if (this.isInterrupted()) {
                System.out.println("我要退出了!");
                break;
            }
        }
    }
};

上面代码可以停止。

留意:sleep方法由于制止而抛出非常之后,线程的制止标志会被扫除(置为false),以是在非常中须要执行this.interrupt()方法,将制止标志位置为true

等候(wait)和关照(notify)

为了支持多线程之间的协作,JDK提供了两个非常紧张的方法:等候wait()方法和关照notify()方法。这2个方法并不是在Thread类中的,而是在Object类中界说的。这意味着所有的对象都可以调用者两个方法。

public final void wait() throws InterruptedException;
public final native void notify();

当在一个对象实例上调用wait()方法后,当火线程就会在这个对象上等候。这是什么意思?比如在线程A中,调用了obj.wait()方法,那么线程A就会制止继续执行,转为等候状态。等候到什么时候竣事呢?线程A会不绝比及其他线程调用obj.notify()方法为止,这时,obj对象成为了多个线程之间的有效通信本领。

那么wait()方法和notify()方法是怎样工作的呢?如图2.5展示了两者的工作过程。如果一个线程调用了object.wait()方法,那么它就会进出object对象的等候队列。这个队列中,大概会有多个线程,因为体系大概运行多个线程同时等候某一个对象。当object.notify()方法被调用时,它就会从这个队列中随机选择一个线程,并将其唤醒。这里盼望大家留意一下,这个选择是不公平的,并不是先等候线程就会优先被选择,这个选择美满是随机的。

编程语言-免费yoqqjava高并发系列 - 第6天:线程的基本操作yoqq资源(1)

除notify()方法外,Object独享还有一个nofiyAll()方法,它和notify()方法的功能雷同,不同的是,它会唤醒在这个等候队列中所有等候的线程,而不是随机选择一个。

这里夸大一点,Object.wait()方法并不能恣意调用。它必须包含在对应的synchronize语句汇总,无论是wait()方法大概notify()方法都须要首先获取目标独享的一个监督器。图2.6表现了wait()方法和nofiy()方法的工作流程细节。此中T1和T2表现两个线程。T1在正确执行wait()方法钱,必须得到object对象的监督器。而wait()方法在执行后,会开释这个监督器。如许做的目标是使其他等候在object对象上的线程不至于因为T1的休眠而全部无法正常执行。

线程T2在notify()方法调用前,也必须得到object对象的监督器。所幸,此时T1已经开释了这个监督器,因此,T2可以顺利得到object对象的监督器。接着,T2执行了notify()方法尝试唤醒一个等候线程,这里假设唤醒了T1。T1在被唤醒后,要做的第一件事并不是执行后续代码,而是要尝试重新得到object对象的监督器,而这个监督器也正是T1在wait()方法执行前所持有的谁人。如果临时无法得到,则T1还必须等候这个监督器。当监督器顺利得到后,T1才可以在真正意义上继续执行。

编程语言-免费yoqqjava高并发系列 - 第6天:线程的基本操作yoqq资源(2)

给大家上个例子:

package com.itsoku.chat01;

/**
 * description
* time:2019/7/12 17:18
* author:微信公众号:路人甲Java,专注于java技术分享(带你玩转 爬虫、分布式事件、异步消息服务、使命调理、分库分表、大数据等),喜欢请关注! */ public class Demo06 { static Object object = new Object(); public static class T1 extends Thread { @Override 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!"); } } } public static class T2 extends Thread { @Override public void run() { synchronized (object) { 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) throws InterruptedException { new T1().start(); new T2().start(); } }

运行结果:

1562934497212:T1 start!
1562934497212:T1 wait for object
1562934497212:T2 start,notify one thread!
1562934497212:T2 end!
1562934499213:T1 end!

留意下打印结果,T2调用notify方法之后,T1并不能立即继续执行,而是要等候T2开释objec投递锁之后,T1重新乐成获取锁后,才气继续执行。因此末了2行日记相差了2秒(因为T2调用notify方法后休眠了2秒)。

留意:Object.wait()方法和Thread.sleeep()方法都可以让现场等候多少时间。除wait()方法可以被唤醒外,别的一个主要的区别就是wait()方法会开释目标对象的锁,而Thread.sleep()方法不会开释锁。

再给大家解说一下wait(),notify(),notifyAll(),加深一下明确:

可以这么明确,obj对象上有2个队列,如图1,q1:等候队列,q2:准备获取锁的队列;两个队列都为空。

编程语言-免费yoqqjava高并发系列 - 第6天:线程的基本操作yoqq资源(3)

obj.wait()过程:

synchronize(obj){
    obj.wait();
}

如果有3个线程,t1、t2、t3同时执行上面代码,t1、t2、t3会进入q2队列,如图2,进入q2的队列的这些线程才有资格去争抢obj的锁,假设t1争抢到了,那么t2、t3机型在q2中等候着获取锁,t1进入代码块执行wait()方法,此时t1会进入q1队列,然后体系会关照q2队列中的t2、t3去争抢obj的锁,抢到之后过程如t1的过程。末了t1、t2、t3都进入了q1队列,如图3。

编程语言-免费yoqqjava高并发系列 - 第6天:线程的基本操作yoqq资源(4)

编程语言-免费yoqqjava高并发系列 - 第6天:线程的基本操作yoqq资源(5)

上面过程之后,又来了线程t4执行了notify()方法,如下:**

synchronize(obj){
    obj.notify();
}

t4会获取到obj的锁,然后执行notify()方法,体系会从q1队列中随机取一个线程,将其到场到q2队列,如果t2运气比较好,被随机到了,然后t2进入了q2队列,如图4,进入q2的队列的锁才有资格争抢obj的锁,t4线程执行完毕之后,会开释obj的锁,此时队列q2中的t2会获取到obj的锁,然后继续执行,执行完毕之后,q1中包含t1、t3,q2队列为空,如图5

编程语言-免费yoqqjava高并发系列 - 第6天:线程的基本操作yoqq资源(6)

编程语言-免费yoqqjava高并发系列 - 第6天:线程的基本操作yoqq资源(7)

接着又来了个t5队列,执行了notifyAll()方法,如下:

synchronize(obj){
    obj.notifyAll();
}

2.调用obj.wait()方法,当火线程会到场队列queue1,然后会开释obj对象的锁

t5会获取到obj的锁,然后执行notifyAll()方法,体系会将队列q1中的线程都移到q2中,如图6,t5线程执行完毕之后,会开释obj的锁,此时队列q2中的t1、t3会争抢obj的锁,争抢到的继续执行,未加强到的带锁开释之后,体系会关照q2中的线程继续争抢索,然后继续执行,末了两个队列中都为空了。

编程语言-免费yoqqjava高并发系列 - 第6天:线程的基本操作yoqq资源(8)

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

Thread类中还有2个方法,即线程挂起(suspend)继续执行(resume),这2个操纵是一对相反的操纵,被挂起的线程,必须要比及resume()方法操纵后,才气继续执行。体系中已经标注着2个方法过时了,不保举使用。

体系不保举使用suspend()方法去挂起线程是因为suspend()方法导致线程暂停的同时,并不会开释任何锁资源。此时,其他任何线程想要访问被它占用的锁时,都会被连累,导致无法正常运行(如图2.7所示)。直到在对应的线程上举行了resume()方法操纵,被挂起的线程才气继续,从而其他所有壅闭在相干锁上的线程也可以继续执行。但是,如果resume()方法操纵意外地在suspend()方法前就被执行了,那么被挂起的线程大概很难有时机被继续执行了。而且,更严峻的是:它所占用的锁不会被开释,因此大概会导致整个体系工作不正常。而且,对于被挂起的线程,从它线程的状态上看,居然还是Runnable状态,这也会影响我们队体系当前状态的判定。

编程语言-免费yoqqjava高并发系列 - 第6天:线程的基本操作yoqq资源(9)

上个例子:

/**
 * description
* time:2019/7/12 17:18
* author:微信公众号:路人甲Java,专注于java技术分享(带你玩转 爬虫、分布式事件、异步消息服务、使命调理、分库分表、大数据等),喜欢请关注! */ public class Demo07 { static Object object = new Object(); public static class T1 extends Thread { public T1(String name) { super(name); } @Override public void run() { synchronized (object) { System.out.println("in " + this.getName()); Thread.currentThread().suspend(); } } } public static void main(String[] args) throws InterruptedException { T1 t1 = new T1("t1"); t1.start(); Thread.sleep(100); T1 t2 = new T1("t2"); t2.start(); t1.resume(); t2.resume(); t1.join(); t2.join(); } }

运行代码输出:

in t1
in t2

我们会发现步调不会竣事,线程t2被挂起了,导致步调无法竣事,使用jstack命令查看线程堆栈信息可以看到:

"t2" #13 prio=5 os_prio=0 tid=0x000000002796c000 nid=0xa3c runnable [0x000000002867f000]
   java.lang.Thread.State: RUNNABLE
        at java.lang.Thread.suspend0(Native Method)
        at java.lang.Thread.suspend(Thread.java:1029)
        at com.itsoku.chat01.Demo07$T1.run(Demo07.java:20)
        - locked <0x0000000717372fc0> (a java.lang.Object)

发现t2线程在suspend0处被挂起了,t2的状态竟然还是RUNNABLE状态,线程明显被挂起了,状态还是运行中轻易导致我们队当前体系举行误判,代码中已经调用resume()方法了,但是由于时间先后序次的缘故,resume并没有生效,这导致了t2永远滴被挂起了,而且永远占用了object的锁,这对于体系来说大概是致命的。

等候线程竣事(join)和谦让(yeild)

许多时候,一个线程的输入大概非常依赖于别的一个大概多个线程的输出,此时,这个线程就须要等候依赖的线程执行完毕,才气继续执行。jdk提供了join()操纵来实现这个功能。如下所示,表现了2个join()方法:

public final void join() throws InterruptedException;
public final synchronized void join(long millis) throws InterruptedException;

第1个方法表现无穷等候,它会不绝只是当火线程。知道目标线程执行完毕。

第2个方法有个参数,用于指定等候时间,如果超过了给定的时间目标线程还在执行,当火线程也会制止等候,而继续往下执行。

比如:线程T1须要等候T2、T3完成之后才气继续执行,那么在T1线程中须要分别调用T2和T3的join()方法。

上个示例:

/**
 * description
* time:2019/7/12 17:18
* author:微信公众号:路人甲Java,专注于java技术分享(带你玩转 爬虫、分布式事件、异步消息服务、使命调理、分库分表、大数据等),喜欢请关注! */ public class Demo08 { static int num = 0; public static class T1 extends Thread { public T1(String name) { super(name); } @Override public void run() { System.out.println(System.currentTimeMillis() + ",start " + this.getName()); for (int i = 0; i < 10; i++) { num++; try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(System.currentTimeMillis() + ",end " + this.getName()); } } public static void main(String[] args) throws InterruptedException { T1 t1 = new T1("t1"); t1.start(); t1.join(); System.out.println(System.currentTimeMillis() + ",num = " + num); } }

执行结果:

1562939889129,start t1
1562939891134,end t1
1562939891134,num = 10

num的结果为10,1、3行的时间戳相差2秒左右,分析主线程等候t1完成之后才继续执行的。

看一下jdk1.8中Thread.join()方法的实现:

public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

从join的代码中可以看出,在被等候的线程上使用了synchronize,调用了它的wait()方法,线程末了执行完毕之后,体系会自动调用它的notifyAll()方法,唤醒所有在此线程上等候的其他线程。

留意:被等候的线程执行完毕之后,体系自动会调用该线程的notifyAll()方法。以是一样平常情况下,我们不要去在线程对象上使用wait()、notify()、notifyAll()方法。

别的一个方法是Thread.yield(),他的界说如下:

public static native void yield();

yield是谦让的意思,这是一个静态方法,一旦执行,它会让当火线程出让CPU,但须要留意的是,出让CPU并不是说不让当火线程执行了,当火线程在出让CPU后,还会举行CPU资源的争夺,但是能否再抢到CPU的执行权就不一定了。因此,对Thread.yield()方法的调用好像就是在说:我已经完成了一些主要的工作,我可以苏息一下了,可以让CPU给其他线程一些工作时机了。

如果以为一个线程不太紧张,大概优先级比较低,而又担心此线程会过多的占用CPU资源,那么可以在适当的时候调用一下Thread.yield()方法,给与其他线程更多的时机。

总结

  1. 创建线程的2中方式:继承Thread类;实现Runnable接口
  2. 启动线程:调用线程的start()方法
  3. 停止线程:调用线程的stop()方法,方法已过时,发起不要使用
  4. 线程制止相干的方法:调用线程实例interrupt()方法将制止标志置为true;使用线程实例方法isInterrupted()获取制止标志;调用Thread的静态方法interrupted()获取线程是否被制止,此方法调用之后会扫除制止标志(将制止标志置为false了)
  5. wait、notify、notifyAll方法,这块比较难懂确,可以回过头去再理理
  6. 线程挂起使用线程实例方法suspend(),恢复线程使用线程实例方法resume(),这2个方法都过时了,不发起使用
  7. 等候线程竣事:调用线程实例方法join()
  8. 出让cpu资源:调用线程静态方法yeild()

java高并发系列

  • java高并发系列 - 第1天:必须知道的几个概念
  • java高并发系列 - 第2天:并发级别
  • java高并发系列 - 第3天:有关并行的两个紧张定律
  • java高并发系列 - 第4天:JMM相干的一些概念
  • java高并发系列 - 第5天:深入明确进程和线程
  • java高并发系列 - 第6天:线程的根本操纵
  • java高并发系列 - 第7天:volatile与Java内存模型
  • java高并发系列 - 第8天:线程组
  • java高并发系列 - 第9天:用户线程和保卫线程
  • java高并发系列 - 第10天:线程安全和synchronized关键字
  • java高并发系列 - 第11天:线程制止的几种方式
  • java高并发系列 - 第12天JUC:ReentrantLock重入锁

java高并发系列连载中,总计估计会有四五十篇文章,可以关注公众号:javacode2018,获取最新文章。
编程语言-免费yoqqjava高并发系列 - 第6天:线程的基本操作yoqq资源(10)

java高并发系列交流群
编程语言-免费yoqqjava高并发系列 - 第6天:线程的基本操作yoqq资源(11)


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

路过

雷人

握手

鲜花

鸡蛋

最新评论

返回顶部