java中的多线程入门

本文主要是想学习下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.增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。

总之,对于多线程,虽然我用的不多,但是对于找工作或者啥的还是非常有用处的。这只是基本的多线程问题,以后还要深入研究多线程的细节东西。这算是入门吧。

原文地址:https://www.cnblogs.com/Pillar/p/4218756.html