zoukankan      html  css  js  c++  java
  • Java 多线程的使用

    多线程的使用

    一,继承Thread类

    构造方法:
    方法名 说明
    Thread() 分配新的Thread对象。
    Thread(String name) 分配新的Thread对象,将指定的name作为其线程名称。
    常用方法:
    方法名 说明
    void start() 使该线程开始执行:Java虚拟机调用该线程的run方法。
    void run() 该线程要执行的操作。
    static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
    创建线程的步骤:
    1. 定义一个类继承Thread类。
    2. 重写run方法。
    3. 创建子类对象,就是创建线程对象。
    4. 调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法。
    public class Demo {
        public static void main(String[] args) {
            //创建自定义线程对象
            MyThread mt = new MyThread("新的线程!");
            //开启新线程
            mt.start();
            //在主方法中执行for循环
            for (int i = 0; i < 10; i++) {
                System.out.println("main线程!" + i);
            }
        }
    }
    
    //线程类
    class MyThread extends Thread {
        //定义指定线程名称的构造方法
        public MyThread(String name) {
            //调用父类的String参数的构造方法,指定线程的名称
            super(name);
        }
    
        /**
         * 重写run方法,完成该线程执行的逻辑
         */
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(getName() + ":正在执行!" + i);
            }
        }
    }
    

    线程对象调用 run方法和调用start方法区别?线程对象调用run方法不开启线程。仅是对象调用方法。线程对象调用start开启线程,并让jvm调用run方法在开启的线程中执行。

    获取线程名称
    方法名 说明
    Thread.currentThread() 获取当前线程对象
    Thread.currentThread().getName(); 获取当前线程对象的名称

    二,实现Runnable接口

    接口中的方法:
    方法名 说明
    void run() 使用实现接口Runnable的对象创建一个线程时,启动该线程将导致在独立执行中的线程调用对象的run方法。
    创建线程的的步骤:
    1. 定义类实现Runnable接口。
    2. 覆盖接口中的Run方法。
    3. 创建Thread类的对象。
    4. 将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
    5. 调用Thread类的start方法开启线程。
    public class Demo {
        public static void main(String[] args) {
    //创建线程执行目标类对象
            Runnable runn = new MyRunnable();
    //将Runnable接口的子类对象作为参数传递给Thread类的构造函数
            Thread thread = new Thread(runn);
            Thread thread2 = new Thread(runn);
    //开启线程
            thread.start();
            thread2.start();
            for (int i = 0; i < 10; i++) {
                System.out.println("main线程:正在执行!" + i);
            }
        }
    }
    
    //自定义线程执行任务类
    class MyRunnable implements Runnable {
        //定义线程要执行的run方法逻辑
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("我的线程:正在执行!" + i);
            }
        }
    }
    

    线程池的使用方法

    线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

    我们详细的解释一下为什么要使用线程池?

    在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。

    线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

    一,使用Runnable接口

    通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。

    Executors:线程池创建工厂类
    方法名 说明
    public static ExecutorService newFixedThreadPool(int nThreads) 返回线程池对象
    ExecutorService:线程池类
    方法名 说明
    Future<?> submit(Runnable task) 获取线程池中的某一个线程对象,并执行
    Future接口 用来记录线程任务执行完毕后产生的结果。线程池创建与使用
    使用线程池中线程对象的步骤:
    1. 创建线程池对象
    2. 创建Runnable接口子类对象
    3. 提交Runnable接口子类对象
    4. 关闭线程池
    package thread;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class ThreadPoolDemo {
        public static void main(String[] args) {
    		//创建线程池对象
            ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
    		//创建Runnable实例对象
            MyRunnable r = new MyRunnable();
    
    		//自己创建线程对象的方式
    		//Thread t = new Thread(r);
    		//t.start(); ---> 调用MyRunnable中的run()
    
    		//从线程池中获取线程对象,然后调用MyRunnable中的run()
            service.submit(r);
    		//再获取个线程对象,调用MyRunnable中的run()
            service.submit(r);
            service.submit(r);
    		//注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。将使用完的线程又归还到了线程池中
    
    		//关闭线程池
    		//service.shutdown();
        }
    }
    
    //Runnable接口实现类
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("我要一个教练");
    
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("教练来了: " + Thread.currentThread().getName());
            System.out.println("教我游泳,交完后,教练回到了游泳池");
        }
    }
    

    二,使用Callable接口

    Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。

    ExecutorService:线程池类

    方法名 说明
    Future submit(Callable task) 获取线程池中的某一个线程对象,并执行线程中的call()方法
    Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
    1. 使用线程池中线程对象的步骤:
    2. 创建线程池对象
    3. 创建Callable接口子类对象
    4. 提交Callable接口子类对象
    5. 关闭线程池
    package cn.last.demo;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class SpringTestApplicationTests {
        public static void main(String[] args) {
            //创建线程池对象
            ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
            //创建Callable对象
            MyCallable c = new MyCallable();
    
            //从线程池中获取线程对象,然后调用MyRunnable中的run()
            service.submit(c);
    
            //再获取个教练
            service.submit(c);
            service.submit(c);
            //注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。将使用完的线程又归还到了线程池中
    
            //关闭线程池
            //service.shutdown();
        }
    }
    
    //Callable接口实现类,call方法可抛出异常、返回线程任务执行完毕后的结果
    class MyCallable implements Callable<Object> {
        @Override
        public Object call() throws Exception {
            System.out.println("我要一个教练:call");
            Thread.sleep(2000);
            System.out.println("教练来了: " + Thread.currentThread().getName());
            System.out.println("教我游泳,交完后,教练回到了游泳池");
            return null;
        }
    }
    

    三,线程池练习:返回两个数相加的结果

    要求:通过线程池中的线程对象,使用Callable接口完成两个数求和操作

    • Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
    • V get() 获取Future对象中封装的数据结果
    package cn.last.demo;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class SpringTestApplicationTests {
        public static void main(String[] args) throws InterruptedException, ExecutionException {
            //创建线程池对象
            ExecutorService threadPool = Executors.newFixedThreadPool(2);
    
            //创建一个Callable接口子类对象
            //MyCallable c = new MyCallable();
            MyCallable c = new MyCallable(100, 200);
            MyCallable c2 = new MyCallable(10, 20);
    
            //获取线程池中的线程,调用Callable接口子类对象中的call()方法, 完成求和操作
            //<Integer> Future<Integer> submit(Callable<Integer> task)
            // Future 结果对象
            Future<Integer> result = threadPool.submit(c);
            //此 Future 的 get 方法所返回的结果类型
            Integer sum = result.get();
            System.out.println("sum=" + sum);
    
            //再演示
            result = threadPool.submit(c2);
            sum = result.get();
            System.out.println("sum=" + sum);
            //关闭线程池(可以不关闭)
    
        }
    }
    
    //Callable接口实现类
    class MyCallable implements Callable<Integer> {
        //成员变量
        int x;
        int y;
    
        public MyCallable(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        @Override
        public Integer call() {
            return x + y;
        }
    }
    

    线程同步(线程安全处理Synchronized)

    java中提供了线程同步机制,它能够解决上述的线程安全问题。

    线程同步的方式有两种:

    一,同步代码块

    同步代码块: 在代码块声明上 加上synchronized

    synchronized (锁对象) {
        可能会产生线程安全问题的代码
    }
    

    同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。

    使用同步代码块,对电影院卖票案例中Ticket类进行如下代码修改:

    package cn.last.demo;
    
    //测试类
    public class SpringTestApplicationTests {
        public static void main(String[] args) {
            //创建票对象
            Ticket ticket = new Ticket();
            //创建3个窗口
            Thread t1 = new Thread(ticket, "窗口1");
            Thread t2 = new Thread(ticket, "窗口2");
            Thread t3 = new Thread(ticket, "窗口3");
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    //模拟票
    class Ticket implements Runnable {
        //共100票
        int ticket = 100;
        //定义锁对象
        Object lock = new Object();
    
        @Override
        public void run() {
            //模拟卖票
            while (true) {
                //同步代码块
                synchronized (lock) {
                    if (ticket > 0) {
                        //模拟电影选坐的操作
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
                    }
                }
            }
        }
    }
    

    二,同步方法

    同步方法:在方法声明上加上synchronized

    public synchronized void method(){
           可能会产生线程安全问题的代码
    }
    

    同步方法中的锁对象是 this

    使用同步方法,对电影院卖票案例中Ticket类进行如下代码修改:

    三,静态同步方法: 在方法声明上加上static synchronized

    public static synchronized void method(){
        可能会产生线程安全问题的代码
    }
    

    静态同步方法中的锁对象是 类名.class

    四,死锁

    同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。

    synchronzied(A锁){
        synchronized(B锁){
        }
    }
    

    我们进行下死锁情况的代码演示:

    package cn.last.demo;
    
    import java.util.Random;
    
    //定义锁对象类
    class MyLock {
        public static final Object lockA = new Object();
        public static final Object lockB = new Object();
    }
    
    //测试类
    public class SpringTestApplicationTests {
        public static void main(String[] args) {
            //创建线程任务类对象
            ThreadTask task = new ThreadTask();
            //创建两个线程
            Thread t1 = new Thread(task);
            Thread t2 = new Thread(task);
            //启动线程
            t1.start();
            t2.start();
        }
    }
    
    //线程任务类
    class ThreadTask implements Runnable {
        int x = new Random().nextInt(1);//0,1
    
        //指定线程要执行的任务代码
        @Override
        public void run() {
            while (true) {
                if (x % 2 == 0) {
                    //情况一
                    synchronized (MyLock.lockA) {
                        System.out.println("if-LockA");
                        synchronized (MyLock.lockB) {
                            System.out.println("if-LockB");
                            System.out.println("if大口吃肉");
                        }
                    }
                } else {
                    //情况二
                    synchronized (MyLock.lockB) {
                        System.out.println("else-LockB");
                        synchronized (MyLock.lockA) {
                            System.out.println("else-LockA");
                            System.out.println("else大口吃肉");
                        }
                    }
                }
                x++;
            }
        }
    }
    

    五,Lock接口

    查阅API,查阅Lock接口描述,Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

    Lock接口中的常用方法:

    方法名 说明
    void lock() 获取锁
    void unlock() 释放锁

    Lock提供了一个更加面对对象的锁,在该锁中提供了更多的操作锁的功能。

    我们使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,对电影院卖票案例中Ticket类进行如下代码修改:

    package cn.last.demo;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class Ticket implements Runnable {
        //共100票
        int ticket = 100;
        //创建Lock锁对象
        Lock ck = new ReentrantLock();
    
        @Override
        public void run() {
            //模拟卖票
            while (true) {
                //synchronized (lock){
                ck.lock();
                if (ticket > 0) {
                    //模拟选坐的操作
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
                }
                ck.unlock();
            }
        }
    }
    

    六,等待唤醒机制

    在开始讲解等待唤醒机制之前,有必要搞清一个概念——线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。

    等待唤醒机制所涉及到的方法:

    方法名 说明
    void notify() 唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
    void notifyAll() 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
    void wait() 等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。

    其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。

    仔细查看JavaAPI之后,发现这些方法 并不定义在 Thread中,也没定义在Runnable接口中,却被定义在了Object类中,为什么这些操作线程的方法定义在Object类中?

    因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。

  • 相关阅读:
    FIFO深度计算
    php学习笔记--函数
    php学习笔记--类型转换
    php学习笔记--变量与常量
    css之伪对象-webkit-scrollbar
    8大排序算法
    正则表达式
    SDC Tcl package of Timequest
    面试经历之今日头条
    《Linux高性能服务器编程》学习总结(十三)——多进程编程
  • 原文地址:https://www.cnblogs.com/SnowPrince/p/14385743.html
Copyright © 2011-2022 走看看