多线程的简单实现,

Posted 郑科院信工

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程的简单实现,相关的知识,希望对你有一定的参考价值。

进程和线程的区别

每一个计算机应用至少有一个进程,并对应一个进程号。 

线程是比进程更加细粒度,一个进程可以有多个线程。可以并发执行多个任务。

并行和并发的区别:

并行:多个CPU实例或者多个机器同时执行一段处理逻辑。真正的同时。

并发:通过CPU调度算法,让用户看上去同时执行一段处理逻辑,并不是真正的同时。


我们通过一个简单的实例了解一下多线程的威力。

多线程的简单实现,

比如。我们想看一下局域网内有哪些主机是可达的。我们可以使用cmd

命令行工具,使用ping命令来看是否接通,可以看到接通的会有TTL关键字,我们就可以根据这个关键字来判断是否可达。

先用单线程实现一下。

package com.xuhang.springbootdemo.thread;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
* @authorxuhang
* @description
* @date2018/10/31 15:07
*/
public class PingHost {

   public static boolean checkHost(String ip) throws IOException {
       long begin = System.currentTimeMillis();
       Process pro = Runtime.getRuntime().exec("ping " + ip);
       //判断返回信息是否包含TTL关键字
       String line;
       BufferedReader buf = new BufferedReader(new InputStreamReader(pro.getInputStream()));

       while ((line = buf.readLine()) != null) {
           if (line.contains("TTL")) {
               long end1 = System.currentTimeMillis();
               long l1 = end1 - begin;
               System.out.println("主机" + ip + "可到达  耗费时间:"+l1+"ms" );
             
               return true;
           }
       }
       long end2 = System.currentTimeMillis();
       long l2 = end2 - begin;
       System.out.println("主机" + ip + "不可到达  耗费时间:"+l2+"ms");
       return false;

   }

   /**
    * @param args
    * @throws IOException
    */
   public static void main(String[] args) throws IOException {
       PingHost ph = new PingHost();
       for (int i = 147; i < 300; i++) {
           ph.checkHost("10.40.96." + i);
       }

}

}

看一下运行结果。

多线程的简单实现,

可以看到是按照顺序进行测试的。而且耗费时间特别多,如果自己操作可以不用等到结束就中断,因为太费时间了。总之单线程的缺点是:所有的操作顺序,时间长,效率低。


那么我们就来看一下多线程是什么样的。实现多线程的方式有多种。今天我们来了解继承Thread类并重写run方法和实现Runnable接口并重写run方法两种方法实现。启动一个线程是调用线程的start方法。面试中经常问到。

package com.xuhang.springbootdemo.thread;

import java.io.IOException;

/**
* @authorxuhang
* @description:通过继承Thread类实现多线程。
* @date2018/10/31 15:25
*/
public class MultiThreadByThread extends Thread {
   private String ip;
   public MultiThreadByThread(String ip) {
       super();
       this.ip = ip;
   }
   @Override
   public void run() {
       try {
           PingHost.checkHost(ip);
       } catch (IOException e) {
           e.printStackTrace();
       }
   }

}


这种方法使用主函数测试时需要变一下;如下所示:

//测试Thread方法
for
(int i = 147; i < 300; i++) {
   MultiThreadByThread btt =new MultiThreadByThread("10.40.96." + i);
   btt.start();
}

如果自己操作,通过控制台就可以很直观的看到质的飞跃。很快就可以执行完。

多线程的简单实现,

可以看出:操作没有按照顺序,并且耗费时间大大缩减。这就是多个线程同时进行操作,不用等待前一个线程结束就可以直接执行。

另一种方式,实现Runnable接口:

package com.xuhang.springbootdemo.thread;

import java.io.IOException;

/**
* @authorxuhang
* @description:通过实现Runnable接口并重写run方法实现多线程
* @date2018/10/31 15:53
*/
public class MultiThreadByRunnable implements Runnable {
   private String ip;
   public MultiThreadByRunnable(String ip) {
       super();
       this.ip = ip;
   }

   @Override
   public void run() {
       try {
           PingHost.checkHost(ip);
       } catch (IOException e) {
           e.printStackTrace();
       }

   }

}


再测试一下Runnable接口的方式,主函数修改为:

//测试runnable方式
for (int i =147; i < 300; i++) {
   MultiThreadByRunnable mtb =new MultiThreadByRunnable("10.40.96." + i);
   new Thread(mtb).start();

}

通过控制台看出。效果和继承Thread类似;

多线程的简单实现,

同样是无序的,在短时间内就完成了。


但是  Thread和Runnable的区别有哪些呢?

我们通过一个火车站卖火车票的实例来了解一下。

如果使用thread方式,火车票资源不共享,各卖各。

package com.xuhang.springbootdemo.thread;

/**
* @authorxuhang
* @description
* @date2018/10/31 16:02
*/
public class ThreadTicket extends Thread {
   private int ticketnum=5;
   private String name;
   public ThreadTicket(String name) {
       super();
       this.name = name;
   };
   @Override
   public void run() {
       for (int i = 1; i <=5; i++) {
           System.out.println(name+"卖票"+ticketnum--);
       }
   }

   public static void main(String[] args) {
       //使用金庸武侠里的美女角色怀念一下金庸大侠
       ThreadTicket zhaomin = new ThreadTicket("赵敏");
       ThreadTicket xiaozhao = new ThreadTicket("小昭");
       //启动两个线程
       zhaomin.start();
       xiaozhao.start();

   }

}

控制台输出:

多线程的简单实现,

可以发现出现了各卖各的的情况。

接下来使用实现Runnable接口的方式:

package com.xuhang.springbootdemo.thread;

/**
* @authorxuhang
* @description
* @date2018/10/31 16:09
*/
public class RunnableTicket implements Runnable {
   private int ticketnum=5;
   @Override
   public void run() {
       for (int i = 1; i <=5; i++) {
           if(ticketnum>0){
               //Thread.currentThread().getName()获取当前进程名称
               System.out.println(Thread.currentThread().getName()+"卖票"+ticketnum--);
           }
       }
   }

   public static void main(String[] args) {
       RunnableTicket rt = new RunnableTicket();
       new Thread(rt,"售票员赵敏").start();
       new Thread(rt,"售票员小昭").start();

   }

}

使用runnable方式,可以实现资源共享,但是出现重复卖的情况

多线程的简单实现,



如何避免这些情况呢?使用关键字synchronized同步锁解决


package com.xuhang.springbootdemo.thread;

/**
* @authorxuhang
* @description
* @date2018/10/31 16:09
*/
public class RunnableTicket implements Runnable {
   private int ticketnum=100;
   @Override
   public void run() {
       for (int i = 1; i <=ticketnum; i++) {
           synchronized (this) {
               if(ticketnum>0){
//                    try {
//                        Thread.sleep(1000);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
                   //Thread.currentThread().getName()获取当前进程名称
                   System.out.println(Thread.currentThread().getName()+"卖票"+ticketnum--);
               }
           }
       }
   }

   public static void main(String[] args) {
       RunnableTicket rt = new RunnableTicket();
       new Thread(rt,"售票员赵敏").start();
       new Thread(rt,"售票员小昭").start();

   }

}

注释掉的是为力让看着比较直观,让线程睡了一秒。不然唰一下子就把票卖完啦, 此时两个线程是被cpu随机分配的。可能性能好的话一直都会是一个线程在执行。增加票数,去掉睡眠一秒钟。可以观察到。所有的票不会被多卖也不会被重复卖。

多线程的简单实现,

前面说到了同步锁的概念。那么什么是锁呢,锁就是当一个线程进入时会将当前状态锁定,直到一个线程结束后才会释放,让第二个线程使用。下面看两个不同的代码了解一下锁以及sleep和wait的区别。

package com.xuhang.springbootdemo.thread;

/**
* @authorxuhang
* @description
* @date2018/10/31 16:46
*/
public class LockTest {
   public static void main(String[] args) {
       final Object lock = new Object();
       //创建两个线程
       Runnable t1= new Runnable() {
           @Override
           public void run() {
               synchronized (lock) {
                   System.out.println(1);
                   try {
                       Thread.sleep(3000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(2);
               }
           }
       };
       Runnable t2= new Runnable() {
           @Override
           public void run() {
               synchronized (lock) {
                   System.out.println(3);
                   try {
                       Thread.sleep(3000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(4);
               }
           }
       };
       new Thread(t1).start();
       new Thread(t2).start();

   }

}

打印结果:

多线程的简单实现,

如果使用wait,则需要使用notify或者notifyAll才能唤醒。而且唤醒后的线程会去竞争cpu执行。

package com.xuhang.springbootdemo.thread;

/**
* @authorxuhang
* @description
* @date2018/10/31 16:46
*/
public class LockTest {
   public static void main(String[] args) {
       final Object lock = new Object();
       //创建两个线程
       Runnable t1= new Runnable() {
           @Override
           public void run() {
               synchronized (lock) {
                   System.out.println(1);
                   try {
                       lock.wait();//一旦线程通过wait进入等待,不会自己醒
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(2);
               }
           }
       };
       Runnable t2= new Runnable() {
           @Override
           public void run() {
               synchronized (lock) {
                   System.out.println(3);
                   try {
                       Thread.sleep(3000);
                       lock.notify();//唤醒lock
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(4);
               }
           }
       };
       new Thread(t1).start();
       new Thread(t2).start();

   }
}

打印结果:

多线程的简单实现,

sleepwait的区别

1sleepthread类的方法,waitobject的方法。

2wait不会自己醒,sleep睡完能够自己唤醒,sleep必须指定睡眠时间。

3线程执行到sleep不会释放锁,但是wait会释放锁



线程死锁


新建一个father类 

package com.xuhang.springbootdemo.thread;

/**
* @authorxuhang
* @description
* @date2018/10/31 16:53
*/
public class Father {

   public void say(){
       System.out.println("爸爸说:你做作业我就给你糖吃!");
   }
   public void get(){
       System.out.println("爸爸拿到了孩子的作业!");
   }
}


再建一个child类

package com.xuhang.springbootdemo.thread;

/**
* @authorxuhang
* @description
* @date2018/10/31 16:53
*/
public class Child {
   public void say(){
       System.out.println("孩子说:你给我糖吃我就做作业!");
   }
   public void get(){
       System.out.println("孩子拿到了糖");
   }
}


写一个死锁。

package com.xuhang.springbootdemo.thread;

/**
* @authorxuhang
* @description
* @date2018/10/31 16:51
*/
public class DeadLockTest implements Runnable {

   private static Father father = new Father();
   private static Child child = new Child();
   private boolean flag = false;


   @Override
   public void run() {
       if(flag){
           //父亲
           synchronized (father) {
               father.say();
               synchronized (child) {
                   father.get();
               }
           }
       }else
       {
           //孩子
           synchronized (child) {
               child.say();
               synchronized (father) {
                   child.get();
               }
           }
       }
   }
   public static void main(String[] args) {
       DeadLockTest d1= new DeadLockTest();
       DeadLockTest d2= new DeadLockTest();
       d1.flag=true;
       d2.flag=false;
       new Thread(d1).start();
       new Thread(d2).start();
   }

}

  运行

多线程的简单实现,


线程不会停止。相互阻塞。


我们可以使用jdk自带的Jconsole图形化界面来检测死锁。



        目前,我自己学习了解到的多线程以及线程锁的知识只到这里。更加深入的需要我们继续深入学习。希望有条件的可以来自己操作试一下。面试中差不多的公司都会问到多线程的问题。




以上是关于多线程的简单实现,的主要内容,如果未能解决你的问题,请参考以下文章

八.多进程与多线程

多线程编程

Python实现简单多线程任务队列

多个用户访问同一段代码

继承Thread类实现多线程简单实例

golang代码片段(摘抄)