java多线程笔记

1、多线程相关概念
1.1 什么是多线程

        在早期的计算机中时没有操做系统的,计算机开启后只能执行一个程序,直到结束。操做系统的出现使得计算机能够同时执行多个程序,操做系统为每一个程序分配不一样的进程,每一个进程拥有独立的句柄、资源等,使得计算机能够同时执行多个程序。可是进程的建立和销毁耗费的代价太大,所以衍生出线程的概念。容许在一个进程中建立多个线程,这些线程共享进程的资源,而且每一个线程拥有本身独立的程序计数器、线程局部变量等资源。线程也被称为进程的轻量型运动实体。
1.2 线程的优点

线程的出现带来不少的好处:

一、发挥多核处理器的性能:在多喝处理器上执行单线程任务是对多核的浪费,由于总有核心在空闲着,多线程的出现能充分发挥多核的优点。

二、化整为零:每每在一个复杂的应用中包含许多不一样类型的任务,将这些不一样类型的任务分配给不一样的线程去执行会比将其混在同一个线程中去执行要好,由于每一个线程更加的简单清晰,更容易测试等。

三、异步事件处理:当一个线程处理的任务遇到阻塞时如IO阻塞,cpu能够调度其余线程去执行而不是在那傻傻的等到IO结束在执行其余任务。

四、更好的用户体验:当多个用户像你的服务发送请求时,你一个线程依次执行任务会使得排在后面的用户等待时间过长得不到响应,带来很差的体验。但使用多个线程可让每一个用户都能很快的获得响应(尽管这不能高执行速度),用户会以为本身的请求正在被处理,得到更好的体验。
1.3 多线程引入的问题

多线程虽然带来了许多优点,可是不当使用多线程也会引入许多问题。

一、安全性:多线程同时访问操做共享资源会形成不可预料的结果。

二、活跃性问题:因为多线程争夺资源致使无限循环,出现死锁、活锁、线程饥饿等问题。

三、性能问题:多线程的引入是为了提升处理器的性能,可是滥用线程不只不能提升性能反而会致使性能降低。如建立过多的线程使CPU频繁地去执行线程切换致使性能下降。
2、Java中建立线程

在java中建立线程的方式有三种:继承Thread类重写run方法、实现runnable接口重写run方法和实现callable接口重写call方法配合futureTask使用
2.一、继承Thread类

    public class MyThread extends Thread{
        
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            myThread.start();
        }
        
        @Override
        public void run() {
            int i = 0;
            for(;i<100;i++) {
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
     
    }

继承Thread类的方式建立线程算是最简单的了,可是你的线程类每每要继承项目中的其余类,而Java是单继承机制的,因此使用此方法会有很大的局限性。
2.二、实现runnable接口

    public class MyRunnable implements Runnable {
     
        public static void main(String[] args) {
            MyRunnable myRunnable = new MyRunnable();
            Thread thread = new Thread(myRunnable);
            thread.start();
        }
     
        @Override
        public void run() {
            int i = 0;
            for (; i < 100; i++) {
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
     
    }

Thread类的构造方法容许传入一个实现runnable接口的target进去,线程启动将会执行target.run方法。实现runnable接口的方式能够很好的避免单继承问题。
2.三、实现callable接口

        public static void main(String[] args) throws Exception {
            System.out.println("12123");
            FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
            new Thread(futureTask).start();
            // futureTask能够在指定时间内获取线程执行的返回值,超时则丢弃任务
            //  所以futureTask能够用做异步任务处理
            futureTask.get(1000, TimeUnit.SECONDS);
        }
     
        @Override
        public Integer call() throws Exception {
            int i = 0;
            for(;i<100;i++) {
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return i;
        }

call方法与run方法最大的区别在于call方法存在返回值futureTask的get方法能够获取这个返回值。使用此种方法实现线程的好处是当你建立的任务的结果不是当即就要时,你能够提交一个线程在后台执行,而你的程序仍能够正常运行下去,在须要执行结果时使用futureTask去获取便可。这是一种典型的异步任务处理的方法。


3、线程的各类状态及其转换

一个线程的生命周期包含如下五种状态:

一、初始状态:线程被new出来时处于此种状态

二、就绪状态:当一个线程的start方法被调用或正在运行的线程CPU时间片耗尽或处于阻塞状态的线程知足了运行条件时处于此种状态。就绪状态的线程处于CPU调度队列中等待得到CPU时间片执行。

三、运行状态:处于就绪状态的线程获取到CPU时间片,正在执行。

四、阻塞状态:当线程要执行的必要条件没法知足时处于此种状态,如等待其余线程释放锁、等待IO等。当这些条件获得知足后线程变为就绪状态。

五、死亡:当线程run方法执行完或者遇到异常退出时线程死亡,线程的生命周期就此终止。

如下为线程状态转换图:


4、经常使用方法总结
4.一、wait、notify和notifyAll

这三个方法并不属于Thread类而是属于Object类,这意味着在Java总全部的类都拥有这三个方法。

在Java中每一个对象均可以用做实现一个同步的锁,这些锁被称为内置锁或者监视器锁。当一个线程调用一个对象的wait方法时,他会让本身(即当前线程)陷入等待状态,并放弃所持有的锁,当其余线程再调用这个对象的notify或notify方法时会唤醒这个对象内置锁上等待的线程,此时刚刚陷入等待的线程能够在停下来的地方继续向下执行。

wait方法:使调用此方法的线程当即进入阻塞状态并放弃所持有的锁。

notify与notifyAll:使当前线程放弃持有的某个对象上的锁并唤醒在这个对象锁上等待的其余线程,notify唤醒单个线程,notifyAll唤醒所有线程。调用notify方法后当前线程会在执行完剩余代码后放弃该对象的锁而不是像wait方法那样当即释放。

wait和notify方法能够实现线程之间的通讯,典型的使用就是用来实现生产者和消费者问题--当队列满了生产者阻塞并唤醒消费者,当队列空了消费者阻塞并唤醒生产者。

以下为使用wait与notify实现的生产者消费者问题:

    public class Test {
        public static void main(String[] args) {
            Queue<Integer> queue = new LinkedBlockingQueue<>();
            new Producer(queue).start();
            new Customer(queue).start();
        }
    }
     
    class Producer extends Thread {
        Queue<Integer> queue;
     
        public Producer(Queue<Integer> queue) {
            this.queue = queue;
        }
     
        @Override
        public void run() {
            while (true) {
                synchronized (queue) {
                    try{
                        Thread.sleep(500);
                    } catch (Exception e) {}
                    if (queue.size() >= 3) {
                        try {
                            System.out.println("队列已满,生产者阻塞");
                            queue.wait();
                        } catch (InterruptedException e) {
                        }
                    }
                    // 唤醒消费者
                    queue.notify();
                    int i = new Random().nextInt(100);
                    System.out.println("生产" + i);
                    queue.add(i);
                }
            }
        }
     
    }
     
    class Customer extends Thread {
        Queue<Integer> queue;
     
        public Customer(Queue<Integer> queue) {
            this.queue = queue;
        }
     
        @Override
        public void run() {
            while (true) {
                synchronized (queue) {
                    try{
                        Thread.sleep(500);
                    } catch (Exception e) {}
                    if (queue.isEmpty()) {
                        try {
                            System.out.println("队列已空,消费者阻塞");
                            queue.wait();
                        } catch (InterruptedException e) {
                        }
                    }
                    // 唤醒生产者
                    queue.notify();
                    int i = queue.poll();
                    System.out.println("消费:" + i);
                }
            }
        }
    }

运行结果以下:



4.二、join()方法

join方法为Thread类全部方法,当在一个线程A中调用另外一个线程B的join方法时,线程A会等待线程B执行完再进行执行。
4.三、yield()方法

yield方法会发送一个通知给调度器表示当前线程愿意让出CPU时间片给其余线程先执行,可是这种让步策略并不能获得保障,由于调度器能够选择忽视这个通知,这取决于当前下同的线程调度策略。
4.四、sleep(long millis)方法

sleep方法会使当前线程休眠指定时间,处于休眠的线程并不会放弃已经持有锁,放不放弃锁是sleep方法与wait方法的最大区别。
4.五、setPriority(int newPriority)

setPriority能够设置线程的优先级,传入一个1到10之间的整数,数值越大表示优先级越高,在一个母线程中建立一个子线程则子线程的优先级默认与母线程相同。

java中设置线程优先级有10个等级可选,可是这种等级制度在不一样的系统中不能获得很好的映射,某些系统可能没有10个线程等级,某些则超过10个等级,Java中默认给咱们提供了三种可选等级,大部分时候使用这三种等级便可:

        /**
         * The minimum priority that a thread can have.
         */
        public final static int MIN_PRIORITY = 1;
     
     
       /**
         * The default priority that is assigned to a thread.
         */
        public final static int NORM_PRIORITY = 5;
     
     
        /**
         * The maximum priority that a thread can have.
         */
        public final static int MAX_PRIORITY = 10;

4.六、stop()和suspend()

这两个方法是被废弃的方法。

stop():当即中止一个线程的运行并放弃他所持有的全部的锁。

为什么弃用:这个当即中止是强制性的,你甚至能够在线程还没运行时就调用     stop使其强制停下,这样线程在运行伊始就会被中止。显然这种强制的中止一个线程并释放锁的方式是不安全的,由于当这个线程在处理时会使所持有锁的对象处于中间状态,这种状态在系统中不该该存在,若是线程能正常执行或异常退出时,这种中间状态是不会被其余线程看到的。可是调用stop方法会使当前线程强制性的释放这个对象的锁,使其在中间状态时就对其余线程可见,这显然会致使线程安全问题。

stop被弃用后的替代:stop不能使用时如何中止一个线程呢?官方推荐的方式是在线程运行时添加一个状态变量,线程会不断的检查这个变量来决定是否停下,以下:

        @Override
        public void run() {
            
            while(!isStop) {
                doSomething();
            }
    }

run方法一旦发现isStop为true会理解结束,这也是最好的中止线程的方法了。可是这种方式有个弊端:一旦doSomething方法执行时间过长或者在等待时会致使当前线程无暇去检测isStop变量,此时建议使用interrupt()方法去中断这个线程。

suspend():暂停一个线程可是不放弃所持有的锁。

suspend被弃用缘由:suspend被调用后当前线程会暂停下来可是不会放弃持有的对象锁,直到其余的线程调用resume方法将此线程唤醒。若是唤醒线程须要拿到被暂停的线程所控制的锁才能运行时会出现死锁状态,即唤醒线程不拿到锁则没法唤醒暂停线程,而暂停线程不被唤醒则没法释放锁。所以suspend被弃用,他的兄弟resume方法天然也被弃用。

suspend与wait和sleep的区别:wait会使当前线程等待可是会释放锁,sleep不释放锁可是会本身唤醒本身,所以sleep与wait都不会死锁。而suspend即不释放锁也不能本身唤醒,会产生死锁因此被弃用。


4.七、interrupt()、interrupted()和isInterrupted()

理论上来说一个线程不能被别的线程中断或终止,只能由本身中断和终止,这也是stop方法被弃用的缘由。

interrupt:此方法会向此线程发送一个信号:你被中断了,并将该线程的中断标志改成true。还记得stop方法被废弃的替代方案吗,使用一个标志位来决定是否停下线程,interrupt方法实际上也是这样操做的。该线程正常处理时会不断的去读取这个中断标志,发现为true时则中止,当该线程处于阻塞状态时(如调用了wait、sleep、join)会让该线程当即抛出一个InterruptedException而且清除该线程的中断状态,这样能够直接进入该线程的catch代码块中进行执行,在catch中能够对此线程进行特殊处理(当即结束或者作一些处理后再次运行)

interrupted:此方法会执行两步操做:一、返回该线程的中断标志。二、将该线程的中断标志设置为false。显然这两部操做是要知足原子性的,此方法也是被synchronized修饰过的。有些线程在被调用了interrupt方法后并不想停下,能够在他的catch代码块中调用本身的interrupted方法,让本身继续正常运行。

isInterrupted:此方法会返回该线程的中断标志
 java

相关文章
相关标签/搜索