线程Thread与Runnable接口

之前一直是懵懂的使用Thread,对这个并没有太完整的概念。最近看了一些资料并在小项目中使用到了一些相关技术,现结合实际应用与查阅到的资料,聊着总结。

一、首先,线程和线程类的区别:

线程是硬件资源CPU调度任务执行的最小单元,是一个抽象的概念;线程类本质上就是一串可执行的代码,在Java中就是Thread.class文件。

Thread线程的定义如下

public class Thread extends Object implements Runnable;

从 Thread 类的定义可以清楚的发现,Thread 类也是 Runnable 接口的子类,但在Thread类中并没有完全实现 Runnable 接口中的 run() 方法,下面是 Thread 类的部分定义:

Private Runnable target; 
public Thread(Runnable target,String name){ 
    init(null,target,name,0); 
} 
private void init(ThreadGroup g,Runnable target,String name,long stackSize){ 
    ... 
    this.target=target; 
} 
public void run(){ 
    if(target!=null){ 
        target.run(); 
    } 
}

从定义中可以发现,在 Thread 类中的 run() 方法调用的是 Runnable 接口中的 run() 方法,也就是说此方法是由 Runnable 子类完成的,所以如果要通过继承 Thread 类实现多线程,则必须覆写 run()。
实际上 Thread 类和 Runnable 接口之间在使用上也是有区别的:

如果一个类继承 Thread类,则不适合于多个线程共享资源,而实现了 Runnable 接口,就可以方便的实现资源的共享。详情如下:
public class MyThread extends Thread {
    private int breakfast = 5;
    private String name;
    public MyThread(String name){
        this.name = name;
    }
    @Override
    public void run(){
        for(int i = 0;i < 10; i++){
            if(breakfast > 0){
            	System.out.println("run: " + Thread.currentThread().getName() + "卖火车票---->" + (this.breakfast--));
            } else {
                break;
            }
        }
    }
    public static void main(String[] args) {
        MyThread mt1= new MyThread("一号窗口");
        MyThread mt2= new MyThread("二号窗口");
        MyThread mt3= new MyThread("三号窗口");
        mt1.start();
        mt2.start();
        mt3.start();
    }
}

运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1QnMWfbW-1589613065361)(https://img-blog.csdn.net/20171121170007986?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGFveXVlZ29uZ3pp/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)]

public class MyRunnable implements Runnable {
    private int breakfast = 5;
    private String name;
    @Override
    public void run(){
        for(int i = 0;i < 50; i++){
            if(this.breakfast > 0){
            	System.out.println("run: " + Thread.currentThread().getName() + "卖火车票---->" + (this.breakfast--));
            }else {
                break;
            }
        }
    }
    public static void main(String[] args) {
        //设计三个线程
        MyRunnable mt = new MyRunnable();
        Thread t1 = new Thread(mt,"一号窗口");
        Thread t2 = new Thread(mt,"二号窗口");
        Thread t3 = new Thread(mt,"三号窗口");
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1dj0TC01-1589613065362)(https://img-blog.csdn.net/20171121170314277?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGFveXVlZ29uZ3pp/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)]

很显然的,这两个运行结果相差甚远,这是为什么呢?现在我们来仔细分析下:

第一个结果是通过继承Thread来实现的,在执行前,通过关键字“new”创建了三个对象,并且这三个对象是相互独立的,彼此之间没有任何关联,因此在运行过程中就是各做各作,各自5张售出火车票,实际上就售出了15张火车票,这在实际中显然是矛盾的,相同的票怎么能出售三次呢?这就是数据无法共享引起的。

第二个结果是通过实现Runnable接口来实现的,通过关键字new 一个MyRunnable对象,然后再new三个线程,让这三个线程共同执行卖5张票的任务。有效避免了前面基础Thread引发的矛盾。

因此:如果一个类继承 Thread类,则不适合于多个线程共享资源,而实现了 Runnable 接口,就可以方便的实现资源的共享。

二、线程的5种状态:

任何线程一般具有5种状态,即创建,就绪,运行,阻塞,终止。下面分别介绍一下这几种状态:

创建状态 
在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新建一个线程对象可采用Thread 类的构造方法来实现,例如 “Thread thread=new Thread()”。

就绪状态 
新建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。

运行状态 
当就绪状态被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run() 方法。run() 方法定义该线程的操作和功能。

阻塞状态 
一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入/输出操作,会让 CPU 暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(),suspend(),wait() 等方法,线程都将进入阻塞状态,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。

死亡状态 
线程调用 stop() 方法时或 run() 方法执行结束后,即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。

在此提出一个问题,Java 程序每次运行至少启动几个线程?

回答:至少启动两个线程,每当使用 Java 命令执行一个类时,实际上都会启动一个 JVM,每一个JVM实际上就是在操作系统中启动一个线程,Java 本身具备了垃圾的收集机制。所以在 Java 运行时至少会启动两个线程,一个是 main 线程,另外一个是垃圾收集线程。

三、取得和设置线程的名称:

class MyThread implements Runnable{
    public void run(){ 
		System.Out.Println(Thread.currentThread().getName());
    } 
}; 
public class ThreadDemo{ 
	public static void main(String args[]){ 
	    MyThread my=new MyThread();  //定义Runnable子类对象 
	    new Thread(my).start;    //系统自动设置线程名称 
	    new Thread(my,"线程A").start();  //手工设置线程名称 
	} 
};

四、线程的强制运行
在线程操作中,可以使用 join() 方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。

class MyThread implements Runnable{ 
    public void run(){  // 覆写run()方法 
        System.out.println(Thread.currentThread().getName()) ;
    } 
}; 
public class ThreadJoinDemo{ 
    public static void main(String args[]){ 
       MyThread mt = new MyThread() ;  // 实例化Runnable子类对象 
       Thread t = new Thread(mt,"线程");     // 实例化Thread对象 
       t.start() ; // 启动线程 
	   try{ 	    
		   t.join() ;  // 线程强制运行 
		}catch(InterruptedException e){
		} 
    } 
};

五、线程的休眠
在程序中允许一个线程进行暂时的休眠,直接使用 Thread.sleep() 即可实现休眠。

class MyThread implements Runnable{ // 实现Runnable接口 
    public void run(){  // 覆写run()方法
		try{ 
			Thread.sleep(500) ; // 线程休眠 
		}catch(InterruptedException e){
		} 
		System.out.println(Thread.currentThread().getName());
    } 
}; 
public class ThreadSleepDemo{ 
    public static void main(String args[]){ 
        MyThread mt = new MyThread() ;  // 实例化Runnable子类对象 
        Thread t = new Thread(mt,"线程");     // 实例化Thread对象 
        t.start() ; // 启动线程 
    } 
};

六、中断线程
当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。

class MyThread implements Runnable{ // 实现Runnable接口 
    public void run(){  // 覆写run()方法 
        try{ 
            Thread.sleep(10000) ;   // 线程休眠10秒 
        }catch(InterruptedException e){ 
            return ; // 返回调用处 
        } 
    } 
}; 
public class ThreadInterruptDemo{ 
    public static void main(String args[]){ 
        MyThread mt = new MyThread() ;  // 实例化Runnable子类对象 
        Thread t = new Thread(mt,"线程");     // 实例化Thread对象 
        t.start() ; // 启动线程 
        try{ 
            Thread.sleep(2000) ;    // 线程休眠2秒 
        }catch(InterruptedException e){ 
        } 
    } 
};

七、后台线程
在 Java 程序中,只要前台有一个线程在运行,则整个 Java 进程都不会消失,所以此时可以设置一个后台线程,这样即使 Java 线程结束了,此后台线程依然会继续执行,要想实现这样的操作,直接使用 setDaemon() 方法即可。

class MyThread implements Runnable{ // 实现Runnable接口 
    public void run(){  // 覆写run()方法 
        while(true){ 
            System.out.println(Thread.currentThread().getName() + "在运行。") ; 
        } 
    } 
}; 
public class ThreadDaemonDemo{ 
    public static void main(String args[]){ 
        MyThread mt = new MyThread() ;  // 实例化Runnable子类对象 
        Thread t = new Thread(mt,"线程");     // 实例化Thread对象 
        t.setDaemon(true) ; // 此线程在后台运行 
        t.start() ; // 启动线程 
    } 
};

八、线程的优先级
在 Java 的线程操作中,所有的线程在运行前都会保持在就绪状态,那么此时,哪个线程的优先级高,哪个线程就有可能会先被执行。

class MyThread implements Runnable{ // 实现Runnable接口 
    public void run(){  // 覆写run()方法  
	    System.out.println(Thread.currentThread().getName()) ; //取得当前线程的名字 
    } 
}; 
public class ThreadPriorityDemo{ 
    public static void main(String args[]){ 
        Thread t1 = new Thread(new MyThread(),"线程A") ;  // 实例化线程对象 
        Thread t2 = new Thread(new MyThread(),"线程B") ;  // 实例化线程对象 
        Thread t3 = new Thread(new MyThread(),"线程C") ;  // 实例化线程对象 
        t1.setPriority(Thread.MIN_PRIORITY) ;   // 优先级最低 
        t2.setPriority(Thread.MAX_PRIORITY) ;   // 优先级最高 
        t3.setPriority(Thread.NORM_PRIORITY) ;  // 优先级最中等 
        t1.start() ;    // 启动线程 
        t2.start() ;    // 启动线程 
        t3.start() ;    // 启动线程 
    } 
};

线程将根据其优先级的大小来决定哪个线程会先运行,但是需要注意并非优先级越高就一定会先执行,哪个线程先执行将由 CPU 的调度决定。

九、线程的礼让
在线程操作中,也可以使用 yield() 方法将一个线程的操作暂时让给其他线程执行

class MyThread implements Runnable{ // 实现Runnable接口 
    public void run(){  // 覆写run()方法 
        for(int i=0;i<5;i++){ 
            try{      
	            Thread.sleep(500) ;   
	        }catch(Exception e){		
	        } 			     
	       System.out.println(Thread.currentThread().getName());     
	       if(i==2){  
		       Thread.currentThread().yield() ;    // 线程礼让  
	       } 
        } 
    } 
}; 
public class ThreadYieldDemo{ 
    public static void main(String args[]){ 
        MyThread my = new MyThread() ;  // 实例化MyThread对象 
        Thread t1 = new Thread(my,"线程A") ; 
        Thread t2 = new Thread(my,"线程B") ; 
        t1.start() ;         t2.start() ; 
    } 
};

十、同步以及死锁
一个多线程的程序如果是通过 Runnable 接口实现的,则意味着类中的属性被多个线程共享,那么这样就会造成一种问题,如果这多个线程要操作同一个资源时就有可能出现资源同步问题。

1、synchronized(同步对象){ 需要同步的代码 }

class MyThread implements Runnable{ 
    private int ticket = 5 ;    // 假设一共有5张票 
    public void run(){ 
        for(int i=0;i<100;i++){ 
            synchronized(this){ // 要对当前对象进行同步 
                if(ticket>0){   // 还有票 
                    System.out.println("卖票:ticket = " + ticket-- ); 
                } 
            } 
        } 
    } 
}; 
public class SyncDemo02{ 
    public static void main(String args[]){ 
        MyThread mt = new MyThread() ;  // 定义线程对象 
        Thread t1 = new Thread(mt) ;    // 定义Thread对象 
        Thread t2 = new Thread(mt) ;    // 定义Thread对象 
        t1.start() ;         t2.start() ; 
    } 
};

2、同步方法:最常用的例子就是单例。

Public static synchronized class a{
	Public a instance;
	Public a getInstance(){
		If (instance == null){	
			Instance = new a();	
		}
		Return instance;
	}
}

好了,关于线程的问题,先总结这么多了,以后遇到新的问题在继续完善。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值