多线程是什么?
要说线程, 我们最好先提出并发这个概念. 或许我们已经无数次听到用车道比喻cpu个数,车道上运行的车比作线程的例子, 在这里, 我还是不厌其烦的重述这个经典的例子, 来加深任何看到这篇博文,并且不了解并发技术应用背景的人对多线程的理解. 我们将看到, 并发技术和多核CPU(Chip Multiprocessors)的发展最大程度的实现了宽发射、乱序的超标量处理, 以此获得的性能提升是现在一切盛行的服务和技术的基础. 有计算机的地方, 就有并发运算, 而多线程技术(Simultaneous Multithreading)就就是实现了并发运算的技术.
如果将计算机CPU的个数比作车道的个数, 在程序运行中,我们可以分出以下几种情况:
- 车很少,车道没有被占满.
- 车数量刚好占满车道.
- 车很多,有些车道有多量车,但是或许还有空间跑更多的车.
- 车非常多,即使把路塞满,也放不下, 有的车不得不在天上飘着.
这里的车道就是CPU核心数,一个车道上的车就是一个线程. 有的车跑的快,有的车跑的慢,我们假设一个车道一次只能用一辆车作为全部车的速度,那么当然让跑的快的先跑,跑的慢的后跑. 有时为了公平起见,可能约定一辆车轮流跑固定的时间,再轮到其他车.
有的人可能会问: 进程到哪里去了? 程序不都是进程吗?
答: 进程是一个封地(CPU和内存)的领主,他是所有资源和名誉的所有者,但是他不自己干活.我们这里讲的是真正在干活的劳动人民, 请不要提它.
多线程有什么用?
从上面的例子我们已经看到, CPU多个核心并行执行,一个CPU核心内多个线程在一定时间内并发执行.
并行是真正意义上的同时发生的多件事,并发是一段时间内轮流发生的多件事.
由此,一个程序内,一心可以多用.
程序并发的进行后台计算,向用户发送数据,接受用户的指令,向网络发送数据、接受数据.
线程带来的并发真正的让一个应用程序有手有脚,能说会跳.
由此拓展,线程使代码模块化,异步化,简单化,这些特点我会在其他微服务的博文中体现.
多线程带来的问题
进程之间内存是隔离的,线程内部内存是共享的.我们都知道共享就意味着抢占与无序. 抢占与无序使得程序的结果不再可预测,我们需要加锁避免. 由此死锁和活锁问题也随之而生,可见线程不是没有代价的.
线程带来了很多问题,也衍生了很多技术. 例如,线程对资源的争抢,促使我们在任何需要发送、接受、使用资源的地方都放一个池或者缓冲区,而这是让程序受益匪浅的. 当一个机器无论如何都运行不过来时,我们需要分布式计算, 这也就促生的大数据的处理能力. 无论如何,线程是一个好的开端.
Java多线程的三种实现方式
线程被创建后,用start方法启用线程,则线程不一定立即执行,而是交给cpu进行调度.线程大致有三种实现方式,第一种只做学习用,第二种较为简单,第三种更成熟.
方式一: 继承Thread类(实际上不使用)
Java是单继承的, 继承了Thread啥也别干了.
一个数数程序
public class MyFirstThread extends Thread{
int id;
MyFirstThread(int id){
this.id = id;
}
public void run(){
for(int i =0;i<2000;i++){
System.out.println("This is number "+ id+". Time: "+i);
}
}
public static void main(String[] args) {
MyFirstThread thread1 = new MyFirstThread(1);
MyFirstThread thread2 = new MyFirstThread(2);
thread1.start();
thread2.start();
}
}
注意:这里若使用对象的run方法,将按照先后顺序执行,不会使用多线程.
一个下载图片程序
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.net.URL;
class ThreadDownLoader {
public void downloader(String url,String name) throws IOException {
FileUtils.copyURLToFile(new URL(url),new File(name));
}
}
public class ThreadWithDownLoad extends Thread{
private String url;
private String name;
public ThreadWithDownLoad(String url,String name){
this.url=url;
this.name=name;
}
public void run(){
ThreadDownLoader webdownloader =new ThreadDownLoader();
try {
webdownloader.downloader(url,name);
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载失败");
}
}
public static void main(String[] args) {
ThreadWithDownLoad IamDownLoading1 =
new ThreadWithDownLoad(
"https://i2.hdslb.com/bfs/archive/e961e1c53d9946d4e67cab442e33a71506278757.png@672w_378h_1c.webp",
"picture1.jpg");
ThreadWithDownLoad IamDownLoading2 =
new ThreadWithDownLoad(
"https://i2.hdslb.com/bfs/archive/e961e1c53d9946d4e67cab442e33a71506278757.png@672w_378h_1c.webp",
"picture2.jpg");
ThreadWithDownLoad IamDownLoading3 =
new ThreadWithDownLoad(
"https://i2.hdslb.com/bfs/archive/e961e1c53d9946d4e67cab442e33a71506278757.png@672w_378h_1c.webp",
"picture3.jpg");
IamDownLoading1.start();
IamDownLoading2.start();
IamDownLoading3.start();
}
}
方法二: 用实现Runnable的对象作为Thread的构造参数
//实现Runnable接口
//重写run方法
//用thread创建带有runnable参数的对象
public class ThreadInitIpRunnable implements Runnable{
int id;
ThreadInitIpRunnable(int id){
this.id = id;
}
public void run(){
for(int i =0;i<2000;i++){
System.out.println("This is number "+ id+". Time: "+i);
}
}
public static void main(String[] args) {
ThreadInitIpRunnable threadR1 = new ThreadInitIpRunnable(1);
ThreadInitIpRunnable threadR2 = new ThreadInitIpRunnable(2);
//用Runnable当参数构造Thread
Thread thread1 = new Thread(threadR1);
Thread thread2 = new Thread(threadR2);
thread1.start();
thread2.start();
}
}
####一个抢票程序
public class BuySomeTickets implements Runnable {
ticket t = new ticket(100);
public void run(){
while(true){
if(t.getNum()<=0)break;
System.out.println(Thread.currentThread().getName()+"拿到了第"+t.getNum()+"张票。");
t.getTicket();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
BuySomeTickets tickets = new BuySomeTickets();
new Thread(tickets,"王二虎").start();
new Thread(tickets,"大黑牛").start();
new Thread(tickets,"奔波霸").start();
}
}
class ticket{
private static int num=100;
ticket(int num){this.num=num;}
public void getTicket(){num--;}
public int getNum(){return num;}
}
如果让你实现这个程序,结果会是什么呢? 一张票会不会被重复拿?
线程是不安全的,我们需要一些措施防止这些问题.