美文网首页
java 多线程

java 多线程

作者: 谁吃了我的薯条 | 来源:发表于2017-09-30 14:10 被阅读0次

    进程即为程序,一个程序可以同时拥有多个操作,多个操作即为此进程的线程;
    线程是比进程效率更快的处理单元,且资源占有较少;
    线程运行在进程的基础之上,main()是主进程,所有线程的起点为run方法;

    线程的状态 (http://www.importnew.com/21136.html)
    在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面对Thread类中的方法的理解。
    创建(new)状态: 准备好了一个多线程的对象
    就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
    运行(running)状态: 执行run()方法
    阻塞(blocked)状态: 暂时停止执行, 可能将资源交给其它线程使用
    终止(dead)状态: 线程销毁

    当需要新起一个线程来执行某个子任务时,就创建了一个线程。但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,在前面的JVM内存区域划分一篇博文中知道程序计数器、Java栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态。
    当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。
    线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。
    当由于突然中断或者子任务执行完毕,线程就会被消亡。
    下面这副图描述了线程从创建到消亡之间的状态:

    2
    在有些教程上将blocked、waiting、time waiting统称为阻塞状态,这个也是可以的,只不过这里我想将线程的状态和Java中的方法调用联系起来,所以将waiting和time waiting两个状态分离出来。

    多线程的实现
    Java 拥有三种途径实现多线程操作;
    继承Thread类
    Thread 实现了 Runnable 接口
    要复写run()方法,调用时为start()方法,而且所有线程只能调用一次;

    
    public
    class Thread implements Runnable {
        /* Make sure registerNatives is the first thing <clinit> does. */
        private static native void registerNatives();
        static {
            registerNatives();
        }
    ...
       public synchronized void start() {
         
            if (threadStatus != 0)
                throw new IllegalThreadStateException(); //抛出异常
            // IllegalThreadStateException  if the thread was already started.
            group.add(this);
            boolean started = false;
            try {
                start0();
                started = true;
            } finally {
                try {
                    if (!started) {
                        group.threadStartFailed(this);
                    }
                } catch (Throwable ignore) {
                    /* do nothing. If start0 threw a Throwable then
                      it will be passed up the call stack */
                }
            }
        }
    
        private native void start0(); //native JNI技术
     // 线程能够执行,需要操作系统进行资源分配,故由JVM根据操作方式实现的
    
        public void run() {
            if (target != null) {
                target.run();
            }
        }
    
    

    为何要调用start()方法,而不是run()方法,下面看一个例子:

    package StringBase;
    
    import java.util.LinkedList;
    
    public class Base extends Thread{
       private String name;
       public Base(String name){
           this.name=name;
       }
       public void run(){
           System.out.println("name"+this.name+" "+Thread.currentThread().getId());
       }
    
    }
    
    package StringBase;
    
    public class Test {
       public static void main(String[] args){
           System.out.println(Thread.currentThread().getId());
           Base a=new Base("线程1");
           Base b=new Base("线程2");
           a.start();
           b.start();
    
       }
    }
    ----
    1 //主线程的ID
    name线程2 14  分线程
    name线程1 13 分线程
    
    

    从输出结果可以得出以下结论:

    1)线程1和线程2的线程ID不同,和主线程ID不相同,说明通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别,其次主线程的名字为 main();

    2)虽然线程1的start方法调用在线程2的run方法前面调用,但是先输出的是线程2的run方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。

    实现Runnable接口
    Thread存在一个问题,java是单继承问题,故通过继承多接口来实现;
    写一个类继承Runnable接口,然后复写run()方法;
    注意:调用多线程必须使用Thread的start()方法,其内部构造函数可以来调用start()

    -----
    public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);
        }
    ------
    public interface Runnable {
        /**
         * When an object implementing interface <code>Runnable</code> is used
         * to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p>
         * The general contract of the method <code>run</code> is that it may
         * take any action whatsoever.
         *
         * @see     java.lang.Thread#run()
         */
        public abstract void run();
    }
    
    
    package StringBase;
    
    import java.util.LinkedList;
    
    public class Base implements Runnable {
    
        private String num;
        public Base(String num){
            this.num=num;
        }
        public void run(){
            for(int i=0;i<12;i++)
                System.out.println(this.num+"主动创建的第"+i+"个线程");
        }
        public static void main(String[] args){
            Base a=new Base("线程1");
            Base b=new Base("线程2");
            Base c=new Base("线程3");
            new Thread(a).start();
            new Thread(b).start();
    
            new Thread(c).start();
        }
    
    
    }
    

    Thread 和 Runnable 的区别:
    1、Thread类有单继承的定义局限,且Thread实现了Runnable接口;
    2、Runnable 接口实现多线程,可以更加清楚的描述数据共享;

    Callable接口
    使用ExecutorService、Callable、Future实现有返回结果的多线程;
    可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。

    @FunctionalInterface //函数式接口
    public interface Callable<V> {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }
    
    
    package StringBase;
    
    import java.util.LinkedList;
    import java.util.concurrent.Callable; 
    
    public class Base implements Callable<String>  {
    
        private int num=10;
    
    
        public String call(){
            while (num>0){
                System.out.println("买票"+num--); 
            }
            return "买完了";
        }
    
    }
    
    package StringBase;
    
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class Test {
        public static void main(String[] args) throws Exception{
        Base a=new Base();
        Base b= new Base();
        FutureTask<String> s=new FutureTask<String>(a); //为了获取call()对象
        FutureTask<String> t=new FutureTask<String>(b); //为了获取call()对象
       // FutureTask 是Runnable 的接口子类;
        new Thread(s).start(); //开始多线程
        new Thread(t).start();
        System.out.println("a"+s.get()); //返回操作结果
        System.out.println("b"+s.get());
        }
    }
    

    多线程的常用方法
    1、线程的命名与获取
    线程的运行结果顺序是不可预测,因为它会抢占系统运行资源;
    线程的命名可以通过以下方法:
    构造方法:

     public Thread(Runnable target, String name) {
            init(null, target, name, 0);
        }
    

    setName()方法

        public final synchronized void setName(String name) {
            checkAccess();
            if (name == null) {
                throw new NullPointerException("name cannot be null");
            }
    
            this.name = name;
            if (threadStatus != 0) {
                setNativeName(name);
            }
        }
    

    每一个JVM进程启动的时候,至少启动几个线程:
    1、main() 主线程,负责程序的主线程以及启动子线程;
    2、GC线程,负责垃圾清理;

    线程的休眠
    休眠,是指让线程的执行速度稍微慢一点;

    public static native void sleep(long millis) throws InterruptedException;
    //打断休眠,会抛出一个异常
    
    package StringBase;
    
    import java.util.LinkedList;
    import java.util.concurrent.Callable;
    
    
    public class Base implements Runnable {
    
        private int num=10;
    
    
        public  void run(){
            while (num>0){
                try {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println("买票"+num--);
            }
    
        }
        public static void main(String args[]){
            Base a=new Base();
            new Thread(a).start();
        }
    
    }
    

    默认情况下,若在休眠的状态下,设置多线程将一起进入(按照先后顺序)休眠状态;

    package StringBase;
    
    import java.util.LinkedList;
    import java.util.concurrent.Callable;
    
    
    public class Base implements Runnable {
    
        private int num=10;
    
    
        public  void run(){
            while (num>0){
                try {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println("买票"+num--);
            }
    
        }
        public static void main(String args[]){
            Base a=new Base();
            Base b=new Base();
            Base c=new Base();
            Base d=new Base();
            new Thread(a).start();
            new Thread(b).start();
            new Thread(c).start();
            new Thread(d).start();
        }
    
    }
    -----
    买票10
    买票10
    买票10
    买票10
    //休眠。。。
    买票9
    买票9
    买票9
    买票9
    买票8
    买票8
    买票8
    ...
    

    线程优先级
    所谓的优先级为,优先级越高的线程,越有可能先执行;
    优先级的设置和取得:

    
        public final void setPriority(int newPriority) {
            ThreadGroup g;
            checkAccess();
            if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
                throw new IllegalArgumentException();
            }
            if((g = getThreadGroup()) != null) {
                if (newPriority > g.getMaxPriority()) {
                    newPriority = g.getMaxPriority();
                }
                setPriority0(priority = newPriority);
            }
        }
    ---
       public final int getPriority() {
            return priority;
        }
    
    

    优先级的级别

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

    线程优先级特性:

    继承性
    比如A线程启动B线程,则B线程的优先级与A是一样的。
    规则性
    高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
    随机性
    优先级较高的线程不一定每一次都先执行完。

    线程的同步与死锁
    不同的线程操作了同一个资源,这就需要线程的同步;(比如两个人同时取一张卡的存钱);

    
    package StringBase;
    
    import java.util.LinkedList;
    import java.util.concurrent.Callable;
    
    
    public class Base implements Runnable {
    
        private int num=6;
    
    
        public  void run(){
            while (num>0){
    
                System.out.println(Thread.currentThread().getName()+"买票"+num--);
            }
    
        }
        public static void main(String args[]){
            Base a=new Base();
            new Thread(a,"票贩子A").start();
            new Thread(a,"票贩子B").start();
            new Thread(a,"票贩子C").start();
            new Thread(a,"票贩子D").start();
        }
    
    }
    -----
    票贩子B买票5
    票贩子B买票2
    票贩子B买票1
    票贩子C买票4
    票贩子D买票3
    票贩子A买票6
    
    -----------------------------------
    package StringBase;
    
    import java.util.LinkedList;
    import java.util.concurrent.Callable;
    
    
    public class Base implements Runnable {
    
        private int num=6;
    
    
        public  void run(){
            while (num>0){
                try {
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"买票"+num--);
            }
    
        }
        public static void main(String args[]){
            Base a=new Base();
            new Thread(a,"票贩子A").start();
            new Thread(a,"票贩子B").start();
            new Thread(a,"票贩子C").start();
            new Thread(a,"票贩子D").start();
        }
    
    }
    ------
    票贩子C买票5
    票贩子A买票6
    票贩子D买票4
    票贩子B买票5
    票贩子C买票3
    票贩子A买票2
    票贩子D买票1
    票贩子B买票0
    票贩子C买票-1
    票贩子A买票-2
    

    同步的操作:
    问题所在点:判断与修改使异步完成的,不同线程都可以进行操作;
    故:


    源码的实现:
    使用 synchronized 关键字:
    同步代码块
    同步方法

    (代码块(构造代码块,普通代码库,静态代码块,同步代码块))

    package StringBase;
    
    import java.util.LinkedList;
    import java.util.concurrent.Callable;
    
    
    public class Base implements Runnable {
    
        private int num=10;
    
    
        public  void run(){
            for(int i=0;i<20;i++){
                this.sale();
            }
        }
        public synchronized void sale(){
            if (num>0){
                try {
                    Thread.sleep(150);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"买票"+num--);
            }
    
        }
        public static void main(String args[]){
            Base a=new Base();
            new Thread(a,"票贩子A").start();
            new Thread(a,"票贩子B").start();
            new Thread(a,"票贩子C").start();
            new Thread(a,"票贩子D").start();
        }
    
    }
    -----
    票贩子A买票9
    票贩子D买票8
    票贩子C买票7
    票贩子C买票6
    票贩子C买票5
    票贩子B买票4
    票贩子B买票3
    票贩子B买票2
    票贩子C买票1
    

    同步操作的执行速度要低于异步操作,但安全性较高;
    但线程同步过多,就会造成死锁;

    Object 类
    等待:wait()

     public final native void wait(long timeout) throws InterruptedException;
    

    唤醒第一个等待:

    public final native void notify();
    

    唤醒所有等待:

    public final native void notifyAll();
    

    注:sleep和wait的区别:

    sleep是Thread类的方法,wait是Object类中定义的方法.
    sleep可以设置休眠时间,wait类不用,但是需要唤醒机制notify()

    相关文章

      网友评论

          本文标题:java 多线程

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