一.使用多线程的两种方法
使用多线程的两种方法有:继承Thread类和实现runable接口。
二.继承Thread类
来看一下thread类的源代码:
class Thread implements Runnable {
首先可以看出thread类也是实现Runable接口的run方法如下:
public void run() { if (target != null) { target.run(); } }
下面就是一个创建继承Thread的类的列子:
public class ExThreadText extends Thread { @Override public void run(){ for(int i=0;i<20;i++){ System.out.println("我自己创建的线程"); } } public static void main(String[] args) { new ExThreadText().start(); System.out.println("程序结束!"); } }
首先我们需要明白在这个程序里面有多少个线程?应该是两个线程一个是main方法的线程一个是我run方法里面的一个线程
从结果可以看出这两个线程的调用和创建的顺序是无关的,
在这个代码实例里面我们重写了run方法,并使用 start 方法来调用,那为什么不用run方法来调用呢?我们来看看run方法调用会有什么结果:
可以看出现在的两个"线程"已经是按照顺序执行的了,其实现在并不是多线程,就是一个单线程按照流程来执行。
总结:1.多线程的调用是无序的
2.多线程需要使用start方法来调用而不是run方法,同样start方法来调用线程也是无序的
三.使用runable接口来实现多线程
由于Java不提供多继承,所以当我们继承了Thread类的时候我们就不能继承其它的父类了,为了解决这一个问题,我建议实现runable接口。
首先继承runable接口必须重写他的run方法,代码如下:
public class ImRunableText implements Runnable { @Override public void run(){ //重写run方法 } }
那怎么使用这个runable接口的子类呢?
我们来看看Thread类的构造方法:
可以看出Thread类的构造器可以接受实现Runable接口的子类对象(不要忘了thread类也是实现runable接口的),同时他还重载了一个构造器可以为线程命名。
那么我们就可以使用这个runable的实现子类了,代码如下:
public class ImRunableText implements Runnable { @Override public void run(){ //重写run方法 for(int i=0;i<10;i++){ System.out.println("run方法"); } } public static void main(String[] args) { Thread thread =new Thread(new ImRunableText(),"线程"); thread.start(); System.out.println("结束了"); } }
四.线程中的数据共享和线程安全
4.1数据不共享
数据不共享那就是一个线程一个数据,单独执行互不影响。代码如下:
这是一个线程类,他有自己的字段num为10。
public class DataNShare extends Thread{ private int num =10; private String name; public DataNShare(String name){ this.name=name; } @Override public void run(){ for(;num>0;){ System.out.println("当前线程为:"+name); System.out.println("num值为"+num); num--; } } }
在设置一个测试类,创建三个对象,各自进行测试代码如下:
public class Text { public static void main(String[] args) { DataNShare d1=new DataNShare("线程1"); DataNShare d2=new DataNShare("线程2"); DataNShare d3=new DataNShare("线程3"); d1.start(); d2.start(); d3.start(); } }
测试结果:可以看出一开始是没有问题的,但是在后面出现了乱序的情况。
那么出现了乱序的情况是不是就一定证明了程序出错了呢?
我们来改进一下这个DataNShare类中的Run方法
public void run(){ for(int i =1;num>=0;i++){ System.out.println("当前线程为:"+name); System.out.println("num值为"+num); num--; if(num==0){ System.out.println("*************i的值为"+i+"*************"); } } }
那么也就是说当我的 i 值输出每一次输出10那么就是代表每一条线程都是执行互不影响的。
*3,虽然还是存在乱序的情况,但是至少保证我们的每一条线程执行都是10次没有问题的。那么出现乱序的情况就是输出语句的问题。
4.2数据共享
数据共享就是多个线程可以访问一个数据,代码如下:
放有共享数据的线程类:
public class DataShare extends Thread { private int num=3;//共享数据 @Override public void run(){ num--;//共享数据减一 System.out.println("当前线程为:"+this.currentThread().getName()+"num值为:"+num); } }
处理类:
public class Text { public static void main(String[] args) { //将共享数据放入3个线程里进行处理 DataShare d=new DataShare(); Thread t1=new Thread(d,"t1"); Thread t2=new Thread(d,"t2"); Thread t3=new Thread(d,"t3"); t1.start(); t2.start(); t3.start(); } }
在这里出现的这种情况就是线程不安全状态。那么怎么解决这个问题呢?
使用关键字synchronized(同步化的)来解决。
当一个方法使用该关键字那么在多个线程执行这个方法时,每一个线程获得执行该方法的执行权就会把这个方法上锁结束后开锁,只有等到这个方法没有被上锁时才可以被其他线程运行。
看看改进后的代码:
public class DataShare extends Thread { private int num=3;//共享数据 @Override synchronized public void run(){ num--;//共享数据减一 System.out.println("当前线程为:"+this.currentThread().getName()+"num值为:"+num); } }
总结:当多个线程在共享一个数据时,可能会造成线程异常,应该使用关键字synchronized来实现同步化,在后面还会深入了解同步化。