本文主要是想学习下java中多线程的东西。
一、理解多线程。
多线程是怎么样的机制?他是允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此之间相互独立。
线程又称为轻量级进程,它和进程一样拥有独立的执行控制,有操作系统负责调度,区别在于线程诶有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单。
多个线程的执行是并发的,也就是在逻辑上“同时”,而不管是否是物理上的“同时”,如果系统只有一个CPU,那么真正的“同时”是不可能的,但是由于CPU的速度非常快,用户感觉不到其中区别,所以不需要关心它。
二、java中多线程的实现
具体到java内存模型,由于java被设计为跨平台的语言,在内存管理上,显然也要有一个统一的模型。系统存在一个主内存,java中所有变量都存储在主存中,对所有线程都是共享的,每条线程都有自己的工作内存,工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行的,线程之间无法相互直接访问,变量传递均需要通过主存完成。
作为一个完全面向对象的语言,java提供了类java.lang.Thread来方便多线程编程。这个类提供了大量方法来方便我们控制自己的各个线程。Tread类最重要的方法是run(),它为Thread类的方法start()所调用,提供我们的线程所要执行的代码。
方法一:继承Thread类,覆盖run()方法
我们在创建的Thread类的子类中重写run()方法,加入线程所要执行的代码即可。如下:
1 public class MyThread extends Thread{ 2 3 int count = 1, number; 4 public MyThread(int num){ 5 number = num; 6 System.out.println("创建线程:" + number); 7 } 8 9 public void run(){ 10 while(true){ 11 System.out.println("线程" + number + ":计数 "+ count); 12 if(++count == 6) 13 return ; 14 } 15 } 16 17 public static void main(String[] args) { 18 for(int i = 0; i < 5; i++){ 19 new MyThread(i+1).start(); 20 } 21 } 22 }
结果如下:
创建线程:1 创建线程:2 线程1:计数 1 线程1:计数 2 线程1:计数 3 线程1:计数 4 线程1:计数 5 线程2:计数 1 线程2:计数 2 线程2:计数 3 线程2:计数 4 创建线程:3 线程2:计数 5 创建线程:4 线程3:计数 1 线程3:计数 2 线程3:计数 3 线程3:计数 4 线程3:计数 5 创建线程:5 线程4:计数 1 线程4:计数 2 线程4:计数 3 线程4:计数 4 线程4:计数 5 线程5:计数 1 线程5:计数 2 线程5:计数 3 线程5:计数 4 线程5:计数 5
这里有一点需要注意,在main函数中,用到的是start函数,而不是run函数,大家可以试一下,用run函数得到的是顺序输出。为什么会用start而不是run?有人说在调用start函数的时候会首先进行与多线程相关的初始化,然后再调用run函数,也有人说通过start的源码
public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0 || this != me) throw new IllegalThreadStateException(); group.add(this); start0(); if (stopBeforeStart) { stop0(throwableFromStop); } } private native void start0();
可以看出调用的是start0(),这个方法用了关键字native,这关键字表示调用本地操作系统的函数,因此需要用start()函数。因为多线程实现需要本地操作系统的支持。当调用run的时候系统并没有初始化多线程环境,还是在一个线程中。或者可以说,start是创建并启动一个线程,run只是运行其线程中代码。
这种方法简单明了,符合大家习惯,但是也有一个很大的缺点,那就是如果我们的类已经从一个类继承,则无法再继承Thread类,这时我们又不想建立一个新的类,那咋写?如下。
方法二:实现Runnable接口。
Runnable接口只有一个方法run(),我们声明自己的类实现Runnable接口,接口提供这一个方法,将我们的线程代码写入其中,就完成这一部分任务。
但是Runnable接口并没有任何对线程的支持,我们还必须创建Thread类的实例,这一点通过Thread类的构造函数public Thread(Runnable target)来实现。
下面是一个例子:
1 public class MyThread implements Runnable{ 2 int count = 1, number; 3 public MyThread(int num){ 4 number = num; 5 System.out.println("创建线程"+number); 6 } 7 public void run(){ 8 while(true){ 9 System.out.println("线程"+number+":计数:"+count); 10 if(++count==6) 11 return ; 12 } 13 } 14 public static void main(String[] args){ 15 for(int i = 0; i<5; i++){ 16 new Thread(new MyThread(i+1)).start(); 17 } 18 } 19 }
结果如下:
创建线程1 创建线程2 线程1:计数:1 线程1:计数:2 线程1:计数:3 创建线程3 线程2:计数:1 线程1:计数:4 线程2:计数:2 线程1:计数:5 创建线程4 线程2:计数:3 线程3:计数:1 线程3:计数:2 线程2:计数:4 线程3:计数:3 创建线程5 线程3:计数:4 线程4:计数:1 线程4:计数:2 线程4:计数:3 线程4:计数:4 线程5:计数:1 线程2:计数:5 线程5:计数:2 线程4:计数:5 线程3:计数:5 线程5:计数:3 线程5:计数:4 线程5:计数:5
严格的说,创建Thread子类的实例也是可行的,但是必须注意的是,该子类必须没有覆盖Thread类的run方法,否则该线程执行的将是子类的run方法而不是我们用以实现Runnable接口类的run方法。
使用Runnable接口来实现多线程使得我们能够在一个类中包容所有的代码,有利于封装,他的缺点在于,我们只能使用一套代码,若想创建多个线程并使各个线程执行不同的代码,则必须额外创建类,这样的话就不如直接用多个类分别继承Thread来的紧凑。
那么如何选择Runnable和Thread呢?
其实Thread也是实现Runnable的,Thread和Runnable都实现了run方法。
Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
1 class hello extends Thread { 2 public void run() { 3 for (int i = 0; i < 7; i++) { 4 if (count > 0) { 5 System.out.println("count= " + count--); 6 } 7 } 8 } 9 10 public static void main(String[] args) { 11 hello h1 = new hello(); 12 hello h2 = new hello(); 13 hello h3 = new hello(); 14 h1.start(); 15 h2.start(); 16 h3.start(); 17 } 18 19 private int count = 5; 20 }
结果如下
count= 5 count= 4 count= 5 count= 4 count= 5 count= 3 count= 2 count= 3 count= 1 count= 4 count= 2 count= 1 count= 3 count= 2 count= 1
这里很明显看出并未实现资源(count)的共享,换位Runnable
1 class MyThread implements Runnable{ 2 3 private int ticket = 5; //5张票 4 5 public void run() { 6 for (int i=0; i<=20; i++) { 7 if (this.ticket > 0) { 8 System.out.println(Thread.currentThread().getName()+ "正在卖票"+this.ticket--); 9 } 10 } 11 } 12 } 13 public class lzwCode { 14 15 public static void main(String [] args) { 16 MyThread my = new MyThread(); 17 new Thread(my, "1号窗口").start(); 18 new Thread(my, "2号窗口").start(); 19 new Thread(my, "3号窗口").start(); 20 } 21 }
结果如下
2号窗口正在卖票5
2号窗口正在卖票2
2号窗口正在卖票1
3号窗口正在卖票4
1号窗口正在卖票3
总结如下:
实现Runnable接口比继承Thread接口有优势:
1.适合多个相同的程序代码的线程去处理同一个资源
2.可以避免java中的单继承的限制
3.增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
总之,对于多线程,虽然我用的不多,但是对于找工作或者啥的还是非常有用处的。这只是基本的多线程问题,以后还要深入研究多线程的细节东西。这算是入门吧。