多线程之并发基础(三)

作者: Real_man | 来源:发表于2018-04-06 08:59 被阅读55次

    线程是轻量级的进程,进程可以说是线程的容器。线程是程序执行的最小单位。使用多线程而不是使用多进程进行并发程序的设计,因为线程的切换和调度成本远远小于进程。

    与文无关

    本文知识点目录:

    • 线程的状态
    • 线程的常见操作
      • Daemon线程
      • 线程优先级
      • wait与notify

    线程的状态

    在Java程序中,线程有如下状态

    • NEW(新建): 新的线程刚刚创建,还没有启动。

    • Runnable(就绪):线程已经调用了start方法,正在被JVM执行的时候,也有可能正在等待某个操作系统的资源。

    • Blocked(阻塞):有其它线程占着锁不释放,当前线程阻塞与锁

    • Waiting(等待):表示当前线程需要等待其它线程做出一些特定动作(通知或中断),当调用以下方法时会进入此状态。
      Object.wait()没有时间参数
      Thread.join()没有超时参数
      LockSupport.park方法

    • Timed Waiting(等待并计时):指定时间waiting,一边等,一边计时,一般调用如下方法可以j进入此状态。
      Thread.sleep
      Object.wait()带有参数
      Thread.join ()带有超时参数
      LockSupport.parkNanos
      LockSupport.parkUntil

    • Terminated(终止) 线程完成了它的操作,也有可能错误的执行而终止。

    线程状态图 线程状态图

    线程操作

    实现多线程

    我们想要实现多线程常用的有两种方法

    • 实现Runnable接口
    • 继承自Thread线程

    java只能单继承,因此如果是采用继承Thread的方法,那么在以后进行代码重构的时候可能会遇到问题,因为你无法继承别的类了。

    如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

    注意这是在多线程情况下, 每个人运行的结果可能都不一样。要想保住线程安全性,还需要额外的操作。

    // 案例代码,继承线程变量无法共享
    public class ThreadOrRunnable extends Thread {
        private int count=5;
        private String name;
        public ThreadOrRunnable(String name) {
            this.name=name;
        }
    
        @Override
        public void run() {
    //        super.run();
            for (int i = 0; i < 5; i++) {
                count--;
                System.out.println(name +  "运行 count=" + count);
            }
        }
    
        public static void main(String[] args) {
            ThreadOrRunnable threadOrRunnable = new ThreadOrRunnable("A");
            ThreadOrRunnable threadOrRunnableB = new ThreadOrRunnable("B");
            threadOrRunnable.start();
            threadOrRunnableB.start();
            
        }
    }
    //运行结果:
    A运行 count=4
    A运行 count=3
    A运行 count=2
    A运行 count=1
    B运行 count=4
    A运行 count=0
    B运行 count=3
    B运行 count=2
    B运行 count=1
    B运行 count=0
    
    // 案例 继承自Runnable可以共享count变量
    public class RunnableTest implements Runnable{
        private int count=15;
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                count--;
                System.out.println(Thread.currentThread().getName() + "运行: count=" + count);
            }
        }
    
        public static void main(String[] args) {
            RunnableTest runnableTest = new RunnableTest();
            new Thread(runnableTest,"A").start();
            new Thread(runnableTest,"B").start();
        }
    }
    
    // 运行结果
    B运行: count=13
    A运行: count=13
    B运行: count=12
    A运行: count=11
    B运行: count=10
    A运行: count=9
    A运行: count=7
    A运行: count=6
    B运行: count=8
    B运行: count=5
    
    Daemon线程

    Daemon线程是一种支持型线程,在后台调度及支持性工作。它有两个点需要注意:

    • 必须在程序调用start方法之前调用Thread.setDaemon(true)方法才能设置为daemon线程,如果start()之后设置,会报IllegalThreadStateException异常
    • 在所有前台线程执行完毕的时候,daemon线程会自动销毁。
    Thread thread = new Thread();
    thread.setDaemon(true);
    thread.start();
    
    线程优先级

    Java中的线程可以有自己的优先级,优先级高的线程在竞争资源时更可能抢占资源,但是这只是一个概览问题,并不是谁的优先级高,谁就一定先执行。

    Thread thread = new Thread();
     // MIN_PRIORITY = 1;
     // NORM_PRIORITY = 5;
     // MAX_PRIORITY = 10;
    thread.setPriority(int newPriority) 
    thread.start();
    
    wait与notify

    wait和notify方法不是在Thread类中的,而是在Object类中,意味着任何对象都可以调用这两个方法。

    当一个线程A调用了obj.wait()方法,那么线程A就会停止继续执行,而是转为waiting状态。一直都其它线程调用obj.notify()方法为止。

    notify与notifyAll

    注意:

    • notify方法是从等待队列中的线程随机选择一个,我们无法保证它唤醒的是那一个。notifyAll()方法会唤醒所有等待的线程。
    • object.wait()方法必须包含在synchronized语句中,wait或notify都需要首先获得目标对象的监视器。

    Object.wait和Thread.sleep方法都可以让线程等待若干时间,它们哟徐诶区别。

    • wait可以被唤醒,sleep不可以
    • wait会释放目标对象的锁,而sleep不释放任何锁。
    public class WaitNotify {
        static volatile boolean flag = true;
        static Object lock = new Object();
    
        static class Wait implements Runnable{
    
            @Override
            public void run() {
                synchronized (lock){
                    while (flag){
                        System.out.println(Thread.currentThread().getName() +  " 时间: " + System.currentTimeMillis());
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //条件满足的时候完成了工作
                    System.out.println("flag=" + flag + "任务完成");
                }
            }
        }
    
        static class Notify implements Runnable{
    
            @Override
            public void run() {
                synchronized (lock){
                    System.out.println(Thread.currentThread().getName() + " 持有锁");
                    lock.notifyAll();
                    flag = false;
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
                synchronized (lock){
                    System.out.println("再次持有锁");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    
        public static void main(String[] args) throws InterruptedException {
            Thread waitThread = new Thread(new Wait(),"waitThread");
            waitThread.start();
            TimeUnit.SECONDS.sleep(1);
            Thread notifyThread = new Thread(new Notify(),"notifyThread");
            notifyThread.start();
        }
    }
    

    //运行结果,调用notify之后,wait状态会变为blocked状态,然后再进入Runnable状态。


    运行结果

    Wait与notify可以提炼出等待/通知的经典范式,氛围两部分:
    等待方规则:

    1. 获取对象的锁
    2. 如果条件不满足,那么调用wait方法,被通知后仍要检查条件
    3. 条件满足则执行相应的逻辑

    通知方规则:

    1. 获得对象的锁
    2. 改变条件
    3. 通知等待对象上的线程。

    问题

    关于Object.wait与notify,最后再留下来一个问题:
    为什么wait与notify方法要在Object类中调用而不是在Thread中?

    最后

    这次提到了一些Thread的基本概念,线程的状态切换,线程的两个属性,最后提了一下wait与notify方法。下次讲一下线程的常见用法。

    参考

    相关文章

      网友评论

      本文标题:多线程之并发基础(三)

      本文链接:https://www.haomeiwen.com/subject/ywpjhftx.html