• 语法:多线程



    面试中被问的最多的问题之一就是Java的并发与多线程了,一直想花点时间好好总结一下,今天终于下笔。


    一、几个概念:

    进程:简而言之就是进行中的程序
    线程:是“进程”中某个单一顺序的控制流,是程序中一条独立的执行路径
    单线程:整个进程中就只有一个单一的线程贯穿始终
    多线程:一个进程中含多个线程,cpu在不同的线程中快速地切换执行

    二、如何创建一个线程:
    1. 继承Thread类
      具体步骤:
      a. 继承Thread类
      b. 复写run()方法
      c. 用该类的对象.start()方法来创建一个线程并调用run()方法
      注意:不要直接调用run方法,如果直接调用该对象的run方法,跟普通的函数调用没有区别,不能达到开启多线程的目的

    2. 实现Runnable接口
      具体步骤:
      a. 实现Runnable接口
      b. 复写run()方法
      c. 以该类的对象为Thread类构造方法的参数建立一个Thread对象
      d. 调用start()方法

    示例程序:

    
    package wintervacation.multithreading;
    
    /**
     * Created by wangw on 2016/3/2.
     * 演示创建多线程的两种方式:
     * a.继承Thread类
     * b.实现Runnable接口
     */
    public class MultiThreadingDemo {
        public static void main(String[] args) {
            MyThread0 thread0 = new MyThread0();
            thread0.start();
    
            /*
            myThread1不能作为线程启动运行,必须包装在Thread对象thread1中
            但是thread1启动运行的线程是myThread1,执行的线程体也是myThread1的
             */
            MyThread1 myThread1 = new MyThread1();
            Thread thread1 = new Thread(myThread1);
            thread1.start();
    
            for(int i=0;i<500;i++) {
                System.out.println(Thread.currentThread().getName()+" is running-------"+i);
            }
            System.out.println(Thread.currentThread().getName()+"is over");
    
        }
    
        //方式1:继承Thread类
        static class MyThread0 extends Thread {
            @Override
            public void run() {
            for(int i=0;i<500;i++) {
                    System.out.println(Thread.currentThread().getName()+" is running-------"+i);
                }
                System.out.println(Thread.currentThread().getName()+"is over");
            }
        }
    
        //方式2:实现Runnable接口
        static class MyThread1 implements Runnable {
            @Override
            public void run() {
                for(int i=0;i<500;i++) {
                    System.out.println(Thread.currentThread().getName()+" is running-------"+i);
                }
                    System.out.println(Thread.currentThread().getName()+"is over");
            }
        }
    
    }
    

    运行结果:
    c1

    二者的区别:

    • Java只允许单继承,第一种方式要求继承Thread类没有其他父类,所以第二种方式的使用范围更广
    • 对于多个具有相同程序代码的线程处理同一资源的情况,第二种方式对CPU(线程)、程序的代码和数据进行了有效的分离,较好的体现了面向对象的思想。
      请看下面两个程序:
    // SellTicketSystem0.java 
    package wintervacation.multithreading;
    
    /**
     * Created by wangw on 2016/3/2.
     * 用继承Thread的方式模拟火车票售票系统,与实现Runnable接口的方式作比较
     * 假设四个系统同时出售某一车次的100张车票
     */
    public class SellTicketSystem0 {
    public static void main(String[] args) {
            MyThread myThread0 = new MyThread();
            myThread0.start();
            MyThread myThread1 = new MyThread();
            myThread1.start();
            MyThread myThread2 = new MyThread();
            myThread2.start();
            MyThread myThread3 = new MyThread();
            myThread3.start();
    
        }
    }
    class MyThread extends Thread {
        private int ticket = 100;
        @Override
        public void run() {
          while(ticket>0) {
                  System.out.println(this.getName()+" sell out ticket" + ticket--);
          }
        }
    }
    

    运行结果:
    c2
    可以看到每一张票被每个线程都卖了一遍,原因是每个线程都有它自己的100张票,显然不是我们想要的
    再看另一种方式:

    // SellTicketSystem1.java 
    package wintervacation.multithreading;
    
    /**
     * Created by wangw on 2016/3/2.
     * 用实现Runnable接口的方式模拟火车票售票系统,与继承Thread的方式作比较
     * 假设四个系统同时出售某一车次的100张车票
     */
    public class SellTicketSystem1 {
     public static void main(String[] args) {
            MyThread thread = new MyThread();
            Thread myThread0 = new Thread(thread);
            myThread0.start();
            Thread myThread1 = new Thread(thread);
            myThread1.start();
            Thread myThread2 = new Thread(thread);
            myThread2.start();
            Thread myThread3 = new Thread(thread);
            myThread3.start();
        }
    
     static class MyThread implements Runnable {
        private int ticket = 100;
        @Override
        public void run() {
          while(ticket>0) {
             System.out.println(Thread.currentThread().getName() + " sell out ticket" + ticket--);
          }
        }
      }
    }
    

    运行结果:
    c3
    可以看到,所有的线程访问同一个变量,在不考虑同步的情况下很好的模拟了火车票售票系统。

    三、线程状态的转换

    线程的生命周期图:

    Oracle官方给出的线程的状态分为四个:new, runnable, non-runnable 以及 terminated。
    此处我们采用更容易接受的五状态生命周期图。说明如下:

    • New
      新建状态,线程的实例已经被创建但是start方法尚未被调用
    • Runnable
      当该线程的start方法被调用,但是还没有被CPU调度选中时,线程处于Runnable状态
    • Running
      CPU调度选中,获得运行权
    • Non-Runnable (Blocked)
      线程阻塞状态,线程仍然存活,但是由于某些原因,导致它的运行受阻暂时不能运行
    • Terminated
      当线程的run方法退出时,线程进入Teminated状态
    四、线程的优先级和常用的方法
    • 优先级
      线程的优先级用1~10之间的一个整数表示,数值越大优先级越高,Thread类中定义了三个常量:
      最高优先级:Thread.MAX_PRIORITY————10
      最低优先级:Thread.MIN_PRIORITY————1
      默认优先级:Thread.NORM_PRIORITY———–5
    • 常用方法
      public void run(): is used to perform action for a thread.
      public void start(): starts the execution of the thread.JVM calls the run() method on the thread.
      public void sleep(long miliseconds): Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds.
      public void join(): waits for a thread to die.
      public void join(long miliseconds): waits for a thread to die for the specified miliseconds.
      public int getPriority(): returns the priority of the thread.
      public int setPriority(int priority): changes the priority of the thread.
      public String getName(): returns the name of the thread.
      public void setName(String name): changes the name of the thread.
      public Thread currentThread(): returns the reference of currently executing thread.
      public int getId(): returns the id of the thread.
      public Thread.State getState(): returns the state of the thread.
      public boolean isAlive(): tests if the thread is alive.
      public void yield(): causes the currently executing thread object to temporarily pause and allow other threads to execute.
      public void suspend(): is used to suspend the thread(depricated).
      public void resume(): is used to resume the suspended thread(depricated).
      public void stop(): is used to stop the thread(depricated).
      public boolean isDaemon(): tests if the thread is a daemon thread.
      public void setDaemon(boolean b): marks the thread as daemon or user thread.
      public void interrupt(): interrupts the thread.
      public boolean isInterrupted(): tests if the thread has been interrupted.
      public static boolean interrupted(): tests if the current thread has been interrupted.

    示例程序:

    // ThreadPriority.java
    package wintervacation.multithreading;
    
    /**
     * Created by wangw on 2016/3/2. 用于演示Thread的优先级,高优先级线程的执行优先于低优先级线程
     */
    public class ThreadPriority {
        public static void main(String[] args) throws InterruptedException {
            System.out.println("最高优先级:" + Thread.MAX_PRIORITY);
            System.out.println("最低优先级:" + Thread.MIN_PRIORITY);
            System.out.println("默认优先级:" + Thread.NORM_PRIORITY);
    
            MyThread thread0 = new MyThread();
            thread0.setPriority(Thread.MAX_PRIORITY);
            thread0.start();
    
            MyThread thread1 = new MyThread();
            thread1.setPriority(Thread.MIN_PRIORITY);
            thread1.start();
    
            MyThread thread2 = new MyThread();
            thread2.setPriority(Thread.NORM_PRIORITY);
            thread2.start();
    
            // join方法:当前线程执行某一线程的join方法之后,当前线程等到该线程执行结束之后才能执行
            // thread1.join();
            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().getName() + " is running-------" + i);
            }
            System.out.println(Thread.currentThread().getName() + "is over");
    
        }
    
        static class MyThread extends Thread {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println(Thread.currentThread().getName() + " is running-------" + i);
                大专栏  语法:多线程ss="token punctuation">}
                System.out.println(Thread.currentThread().getName() + "is over");
            }
        }
    }
    
    五、同步问题

    临界资源:同一时刻只允许一个线程访问的资源
    临界区:处理临界资源的代码
    涉及到临界资源的问题需要用到同步机制,否则可能会出现错误或会导致不安全,上面的卖票程序就存在这样的问题,为了使效果更加明显,我们在单个线程卖票之前让它睡眠100ms:

    static class MyThread implements Runnable {
        private int ticket = 100;
    
        @Override
        public void run() {
            while (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " sell out ticket" + ticket--);
            }
        }
    }
    

    运行结果:
    result
    我们看到Thread-0,Thread-2和Thread-1分别卖出了代号为0,-1,-2的票,这显然是错误的
    原因是线程3检测到ticket=1时,进入睡眠,此时线程0检测ticket仍然为1,也进入临界区,睡眠,同理,线程2,1依次进入临界区,睡眠。
    100ms后,Thread-3恢复运行,卖出标号为1的票,ticket减一变为0,线程0,2,1依次恢复运行并执行卖票和ticket–的动作,便出现了图示的结果。
    为了保护共享数据的完整性,JAVA语言引入了互斥锁的概念。
    互斥锁:Java中的每一个对象都有且仅有一个互斥锁,对于加锁的临界区,只有拿到该锁的线程可以访问,Java使用synchronized关键字给对象家锁。
    同步方法:使用synchronized修饰的方法,开发者不用指定加锁对象,JVM默认给this对象加锁
    同步代码块:使用synchronized修饰的代码块,需要指定加锁对象
    改进后的火车票售票系统:
    <方式1>

    static class MyThread implements Runnable {
        private int ticket = 100;
    
        @Override
        public void run() {
            while (true) {
                synchronized ("haha") {
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + " sell out ticket" + ticket--);
                    } else
                        break;
                }
            }
        }
    }
    

    <方式2>

    static class MyThread implements Runnable {
        private int ticket = 100;
    
        @Override
        public void run() {
            while (true) {
                sell();
                if (ticket <= 0)
                    break;
            }
        }
    
        public synchronized void sell() {
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " sell out ticket" + ticket--);
            }
        }
    }
    

    运行结果:
    result1

    六、死锁问题

    死锁:并发运行的多个线程彼此等待对方占有的资源、都无法运行的状态
    synchronized关键字要慎用,不然很容易出现死锁,下面是一个例子:

    // DeadLockDemo.java
    package wintervacation.multithreading;
    
    /**
     * Created by wangw on 2016/3/2. 死锁的例子
     */
    public class DeadLockDemo {
        public static void main(String[] args) throws InterruptedException {
            StringBuffer sb = new StringBuffer("haha");
            MyThread thread = new MyThread(sb);
            thread.start();
    
            synchronized (sb) {
                thread.join();
                System.out.println(Thread.currentThread().getName());
            }
        }
    
        static class MyThread extends Thread {
            private StringBuffer sb = new StringBuffer();
    
            MyThread(StringBuffer sb) {
                this.sb = sb;
            }
    
            @Override
            public void run() {
                synchronized (sb) {
                    System.out.println(this.getName());
                }
            }
    
        }
    }
    

    主线程中调用了thread的join方法,等待thread线程执行完毕,而thread线程需要sb锁,sb锁被main线程占用,故也无法执行,发生死锁。
    死锁没有好的解决方式,应该尽量避免。

    七、线程之间的通信

    多线程程序之间如果没有通信,相互孤立,就失去了多线程的意义了。
    通信问题的经典例子:
    生产者消费者的同步问题(问题描述请自行上网搜索)
    Java多线程实现:

    // ConsumerAndProductor.java
    package wintervacation.multithreading;
    
    /**
     * Created by wangw on 2016/3/2. 消费者和生产者的例子,用于说明线程之间的通信 把生产者和消费者作为两个线程
     * 仓库作为一个类,有装入生产者生产的商品和向消费者提供商品两个方法
     */
    public class ConsumerAndProductor {
        public static void main(String[] args) {
            Repo repo = new Repo();
            ComsumerThread comsumerThread = new ComsumerThread(repo);
            comsumerThread.start();
            ProductorThread productorThread = new ProductorThread(repo);
            productorThread.start();
        }
    }
    
    class Repo {
        // 仓库可以容纳6件商品
        char[] data = new char[6];
        int index = 0;
    
        public synchronized void in(char c) {
            if (index == 6) {
                try {
                    this.wait();
                    System.out.println("-----" + this);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            data[index++] = c;
            System.out.println("生产了产品" + c);
            this.notify();
        }
    
        public synchronized char out() {
            if (index == 0) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            index--;
            System.out.println("消费了产品" + data[index]);
            this.notify();
            return data[index];
        }
    }
    
    class ComsumerThread extends Thread {
        Repo repo;
    
        ComsumerThread(Repo repo) {
            this.repo = repo;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                char c = (char) (Math.random() * 26 + 'A');
                repo.in(c);
                try {
                    Thread.sleep((int) (Math.random() * 10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    class ProductorThread extends Thread {
        Repo repo;
    
        ProductorThread(Repo repo) {
            this.repo = repo;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                repo.out();
                try {
                    Thread.sleep((int) (Math.random() * 100));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    
  • 相关阅读:
    2020软件工程个人作业06——软件工程实践总结作业
    git上传文件夹内容
    git常用命令(部分)
    java命令行输入参数
    2020软件工程作业05
    软件工程——问题清单
    2020软件工程作业04
    2020软件工程作业03
    2020软件工程作业02
    问题清单
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12327397.html
Copyright © 2020-2023  润新知