java多线程实例解析

多线程含义:

1、首先从宏观上解释一下多线程到底是个什么东西,我们拿一个生活中例子来看一下;汽车上高速和出高速的时候都需要经过收费站吧,我们可以脑补下这个场景;现在汽车要下高速,收费站最主要的任务是对每辆车进行收费,这样我们可以把收费站看成一个程序方法体,专门处理收费这个功能;这里我们先要抛弃掉多个窗口的那种收费站,最原始的情况下,就像在一条上设了一个卡口,每辆车都要只能从这个一个卡口通过,这是原始的情况;当然我们知道这种方式效率很慢,所有的车都必须等待,只能从一个口出,进而为了提高效率收费站进行了改造,不是单一的窗口了,而是并排建了多个收费窗口,车辆可以选择任意一个窗口缴费,因为每个窗口的功能都是一样的;这样一对比,比原始的单一窗口效率高出好几倍;同样程序代码中也是这样,每个任务相当于每辆车,进入方法体都要执行缴费的功能,在程序代码中开辟多个线程来处理,提高执行效率;

2、好了,在大致理解什么是多线程之后,我们在来看java对线程的解释也就简单的多了;java中对线程的解释是这样的,线程是进程的单位,进程是由线程组成;这里我们可以把收费站看成一个单一功能的软件,这个软件开启了一个收费进程;进程中最少也有一条线程在执行这里什么意思呢,就是说进程其实就是有线程组成的,确实,同样拿收费站来看,最原始的时候收费站就只有一个窗口,就相当于一条线程,也可以是改良后的多个窗口,多个线程组成;

      同样线程的一些特点也能解释的同了,同一类线程共享数据空间和代码块,代码块就是指的收费站这个整体,而数据空间则是指每个窗口其实都是同一个数据库,每条记录都是记在同一张表中,不管是从哪个窗口录入的;每个线程有独立运行的栈和程序计数器,也就相当于每个窗口,每个收费窗口之间互不影响,每个窗口都记录从该窗口缴费的车型,费用;

 实现多线程方法:

1)、继承Thread类;

2)、实现Runnable接口;

3)、实现Callable接口,该接口是用于线程池中的,后面再讲;

首先我们按顺序先看Thread类:

/**
 * Created by zelei.fan on 2017/7/23.
 */
public class ThreadTest extends Thread {

    private int num;

    ThreadTest(int num){
        this.num = num;
    }

    @Autowired
    public void run(){
        System.out.println("缴费" + num);
    }
}
/**
 * Created by zelei.fan on 2017/7/23.
 */
public class test {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i ++){
            int num = new Random().nextInt(100);/*取一个随机数*/
            ThreadTest threadTest = new ThreadTest(num);
            threadTest.start();
        }
    }
}

实现runnable接口:

/**
 * Created by zelei.fan on 2017/7/23.
 */
public class ThreadTest implements Runnable {

    private int num;

    ThreadTest(int num){
        this.num = num;
    }

    @Autowired
    public void run(){
        System.out.println("缴费" + num);
    }
}
/**
 * Created by zelei.fan on 2017/7/23.
 */
public class test {

    public static void main(String[] args) {
        int num = new Random().nextInt(100);/*取一个随机数*/
        ThreadTest threadTest = new ThreadTest(num);/*这边实例其实是可以方法循环中,但是这杨就不能体现出和thread的差别了*/
        for (int i = 0; i < 10; i ++){
            new Thread(threadTest).start();
        }
    }
}

调用方式的区别:

继承thread类的实例不能重复调用start(),原因是一个thread实例不能重复调用start(),因为当前这个thread已经变成可执行状态,当再次调用start()方法时,有可能线程已经在执行过程中了,而线程中除了阻塞和可运行状态之间可以相互逆转之外,其他状态时不能逆转的,所以也就解释了上述问题,运行中不能再次逆转成可运行状态;

但是实现runnable接口则不然,只new一个程序代码,但是多线程的时候是每用一个都是new一个新的thread类来启动这个程序实例这样就不存在上述thread的那样的问题,同时也实现了多个线程处理同一资源;

两者的区别:

1、runnable适合多个相同的程序代码的线程去处理同一资源;当用继承thread类时,new出的多个ThreadTest实例,在同时启动线程的时候这几个线程都是在各自运行各自中的资源, 各自的线程中都有num这个资源,并且代码空间也都是互相独立,因为ThreadTest被new出多个实例,这几个线程并不共用同一实例;
但是实现runnable接口就不一样了,new出一个RunnableTest实例后,下面几个线程都是用的同一个实例来启动线程,当执行到程序中时,每个线程用的代码都是同一个,而且用的资源num也是同一个,这样就实现了多线程对同一资源的处理;

  2、可以避免java中的单继承的限制,继承thread:如果ThreadTest需要继承一个父类代码,但是又同时想实现多线程这个功能,那就和java单继承有冲突,实现runnable:可以实现在继承thread的同时再实现runnable接口;

  3、代码可以被多个线程共享,代码和数据独立; 代码共享:都是用的同一实例;代码和数据独立:即代码和数据是分开的,数据是一个公共的资源,个线程之间都能使用

  4、线程池中只能放入runnable接口或者callable接口;

线程状态转换详情:

1、创建一个线程(new Thread())
2、可运行状态(调用start()方法)
3、正常情况下,获取到cpu使用权,直接执行
4、阻塞:
1)、等待阻塞,线程调用wait()方法,jvm会吧该线程放到等待池中;
2)、同步阻塞,线程在获取同步锁时,同步锁还在被其他线程占用中,jvm把该线程放到锁池中;
3)、其他阻塞,调用sleep()或者join(),线程sleep超时或者join等到其他线程终止,改线成则重新回到可执行状态;(注意执行sleep时不释放锁)
5、线程结束

线程调度:

Thread.sleep(100);:sleep不会释放对象锁,而wait在调用后会释放对象锁
new Thread().wait();:线程等待,等到其他线程调用notify()或者notifyAll()方法来唤醒这些wait的线程
Thread.yield();:线程让步,把资源让给优先级高的线程,让当前线程回到可执行状态,允许相同优先级的其它线程获得执行机会
new Thread().join();:等待其他线程终止,在当前线程中调用另一个线程的join方法,则当前线程进入阻塞状态,直到另一个线程完成菜进入就绪状态
new Thread().notify();:唤醒在等待的线程中的某一个线程,随机唤醒其中一个;notifyAll()唤醒所有线程
Thread.interrupted();:终止线程,在线程阻塞的时候(join,sleep)执行会导致InterruptedException异常

线程调度具体实例:

jion():
不使用的时候:
/**
 * Created by zelei.fan on 2017/7/23.
 */
public class ThreadTest extends Thread {

    private String name;

    ThreadTest(String name){
        this.name = name;
    }

    ThreadTest(){}

    private int index = 5;

    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName()+"线程开始运行");
        while (index > 0) {
            System.out.println(Thread.currentThread().getName()+name + "运行  :  " + index);
            index --;
        }
        System.out.println(Thread.currentThread().getName()+"线程运行结束");
    }

    public static void main(String[] args) {
        /*join用法
        * 作用:当主线程需要子线程的执行结果时,主线程必须等待子线程执行结束才能继续执行
        * */
        System.out.println("主线程运行开始");
        ThreadTest a = new ThreadTest("A");
        a.start();
        System.out.println("主线程运行结束");
    }
}

打印结果:

主线程运行开始
主线程运行结束
Thread-0线程开始运行
Thread-0A运行  :  5
Thread-0A运行  :  4
Thread-0A运行  :  3
Thread-0A运行  :  2
Thread-0A运行  :  1
Thread-0线程运行结束

使用后:

/**
 * Created by zelei.fan on 2017/7/23.
 */
public class ThreadTest extends Thread {

    private String name;

    ThreadTest(String name){
        this.name = name;
    }

    ThreadTest(){}

    private int index = 5;

    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName()+"线程开始运行");
        while (index > 0) {
            System.out.println(Thread.currentThread().getName()+name + "运行  :  " + index);
            index --;
        }
        System.out.println(Thread.currentThread().getName()+"线程运行结束");
    }

    public static void main(String[] args) {
        /*join用法
        * 作用:当主线程需要子线程的执行结果时,主线程必须等待子线程执行结束才能继续执行
        * */
        System.out.println("主线程运行开始");
        ThreadTest a = new ThreadTest("A");
        a.start();
        try {
            a.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程运行结束");
    }
}

打印结果:

主线程运行开始
Thread-0线程开始运行
Thread-0A运行  :  5
Thread-0A运行  :  4
Thread-0A运行  :  3
Thread-0A运行  :  2
Thread-0A运行  :  1
Thread-0线程运行结束
主线程运行结束

yield():线程让步在用例子比较难体现出来,打印出来的结果几乎一样的,因为就算不执行线程让步,每个线程都是互相抢占cpu资源的,谁抢到谁执行,没有固定的先后顺序;

如下:

/**
 * Created by zelei.fan on 2017/7/23.
 */
public class ThreadTest extends Thread {

    private String name;

    ThreadTest(String name){
        this.name = name;
    }

    ThreadTest(){}

    private int index = 5;

    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName()+"线程开始运行");
        while (index > 0) {
            System.out.println(Thread.currentThread().getName()+name + "运行  :  " + index);
            index --;
        }
        System.out.println(Thread.currentThread().getName()+"线程运行结束");
    }

    public static void main(String[] args) {
        
        ThreadTest a = new ThreadTest("A");
        ThreadTest b = new ThreadTest("B");
        a.start();
        b.start();
    }
}

打印结果:

Thread-0线程开始运行
Thread-1线程开始运行
Thread-1B运行  :  5
Thread-1B运行  :  4
Thread-0A运行  :  5
Thread-0A运行  :  4
Thread-0A运行  :  3
Thread-0A运行  :  2
Thread-0A运行  :  1
Thread-0线程运行结束
Thread-1B运行  :  3
Thread-1B运行  :  2
Thread-1B运行  :  1
Thread-1线程运行结束

使用后:这里为了能清楚的看到这个执行过程,我在代码中设定只要是index为3是就让步,不管是哪个线程

/**
 * Created by zelei.fan on 2017/7/23.
 */
public class ThreadTest extends Thread {

    private String name;

    ThreadTest(String name){
        this.name = name;
    }

    ThreadTest(){}

    private int index = 5;

    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName()+"线程开始运行");
        while (index > 0) {
            if (3 == index){
                Thread.yield();
                System.out.println(Thread.currentThread().getName()+name + "线程让步");
                index --;
            }else {
                System.out.println(Thread.currentThread().getName()+name + "运行  :  " + index);
                index --;
            }
        }
        System.out.println(Thread.currentThread().getName()+"线程运行结束");
    }

    public static void main(String[] args) {
       
        ThreadTest a = new ThreadTest("A");
        ThreadTest b = new ThreadTest("B");
        a.start();
        b.start();
    }
}

打印结果:

Thread-0线程开始运行
Thread-1线程开始运行
Thread-0A运行  :  5
Thread-1B运行  :  5
Thread-0A运行  :  4
Thread-0A线程让步
Thread-1B运行  :  4
Thread-0A运行  :  2
Thread-0A运行  :  1
Thread-1B线程让步
Thread-0线程运行结束
Thread-1B运行  :  2
Thread-1B运行  :  1
Thread-1线程运行结束
从上面的打印结果可以看出:当a让步后,b获得资源,然后执行;但是这只是一种情况,也有可能还是a继续执行,他们之间是公平竞争,谁抢到谁执行;

wait()和notify():这两个是搭档,配合使用的;一个等待一个唤醒;

下面通过实例来讲解下,需求是我要打印出ABABAB这种结果,不能乱:

class SortThread implements Runnable{

    private int index = 10;

    private String name;

    private Object prev;

    private Object self;

    SortThread(String name, Object prev, Object self){
        this.name = name;
        this.prev = prev;
        this.self = self;
    }

    @Override
    public void run() {
        while (index > 0) {
            synchronized (prev) {
                synchronized (self) {
                    System.out.print(name);
                    self.notify();
                    index--;
                }
                try {
                    prev.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

public static void main(String[] args) throws InterruptedException {
        Object a = new Object();
        Object b = new Object();
        SortThread sortThread = new SortThread("A", a, b);
        SortThread sortThread1 = new SortThread("B", b, a);
        new Thread(sortThread).start();
        Thread.sleep(100);
        new Thread(sortThread1).start();
    }

打印结果: ABABABABABABABABABAB

解释:

先从main方法中的两个对象说起,a和b其实分别是下面两个线程的锁,当然这边涉及到锁的知识后面再讲,这两个线程都持有自身的锁和对方的锁;Thread.sleep(100);主要是让A能先执行;

然后我们再看run方法中做了什么,第一步获取对方的锁和自身的锁,当获取到自身锁时,就算B是可执行状态也进不来,以为第一层B要执行就得先获得A的锁,而A的锁已经被A在使用,只有等待A释放;self.notify();唤醒其它线程,此时在门外等待的B线程被唤醒;执行完成后首先是释放自身的锁;当A释放自身锁的时候,B就能获取到锁进入第一层,然而B的自身锁还没有被A释放;当A的自身锁释放后继续执行到prev.wait();,此处执行就会立马释放B的锁,只要一释放B会立马抢占到自身锁,然后执行方法,而此刻A线程则被关在门外;就是这样循环下去;




你可能感兴趣的:(多线程,多线程)