Java 多线程操作

来源:互联网 发布:cool edit for mac版 编辑:程序博客网 时间:2024/05/20 00:15

以前一听到多线程操作就感到好腻害好腻害的,如果你现在也是这种情况或许这篇文章能够帮助到你。

这里写图片描述

1、什么是多线程?

先了解两个概念
进程:正在运行的程序,是系统进行资源分配和调用的独立单位,有自己的内存空间和系统资源。
线程:是进程中的单个顺序控制流,是一条执行路径,线程是应用程序中执行的基本单元。

某位大神的总结:进程就相当于工厂,线程就是工厂里的流水线,线程不能独立存在,必须存在于进程中。

多进程:系统中同时存在多个并行的进程,则称为多进程。可通过电脑任务管理器查看正在运行的进程,比如用电脑聊QQ的同时看电影,就是多进程的体现。
多线程:线程是进程中的单个顺序控制流,是一条执行路径,一个进程如果有多条执行路径,则称为多线程。比如给某人聊QQ的同时还可以接收到其他人的消息。

2、多线程实现方式

1.继承Thread类

Java提供了Thread类,让我们对线程进行操作。

    class MyThread extends Thread {    @Override    public void run() {        /* 多线程执行的逻辑 */        super.run();    }}    //开启线程    new MyThread().start();

2.实现Runable接口

   class MyRunnable implements Runnable {    @Override    public void run() {        /* 多线程执行的逻辑 */    }}  //将实现了Runnable接口的类,以参数的形式传递给Thread类  new Thread(new MyRunnable()).start();

3.实现Callable接口

这是一种有返回值的线程,但是必须通过线程池使用。

    public class Test {    public static void main(String[] args) {        // 生成线程池对象        ExecutorService pool = Executors.newCachedThreadPool();        // 提交Callable线程        pool.submit(new MyCallable());    }}   class MyCallable implements Callable<String> {    // 通过指定范型 可以通过线程方法返回该类型的数据 而其他两种实现方式都没有返回值    @Override    public String call() throws Exception {        /* 多线程执行的逻辑 */        return "hello";    }}

3、线程调度

分时调度模型 :所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
抢占式调度模型:抢占CPU使用权,其中优先级高的线程抢到CPU执行权的概率会越大,存在很大随机性。Java采用的是抢占式调度模型。

查看抢占式调度效果:

    public class Test {    public static void main(String[] args) throws InterruptedException {        // 生成线程对象        MyThread thread = new MyThread();        MyThread thread2 = new MyThread();        // 启动线程        thread.start();        thread2.start();        //第一次输出      //第二次输出        //Thread-0:0     //Thread-1:0        //Thread-1:0     //Thread-0:0        //Thread-1:1     //Thread-1:1        //Thread-1:2     //Thread-0:1        //Thread-0:1     //Thread-0:2        //Thread-0:2     //Thread-1:2    }}   // 线程类   class MyThread extends Thread {    @Override    public void run() {        for (int i = 0; i <3; i++) {            // 输出线程名和i的值            System.out.println(this.getName() + ":" + i);        }        super.run();    }}

通过输出可以看出线程抢占是随机的无规律可言(不设置优先级的情况下)。

如何设置线程优先级呢?

        //Thread提供了相关方法        // 设置线程的优先级        void setPriority(int newPriority)        // 返回线程的优先级        int getPriority()        // 生成线程对象        MyThread thread = new MyThread();        // 获取默认线程优先级        int priority = thread.getPriority();        System.out.println(priority);// 5        // 设置线程优先级        thread.setPriority(6);        // 输出最小线程优先级        System.out.println(thread.MIN_PRIORITY);// 1        // 输出最大线程优先级        System.out.println(thread.MAX_PRIORITY);// 10

可以看出线程的优先级的是1-10

4、线程控制

Thread类提供了相关方法对线程进行控制。

1.线程休眠sleep

    // 生成线程对象    MyThread thread = new MyThread();    //线程休眠3000毫秒    thread.sleep(3000);    //获取当前线程对象并设置其休眠200毫秒2000纳秒    Thread.currentThread().sleep(200, 2000);

2.线程加入join

        public static void main(String[] args) throws InterruptedException {        // 生成线程对象        MyThread thread = new MyThread("线程一");        MyThread thread2 = new MyThread("线程二");        // 启动线程        thread.start();        // 线程加入 当调用了线程加入了之后在该放后启动的线程会等这个线程执行完 在抢占CPU执行权        thread.join();        thread2.start();        //未添加前输出        //      线程一:0        //      线程一:1        //      线程二:0        //      线程一:2        //      线程二:1        //      线程二:2        //添加后输出        //      线程一:0        //      线程一:1        //      线程一:2        //      线程二:0        //      线程二:1        //      线程二:2    }}    // 线程类    class MyThread extends Thread {    public MyThread(String name) {        // 设置线程名        this.setName(name);    }    @Override    public void run() {        for (int i = 0; i < 3; i++) {            // 输出线程名和i的值            System.out.println(this.getName() + ":" + i);        }        super.run();    }}

3.线程礼让yield

        // 生成线程对象        MyThread thread = new MyThread("线程一");        MyThread thread2 = new MyThread("线程二");        //暂停当前正在执行的线程对象,并执行其他线程,让线程执行更和谐        thread.yield();        // 启动线程        thread.start();        thread2.start();        //输出        //      线程一:0        //      线程二:0        //      线程一:1        //      线程二:1        //      线程二:2        //      线程一:2

4.线程守护setDaemon

        // 生成线程对象        MyThread thread = new MyThread("线程一");        MyThread thread2 = new MyThread("线程二");        //将该线程标记为守护线程或用户线程  在启动线程前调用        //设置thread为守护线程   即当线程thread2执行完成后,如果thread未执行完则不再继续执行        //因为线程具有随机性,所以存在thread在thread2执行完的情况        thread.setDaemon(true);        // 启动线程        thread.start();        thread2.start();

5.线程中断stop、interrupt

        // 生成线程对象        MyThread thread = new MyThread("线程一");        MyThread thread2 = new MyThread("线程二");        // 启动线程        thread.start();        thread2.start();        // 立即终止某个线程,没有任何反馈,存在不安全性        //thread.stop();        // 中断线程        thread2.interrupt();        //输出:        //线程二线程终止!!!        //线程二------------        // 线程类        class MyThread extends Thread {            public MyThread(String name) {                // 设置线程名                this.setName(name);            }            @SuppressWarnings("static-access")            @Override            public void run() {                try {                    //当前线程休眠3秒                    this.sleep(3000);                } catch (InterruptedException e) {                    System.out.println(this.getName()+"线程终止!!!");                }                System.out.println(this.getName()+"------------");                super.run();            }        }

stop()方法会立刻终止线程并没有任何反馈,而interrupt()会有后续的输出。

5、线程生命周期

图示:

这里写图片描述

6、线程安全问题

1.线程安全问题

模拟卖票情况:两个线程对同一数据进行操作

    public class Test {    public static void main(String[] args) throws InterruptedException {        MyRunnable runnable = new MyRunnable();        new Thread(runnable, "卖票口一:").start();        new Thread(runnable, "卖票口二:").start();    }}    // 线程类    class MyRunnable implements Runnable {        private int ticket = 100;        @Override        public void run() {            // 多次执行            while (true) {                if (ticket > 0) {                    try {                        // 线程休眠模拟网络延迟                        Thread.sleep(300);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticket-- + "张票");                }            }        }    }

部分输出情况:

这里写图片描述

这里写图片描述

1.出现同票情况?根据输出可以推断,当ticket为40的时候,卖票窗口一线程抢到CPU执行权,并执行,但是执行run方法输出语句的时候, ticket–只执行完赋值操作并未执行–操作的时候,CPU执行权被卖票窗口二抢到,输出卖票窗口二:正在出售第40张票,然后卖票窗口一又抢到了CPU执行权输出卖票窗口一:正在出售第40张票。

2.出现0票情况?根据输出可以推断,当ticket为1的时候,卖票窗口一线程抢到CPU执行权,并执行,但是执行run方法未执行到输出语句的时候,CPU执行权并执行完run方法输出卖票窗口二:正在出售第1张票,然后卖票窗口一又抢到CPU执行权,此刻ticket已经为0了, 所以输出卖票窗口一:正在出售第0张票。

注:一般多线程环境、存在共享数据、多条语句操作共享数据的情况下会出现线程安全问题。那么如何解决呢?就用到了线程同步。

2.2.线程同步

1.同步代码块

 class MyRunnable implements Runnable {    private int ticket = 100;    @Override    public void run() {        // 多次执行        while (true) {            // 同步代码块(添加任意对象)            synchronized (this) {                if (ticket > 0) {                    try {                        // 线程休眠模拟网络延迟                        Thread.sleep(300);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(Thread.currentThread().getName()                            + "正在出售第" + ticket-- + "张票");                }            }        }    }}

注:同步代码块格式synchronized(对象){同步代码;};同步可以解决安全问题的根本原因就在那个对象上,该对象就是锁,多个线程操作同一代码块,一定要保证那个锁对象相同。

2.同步方法

就是在方法上同步关键字。

  class MyRunnable implements Runnable {        private int ticket = 100;        @Override        public void run() {            // 多次执行            while (true) {                sellTicket();            }        }        /**         * 同步方法         */        private synchronized void sellTicket() {            if (ticket > 0) {                try {                    // 线程休眠模拟网络延迟                    Thread.sleep(300);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(Thread.currentThread().getName() + "正在出售第"                        + ticket-- + "张票");            }        }        /**         * 同步静态方法         */        private static synchronized void sellTicket() {            if (ticket > 0) {                try {                    // 线程休眠模拟网络延迟                    Thread.sleep(300);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticket-- + "张票");            }        }

注:同步方法的锁对象其实是当前对象this,同步静态方法的锁对象是当前类的字节码文件。

3.Lock锁的使用

JDK5提供了Lock锁接口,提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。

         // 获取锁         void lock()         // 释放锁         void unlock()
class MyRunnable implements Runnable {    private int ticket = 100;    //实现类对象实例化锁接口    Lock lock = new ReentrantLock();    @Override    public void run() {        // 多次执行        while (true) {            // 获取锁            lock.lock();            if (ticket > 0) {                try {                    // 线程休眠模拟网络延迟                    Thread.sleep(000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(Thread.currentThread().getName() + "正在出售第"                        + ticket-- + "张票");            }            // 释放锁            lock.unlock();        }    }

线程同步总结:

7、线程死锁问题

死锁就是线程间因相互等待对方资源,而不能继续执行的情况。线程同步嵌套就非常容易产生死锁问题。

修改之前的代码:

 class MyRunnable implements Runnable {    private int ticket = 100;    // 锁对象1    private Object object1 = new Object();    // 锁对象2    private Object object2 = new Object();    @Override    public void run() {        // 多次执行        while (true) {            if (ticket > 0) {                if (ticket % 2 == 0) {                    synchronized (object1) {// 这里出现线程死锁                        synchronized (object2) {                            System.out.println(Thread.currentThread().getName()                                    + "正在出售第" + ticket-- + "张票");                        }                    }                } else {                    synchronized (object2) {// 这里出现线程死锁                        synchronized (object1) {                            System.out.println(Thread.currentThread().getName()                                    + "正在出售第" + ticket-- + "张票");                        }                    }                }            }        }    }

该程序因为进行了同步嵌套所以会线程死锁问题,会出现输出不全的情况。

注:那么如何解决死锁问题呢?给资源排序,在所有的线程中,决定次序并始终遵照这个次序获取锁。

8、线程间通信问题

1.生产消费模式

多线程间除了线程安全问题外,还存在线程间通信问题。比如线程A执行完,操作了某些数据,而线程B需要这些数据做某些操作,这时线程A需要通知线程B已经有数据了,然后线程B做某些操作,当没有数据时线程B需要反馈给线程A。线程A和线程B之间就冥冥中存在了联系,这就是生成消费模式。
这个类似于Android中的Work线程和UI线程通过Handler通信一样,而java则提供了等待唤醒机制进行线程通信。

2.等待唤醒机制

Object类提供了notify、wait方法,对线程进行唤醒和等待。

    // 唤醒在此对象监视器上等待的单个线程    void notify()    // 唤醒在此对象监视器上等待的所有线程    void notifyAll()    // 在其他线程调用此对象的notify()方法或 notifyAll()方法前,导致当前线程等待    void wait()    // 在其他线程调用此对象的notify()方法或 notifyAll()方法,或者超过指定的时间量前,导致当前线程等待    void wait(long timeout)
    public class User {    String name;    int age;    boolean b = false;// 判断是否有数据    // 设置数据    public synchronized void setUser(String name, int age)            throws InterruptedException {        if (this.b) {            //如果存在数据就等待            // 线程等待            this.wait();        } else {            //没有数据则设置数据并唤醒某个线程            setName(name);            setAge(age);            this.b = true;            // 唤醒等待的单个线程            this.notify();        }    }    // 获取数据    public synchronized void getUser() throws InterruptedException {        if (this.b) {            // 相当于消费数据            System.out.println(getName() + ":" + getAge());            // 消费后相当资源消失            this.b = false;            // 唤醒线程            this.notify();        } else {            // 线程等待            this.wait();        }    }    /** 实现get set方法 **/}    public class Test {        public static void main(String[] args) throws InterruptedException {            User user = new User();            SetUserThread setUserThread = new SetUserThread(user);            GetUserThread getUserThread = new GetUserThread(user);            getUserThread.start();            setUserThread.start();        }    }    class GetUserThread extends Thread {        private User user;        public GetUserThread(User user) {            super();            this.user = user;        }        @Override        public void run() {            while (true) {                try {                    user.getUser();                } catch (InterruptedException e) {                    e.printStackTrace();                }                super.run();            }        }    }    class SetUserThread extends Thread {        private User user;        public SetUserThread(User user) {            super();            this.user = user;        }        @Override        public void run() {            while (true) {                try {                    user.setUser("Hello", 18);                } catch (InterruptedException e) {                    e.printStackTrace();                }                super.run();            }        }    }

9、线程组的使用

Java提供了线程组ThreadGroup对线程进行统一管理和调度。
ThreadGroup:线程组就是一个线程的集合。

        // 生成线程组对象,并设置名字        ThreadGroup threadGroup = new ThreadGroup("线程组一");        // 使用Thread的构造设置线程归宿的线程组        new Thread(threadGroup, new MyRunnable(), "线程一").start();        new Thread(threadGroup, new MyRunnable(), "线程二").start();        new Thread(threadGroup, new MyRunnable(), "线程三");        // 获取线程组名字        System.out.println(threadGroup.getName());// 线程组一        // 获取线程组活动线程的估计数        Thread[] threads = new Thread[threadGroup.activeCount()];        // 线程组中的所有活动线程复制到指定数组中        threadGroup.enumerate(threads);        System.out.println(threads.length);//运行多次输出 0 1 2        //通过输出可以看到 threadGroup.activeCount()结果所固有的不精确特性        // 中断线程组中所有线程        threadGroup.interrupt();

10、线程池的使用

线程池简述:开启一条线程是非常浪费资源的,因为它涉及到要与操作系统进行交互;因此JDK5之后Java提供了线程池让我们提高性能,线程池里的线程执行完后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

相关对象
Executors:创建线程池的工厂类。

    创建方法:    // 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们    static ExecutorService newCachedThreadPool()    // 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程    static ExecutorService newFixedThreadPool(int nThreads)    // 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的ThreadFactory创建新线程    static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)    // 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的ThreadFactory创建新线程    static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)    // 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行    static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)    // 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行    static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)    //创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程    public static ExecutorService newSingleThreadExecutor()

ExecutorService:线程池管理接口,提供了线程的操作方法。

    // 启动一次顺序关闭,执行以前提交的任务,但不接受新任务    void shutdown()    // 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表    List<Runnable> shutdownNow()    // 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future    <T> Future<T> submit(Callable<T> task)    // 提交一个 Runnable任务用于执行,并返回一个表示该任务的 Future    Future<?> submit(Runnable task)    // 提交一个 Runnable任务用于执行,并返回一个表示该任务的 Future    <T> Future<T> submit(Runnable task, T result)
        ExecutorService threadpool= Executors.newCachedThreadPool();        //提交任务        threadpool.submit(new Runnable() {            @Override            public void run() {                /**线程中执行逻辑**/            }        });        //这是实现线程的第三种方式        threadpool.submit(new Callable<String>() {            @Override            public String call() throws Exception {                /**线程中执行逻辑**/                return null;            }        });        //停止任务        threadpool.shutdown();

注:线程池可以很大程度上提高性能,如存在多线程环境建议使用 。

更多方法查看API

1 0