java多线程基础知识(二)

上一篇文章java多线程基础知识(一)讲到了线程的优先级,状态,Daemon线程等,这里继续线程的基础知识.

线程的启动和结束

上篇文章的demo中,都是用start方法启动线程,当run方法运行结束后,线程自动结束,当我们new一个Thread的时候
java Thread daemonThread = new Thread(daemonRunner, "daemonThread");
可以去看他的构造方式,都做了一些什么事儿

public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }

最终的init方法

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name.toCharArray();
        //获取其父线程,即实当前线程
        Thread parent = currentThread();
        //获取java安全管理器
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* 分配线程ID */
        tid = nextThreadID();
    }

上面过程中,新初始化的线程是由其父线程来配置的,子线程继承了他的一些属性.

线程中断

中断时线程的一个标示位,表示一个线程是否被其他线程终端过.

  • 调用该线程的interrupt()进行终端操作
  • 检查自身是否被中断isInterrupted()
  • 调用静态方法interrupted()对当前线程的中断标示位进行复位

先看代码示例:

/**
 * 线程终端
 * Created by gzd on 2017/1/11.
 */
public class ThreadInterrupted {

    //一直睡觉的线程
    private static Runnable sleep = () ->{
        while (true){
            try {
                TimeUnit.SECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    //一直运行的线程
    private static Runnable go = () ->{
        while (true){
        }
    };

    public static void main(String[] args) throws InterruptedException {
        Thread sleep = new Thread(ThreadInterrupted.sleep, "sleepThread");
        sleep.setDaemon(true);
        Thread goThread = new Thread(go, "goThread");
        goThread.setDaemon(true);
        sleep.start();
        goThread.start();
        TimeUnit.SECONDS.sleep(5);
        sleep.interrupt();
        goThread.interrupt();
        System.out.println("sleep是否被中断  "+sleep.isInterrupted());
        System.out.println("go是否被中断  "+goThread.isInterrupted());
        TimeUnit.SECONDS.sleep(3);
    }

打印结果是:

sleep是否被中断  false
go是否被中断  true
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at cn.gzd.concurrency.base.ThreadInterrupted.lambda$static$0(ThreadInterrupted.java:14)
    at cn.gzd.concurrency.base.ThreadInterrupted$$Lambda$1/1096979270.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)

因为sleep调用了sleep方法,sleep方法被中断会抛出InterruptedException异常,而jvm在抛出这个异常后,会清除中断位,所以结果是false,而go线程被中断,标识位正常显示为true!

//sleep被中断,抛出异常
public void sleep(long timeout) throws InterruptedException {
        if (timeout > 0) {
            long ms = toMillis(timeout);
            int ns = excessNanos(timeout, ms);
            Thread.sleep(ms, ns);
        }
    }
线程的暂停,恢复和停止

概念很简单,不多说,直接看代码:

/**
 * 线程的暂停,恢复和通停止
 * Created by gzd on 2017/1/11.
 */
public class PrintThread {

    private static Runnable print = ()->{
        while (true){
            System.out.println("打印线程运行时间是: "+ LocalDateTime.now());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    public static void main(String[] args) throws InterruptedException {
        Thread printThread = new Thread(print, "printThread");
        printThread.setDaemon(true);
        printThread.start();
        TimeUnit.SECONDS.sleep(3);
        printThread.suspend();
        System.out.println("线程在"+LocalDateTime.now()+"暂停");
        TimeUnit.SECONDS.sleep(3);
        printThread.resume();
        System.out.println("线程在"+LocalDateTime.now()+"恢复");
        TimeUnit.SECONDS.sleep(3);
        printThread.stop();
        System.out.println("线程在"+LocalDateTime.now()+"停止");
    }

}

可以看到suspend(),resume(),stop()这三个方法,暂停,恢复,停止.其实他们已经过期了,不建议使用了.因为他们在执行的时候,会占着锁而睡眠状态,所以会造成死锁.至于不用这个那用什么?后面会提到的!

终止线程

终止线程,可以用Thread.Interrupted(),也可以用一个变量来控制线程的结束.

public class StopThread {

    public static void main(String[] args) throws InterruptedException {
        Runner runner = new Runner();
        Thread thread = new Thread(runner);
        Runner runner1 = new Runner();
        Thread thread1 = new Thread(runner1);
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();
        thread1.start();
        TimeUnit.SECONDS.sleep(1);
        runner1.stop();
        System.out.println(runner.count);
        System.out.println(runner1.count);
    }

    private static class Runner implements Runnable{

        private long count = 1L;
        private volatile boolean ifStop = Boolean.TRUE;

        @Override
        public void run() {
            while (ifStop && !Thread.currentThread().isInterrupted()){
                count++;
            }
        }

        public void stop(){
            ifStop = Boolean.FALSE;
        }
    }

}
线程间通讯
volatile和synchronized

多个线程可以对同一个对象操作,其实,每个线程操作的并不一定就是这个目标对象本身,因为每个线程都可以有这个变量的拷贝,这样的目的是加快速度,所以当前线程看到的并不是最新的对象.如果要保持每个线程对此对象的操作都是最新的,那么在对象这里,就要设置同步.

  • volatile关键字的作用,就是告诉程序,这个变量,必须从内存中获取,操作之后,再刷新到内存.滥用会降低效率!
  • synchronized关键字可以修饰方法,或者代码块儿,功能就是在同一时间,只有一个线程可以操作这块区域.

查看java字节码文件,可以看到,同步快这里使用可monitorenter和monitorexit两个命令,核心就是对对象的监控器monitor获取,这个过程是排他的.所有的java对象都会有自己的监控器,程序执行到同步代码这的时候,必须获得这个对象的监控器才能进去同步代码块中,没有获取的话,在排在队列中,状态更改为BLOCKED状态

下图描述了对象监控器,同步队列,对象,线程之间的关系:
这里写图片描述

等待/通知

如果一个线程修改了一个对象之后,需要把结果通知给另一个线程,这样的需求该怎么做,如果采用轮训的办法,那就不太好了,其实java的OBject对象,已经有了这些方法:

    /**
     * 当一个线程在对象上等待,调用这个方法,就会让线程从wait()方法返回
     * 前提是该线程获取到了锁
     */
    public final native void notify();
    /**
     * 同上,区别就是它会通知在该对象上等待的所有线程
     */
    public final native void notifyAll();
    /**
     * 让该线程进入WAITING状态,调用这个方法后,该线程会释放锁
     * 该方法还有其他重载方法,如果传入long参数,那么就是等待这么久
     */
    public final native void wait() throws InterruptedException;

总结一下,所谓的等待/通知,无非围绕这几个方法,当A线程调用了对象obj的wait方法进入等待状态,另一个线程B调用了obj的notify()或notifyAll()方法,线程A会受到通知后,从wait()方法返回,继续执行下面的逻辑.

Thread.join()

如果一个线程执行了这个方法,意义就是:当前线程等待thread线程停止后,才从join()返回并继续执行.例如:有两个线程A和B,A线程启动中,B调用了A.join(),那么B久得等A执行完,B才会继续执行,下面代码示例,给出了main线程和A线程的执行顺序:

public class ThreadJoin {

    public static void main(String[] args) throws InterruptedException {
        Thread main = Thread.currentThread();
        for (int i = 0; i < 5; i++) {
            Thread a = new Thread(new A(main));
            a.start();
        }
        //sleep更直观,给for循环充分的时间,否则直接往下执行了,看不出效果
        TimeUnit.SECONDS.sleep(5);
        System.out.println("我是main线程,你调用了我的join,就算我睡着了,你也得等我执行完你再执行");
    }

    private static class A implements Runnable {

        private Thread thread;

        public A(Thread thread) {
            this.thread = thread;
        }

        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
            }
            System.out.println("我是A线程,我调用了main线程的join方法");
        }
    }
}

可以复制下来,自己去执行感受一下~

线程的一些基础知识就先写到这里,不一定是全的,后面还会写线程的高级一点儿的东西!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值