一. 使用多线程的两种方法
使用多线程的两种方法有: 继承 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 来实现同步化, 在后面还会深入了解同步化.
来源: https://www.cnblogs.com/SAM-CJM/p/9782062.html