多线程的简单实现,
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;
/**
* @author:xuhang
* @description:
* @date:2018/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;
/**
* @author:xuhang
* @description:通过继承Thread类实现多线程。
* @date:2018/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;
/**
* @author:xuhang
* @description:通过实现Runnable接口并重写run方法实现多线程
* @date:2018/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;
/**
* @author:xuhang
* @description:
* @date:2018/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;
/**
* @author:xuhang
* @description:
* @date:2018/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;
/**
* @author:xuhang
* @description:
* @date:2018/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;
/**
* @author:xuhang
* @description:
* @date:2018/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;
/**
* @author:xuhang
* @description:
* @date:2018/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();
}
}
打印结果:
sleep和wait的区别
1,sleep是thread类的方法,wait是object的方法。
2,wait不会自己醒,sleep睡完能够自己唤醒,sleep必须指定睡眠时间。
3,线程执行到sleep不会释放锁,但是wait会释放锁。
线程死锁
新建一个father类
package com.xuhang.springbootdemo.thread;
/**
* @author:xuhang
* @description:
* @date:2018/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;
/**
* @author:xuhang
* @description:
* @date:2018/10/31 16:53
*/
public class Child {
public void say(){
System.out.println("孩子说:你给我糖吃我就做作业!");
}
public void get(){
System.out.println("孩子拿到了糖");
}
}
写一个死锁。
package com.xuhang.springbootdemo.thread;
/**
* @author:xuhang
* @description:
* @date:2018/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图形化界面来检测死锁。
目前,我自己学习了解到的多线程以及线程锁的知识只到这里。更加深入的需要我们继续深入学习。希望有条件的可以来自己操作试一下。面试中差不多的公司都会问到多线程的问题。
以上是关于多线程的简单实现,的主要内容,如果未能解决你的问题,请参考以下文章