进程和线程的区别
每一个计算机应用至少有一个进程,并对应一个进程号。
image线程是比进程更加细粒度,一个进程可以有多个线程。可以并发执行多个任务。
并行和并发的区别:
并行:多个CPU实例或者多个机器同时执行一段处理逻辑。真正的同时。
并发:通过CPU调度算法,让用户看上去同时执行一段处理逻辑,并不是真正的同时。
我们通过一个简单的实例了解一下多线程的威力。
image比如。我们想看一下局域网内有哪些主机是可达的。我们可以使用cmd
命令行工具,使用ping命令来看是否接通,可以看到接通的会有TTL关键字,我们就可以根据这个关键字来判断是否可达。
先用单线程实现一下。
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(43, 43, 43); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; color: rgb(169, 183, 198); font-family: "Source Code Pro"; font-size: 10pt;">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);
} } } </pre>
看一下运行结果。
image可以看到是按照顺序进行测试的。而且耗费时间特别多,如果自己操作可以不用等到结束就中断,因为太费时间了。总之单线程的缺点是:所有的操作顺序,时间长,效率低。
那么我们就来看一下多线程是什么样的。实现多线程的方式有多种。今天我们来了解继承Thread类并重写run方法和实现Runnable接口并重写run方法两种方法实现。启动一个线程是调用线程的start方法。面试中经常问到。
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(43, 43, 43); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; color: rgb(169, 183, 198); font-family: "Source Code Pro"; font-size: 10pt;">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();
}
} }</pre>
这种方法使用主函数测试时需要变一下;如下所示:
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(43, 43, 43); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; color: rgb(169, 183, 198); font-family: "Source Code Pro"; font-size: 10pt;">//测试Thread方法
for (int i = 147; i < 300; i++) {
MultiThreadByThread btt =new MultiThreadByThread("10.40.96." + i);
btt.start(); }</pre>
如果自己操作,通过控制台就可以很直观的看到质的飞跃。很快就可以执行完。
image可以看出:操作没有按照顺序,并且耗费时间大大缩减。这就是多个线程同时进行操作,不用等待前一个线程结束就可以直接执行。
另一种方式,实现Runnable接口:
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(43, 43, 43); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; color: rgb(169, 183, 198); font-family: "Source Code Pro"; font-size: 10pt;">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();
} } }</pre>
再测试一下Runnable接口的方式,主函数修改为:
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(43, 43, 43); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; color: rgb(169, 183, 198); font-family: "Source Code Pro"; font-size: 10pt;">//测试runnable方式 for (int i =147; i < 300; i++) {
MultiThreadByRunnable mtb =new MultiThreadByRunnable("10.40.96." + i);
new Thread(mtb).start(); }</pre>
通过控制台看出。效果和继承Thread类似;
image同样是无序的,在短时间内就完成了。
但是 Thread和Runnable的区别有哪些呢?
我们通过一个火车站卖火车票的实例来了解一下。
如果使用thread方式,火车票资源不共享,各卖各。
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(43, 43, 43); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; color: rgb(169, 183, 198); font-family: "Source Code Pro"; font-size: 10pt;">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(); } }</pre>
控制台输出:
image可以发现出现了各卖各的的情况。
接下来使用实现Runnable接口的方式:
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(43, 43, 43); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; color: rgb(169, 183, 198); font-family: "Source Code Pro"; font-size: 10pt;">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(); } }</pre>
使用runnable方式,可以实现资源共享,但是出现重复卖的情况
image如何避免这些情况呢?使用关键字synchronized同步锁解决
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(43, 43, 43); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; color: rgb(169, 183, 198); font-family: "Source Code Pro"; font-size: 10pt;">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(); } } </pre>
注释掉的是为力让看着比较直观,让线程睡了一秒。不然唰一下子就把票卖完啦, 此时两个线程是被cpu随机分配的。可能性能好的话一直都会是一个线程在执行。增加票数,去掉睡眠一秒钟。可以观察到。所有的票不会被多卖也不会被重复卖。
image前面说到了同步锁的概念。那么什么是锁呢,锁就是当一个线程进入时会将当前状态锁定,直到一个线程结束后才会释放,让第二个线程使用。下面看两个不同的代码了解一下锁以及sleep和wait的区别。
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(43, 43, 43); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; color: rgb(169, 183, 198); font-family: "Source Code Pro"; font-size: 10pt;">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(); } } </pre>
打印结果:
image如果使用wait,则需要使用notify或者notifyAll才能唤醒。而且唤醒后的线程会去竞争cpu执行。
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(43, 43, 43); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; color: rgb(169, 183, 198); font-family: "Source Code Pro"; font-size: 10pt;">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(); } } </pre>
打印结果:
imagesleep和wait的区别
1,sleep是thread类的方法,wait是object的方法。
2,wait不会自己醒,sleep睡完能够自己唤醒,sleep必须指定睡眠时间。
3,线程执行到sleep不会释放锁,但是wait会释放锁。
线程死锁
新建一个father类
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(43, 43, 43); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; color: rgb(169, 183, 198); font-family: "Source Code Pro"; font-size: 10pt;">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("爸爸拿到了孩子的作业!");
} } </pre>
再建一个child类
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(43, 43, 43); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; color: rgb(169, 183, 198); font-family: "Source Code Pro"; font-size: 10pt;">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("孩子拿到了糖");
} } </pre>
写一个死锁。
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(43, 43, 43); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; color: rgb(169, 183, 198); font-family: "Source Code Pro"; font-size: 10pt;">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();
} } </pre>
运行
image线程不会停止。相互阻塞。
我们可以使用jdk自带的Jconsole图形化界面来检测死锁。
image image 目前,我自己学习了解到的多线程以及线程锁的知识只到这里。更加深入的需要我们继续深入学习。希望有条件的可以来自己操作试一下。面试中差不多的公司都会问到多线程的问题。
网友评论