JavaSE学习52:细说多线程之Thread类和Runable接口

一线程创建的两种方式比较

线程创建和启动有两种方式,这里只是列出步骤,不再进行详细解释。

(1)继承Thread类

class MyThread extends Thread{
     public void run(){
         ...
     }
}

MyThread mt=new MyThread();//创建线程
mt.start();//启动线程

(2)实现Runnable接口

class MyThread implements Runnable{
     public void run(){
        ...
     }
}

MyThread mt=new MyThread();
Thread td=new Thread(mt);//创建线程
td.start();//启动线程

(3)两种方式的比较

1)Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷。

2)Runnable方式的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况。

二模拟应用场景

模拟一个火车站买票的场景,某车次还剩下5张火车票,有三个窗口去卖这5张火车票,我们使用三个线程模拟三

个窗口同时卖这5张火车票,我们看Thread方式和Runnable方式这两种方式模拟出一个什么样的结果。

(1)使用Thread方式模拟买票

TicketsThread.java源文件代码:

class MyThread extends Thread{
	//一共有五张火车票
	private int ticketsCount = 5;
	//窗口,也就是线程的名字
	private String name;

	//构造方法
	public MyThread(String name){
		this.name = name;
	}

	public void run(){
		while(ticketsCount > 0){
			//如果还有票,就卖掉一张
			ticketsCount--;
			System.out.println(name+"卖了1张票,剩余票数为:"+ticketsCount);
		}
	}
}

public class TicketsThread{
	public static void main(String[] args){
		//创建三个线程,模拟三个窗口买票
		MyThread mt1 = new MyThread("窗口1");
		MyThread mt2 = new MyThread("窗口2");
		MyThread mt3 = new MyThread("窗口3");

		//启动三个线程,也就是窗口开始卖票
		mt1.start();
		mt2.start();
		mt3.start();
	}
}

运行结果:

得到的结果并不是我们想要的结果。

在票的数量加static修饰关键字得到的结果是正确的。这里不再进行演示。

(2)使用Runnable方式模拟买票

TicketsRunnable.java源文件代码:

class MyThread1 implements Runnable{
	//一共有五张火车票
	private int ticketsCount = 5;

	public void run(){
		while(ticketsCount > 0){
			//如果还有票,就卖掉一张
			ticketsCount--;
			System.out.println(Thread.currentThread().getName()+"卖了1张票,剩余票数为:"+ticketsCount);
		}
	}
}

public class TicketsRunnable{
	public static void main(String[] args){
		MyThread1 mt = new MyThread1();
		//创建三个线程,模拟三个窗口买票
		Thread th1 = new Thread(mt,"窗口1");
		Thread th2 = new Thread(mt,"窗口2");
		Thread th3 = new Thread(mt,"窗口3");

		//启动三个线程,也就是窗口开始卖票
		th1.start();
		th2.start();
		th3.start();
	}
}

得到了预期的结果:

(3)结果分析

由于线程的执行是随机的,打印的结果也是随机的。

Thread方式

三个线程,创建了三个Thread对象,每个线程都有自己的Thread对象,都有自己的ticketsCount变量,它们三个

线程并不是共享ticketsCount变量,也就是每个线程都可以卖出5张火车票,即三个窗口卖出去15张火车票。

Runnable方式

三个线程共用一个Runnable对象,也就是三个线程共用一个ticketsCount变量,即三个窗口一共卖了5张火车票。

前者不是一个线程三个对象,是三个Thread对象,也是三个线程,这三个线程启动后都会执行5次卖票,实现不

了共享“5张票”这个资源,所以输出就会有15张票卖出去,显然不符合实际,用Runnable就可以解决这个问题,创建

的三个线程可以共享"5张票"这个资源。

ticketsCont变量是实例变量,它的值自然是存在堆中(每个java对象在堆中都会占据一定内存,而实例变量的值就

是存储在这块内存中,类似于结构体,因此每个对象对应一个ticketsCont的值),ticketsCont跟值传递没有关系啊,如

果是Runnable方式的话,传递的也只是MyThread对象引用的副本,不管ticketsCont的事,但是因为ticketsCont的值

在引用和引用副本所指向的堆内存中,所以无论是引用还是引用副本改变了堆内存中ticketsCont的值,都会产生效

果!

这个根据你的需要来操作,这样说吧,如果有一个比较大的资源要你下载,那么你用Thread方式那么你就只能一

个线程去吧这个资源下载完,如果是runable方式的话你就可以 多new几个子线程来出来,通过共享runable对象里面

的资源来用多个子线程来下载这个资源,这样的话,下载资源的时候 runable方法会使下载的线程多一些几率在cpu里

面,也会让你下载速度变快继承Thread类是多个线程分别完成自己的任务,实现Runnable接口是多个线程共同完成

一个任务。

三线程的生命周期

线程的生命周期转换示意图:

线程的生命周期:

1)创建:新建一个线程对象,如Thread thd=new Thread()。

2)就绪:创建了线程对象后,调用了线程的start()方法(注意:此时线程只是进入了线程队列,等待获取CPU服

务,具备了运行的条件,但并不一定已经开始运行了)。

3)运行:处于就绪状态的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()方法里面的逻辑。

4)阻塞:一个正在执行的线程在某些情况下,由于某种原因而暂时让出了CPU资源,暂停了自己的执行,便进入

了阻塞状态,如调用了sleep()方法。

5)终止:线程的run()方法执行完毕,或者线程调用了stop()方法,线程便进入终止状态。

这里我们可以用一个经典的线问题就是生产者和消费者问题的实例:

import java.util.*;

public class ProducerConsumer{
	public static void main(String[] args){
		SyncStack ss = new SyncStack();
		Producer p = new Producer(ss);
		Consumer c = new Consumer(ss);

		new Thread(p).start();
		new Thread(c).start();
	}
}

//生产与消费对象
class WoTou{
	int id; 

	//构造方法
	WoTou(int id){
		this.id = id;
	}

	//重写toString()方法
	public String toString(){
		return "WoTou : " + id;
	}
}

//容器类
class SyncStack{
	int index = 0;
	WoTou[] arrWT = new WoTou[4];

	public synchronized void push(WoTou wt){
		while(index == arrWT.length){
			try{
				//这里的wait()方法指的是Object类中的方法
				this.wait();
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		//这里的notifyAll()方法指的是唤醒所有线程,而notify()方法唤醒一个线程
		this.notifyAll();
		arrWT[index] = wt;
		index ++;
	}

	public synchronized WoTou pop(){
		while(index == 0){
			try{
				//这里的wait()方法指的是Object类中的方法
				this.wait();
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		//这里的notifyAll()方法指的是唤醒所有线程,而notify()方法唤醒一个线程
		this.notifyAll();
		index--;
		return arrWT[index];
	}
}

//生产者
class Producer implements Runnable{
	SyncStack ss = null;

	Producer(SyncStack ss){
		this.ss = ss;
	}

	public void run(){
		for(int i=0; i<20; i++){
			WoTou wt = new WoTou(i);
			ss.push(wt);
			System.out.println("生产了:" + wt);
			try{
				Thread.sleep((int)(Math.random() * 200));
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
}

//消费者
class Consumer implements Runnable{
	SyncStack ss = null;

	Consumer(SyncStack ss){
		this.ss = ss;
	}

	public void run(){
		for(int i=0; i<20; i++){
			WoTou wt = ss.pop();
            System.out.println("消费了: " + wt);
			try{
				Thread.sleep((int)(Math.random() * 1000));
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
}

运行结果:

关于一些问题的解析:

执行线程sleep()方法是依然占着cpu的,操作系统认为该当前线程正在运行,不会让出系统资源。

执行wait()方法是让线程到等待池等待,让出一系列的系统资源,其他线程可以根据调度占用cpu线程的资源有不

少,但应该包含CPU资源和锁资源这两类。

sleep(long mills):让出CPU资源,但是不会释放锁资源。

wait():让出CPU资源和锁资源。

锁是用来线程同步的,sleep(long mills)虽然让出了CPU,但是不会让出锁,其他线程可以利用CPU时间片了,但

如果其他线程要获取sleep(long mills)拥有的锁才能执行,则会因为无法获取锁而不能执行,继续等待。但是那些没有

和sleep(long mills)竞争锁的线程,一旦得到CPU时间片即可运行了。

四守护线程

(1)守护线程理论知识

Java线程分为两类:

1)用户线程:运行在前台,执行具体的任务。程序的主线程、连接网络的子线程等都是用户线程。

2)守护线程:运行在后台,为其他前台线程服务。守护线程的特点是一旦所有用户线程都结束运行,守护线程会

随JVM一起结束工作。守护线程的应用:数据库连接池中的检测线程和JVM虚拟机启动后的检测线程等等。最常见的

守护线程:垃圾回收线程

设置守护线程:

可以通过调用Thread类的setDaemon(true)方法来设置当前的线程为守护线程。

使用守护线程的注意事项:

1)setDaemon(true)必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常。

2)在守护线程中产生的新线程也是守护线程。

3)不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑。

(2)守护线程代码示例:

import java.io.*;
import java.util.*;

class DaemonThread implements Runnable{
	public void run(){
		System.out.println("进入守护线程"+Thread.currentThread().getName());
		try{
			writeToFile();
		}catch(Exception e){
			e.printStackTrace();
		}
		System.out.println("退出守护线程");
	}

	private void writeToFile() throws Exception{
		File filename = new File("E:\\Java\\JavaSE\\Thread"+File.separator+"daemon.txt");
		OutputStream os = new FileOutputStream(filename,true);
		int count = 0;
		while(count < 999){
			os.write(("\r\nword"+count).getBytes());
			System.out.println("守护线程"+Thread.currentThread().getName()+"向文件中写入了word"+ count++);
			Thread.sleep(1000);
		}
	}
}

public class DaemonThreadDemo{
	public static void main(String[] args){
		System.out.println("进入主线程"+Thread.currentThread().getName());

		DaemonThread daemonThread = new DaemonThread();
		Thread thread =new Thread(daemonThread);
		thread.setDaemon(true);
		thread.start();

		Scanner sc = new Scanner(System.in);
		sc.next();

		System.out.println("退出主线程"+Thread.currentThread().getName());
	}
}

运行结果:

(3)使用jstack生成线程快照

作用:生成JVM当前时刻线程的快照(threaddump,即当前进程中所有线程的信息)。

目的:帮助定位程序问题出现的原因,如长时间停顿、CPU占用率高等。

jstack命令行工具

你安装JDK安装目录下的bin文件夹下:

位置:E:\Java\develop\jdk1.8.0_25\bin

如何使用jstack

在cmd中输入:jstack

我们去任务管理器中找到一个进程的PID:

生成快照:

五总结

(1)怎样解决死锁的问题

1)尽量避免不必要的synchronized关键字。

2)可以用其他方法替换synchronized关键字,比如标志不可变量。

3)保证synchronized代码块简练。

(2)创建线程的建议

根据两种创建线程方法的比较,得出的结论是使用实现Runnable接口的方法创建的多线程更好一些,另外,就是

需要重点注意,程序中的同一个资源指的是同一个Runnable对象。建议多使用Runnable这种方式创建多线程。

(3)补充:

1)程序中的同一资源指的是同一个Runnable对象。

2)安全的卖票程序中需要加入同步(Synchronized)。我们的代码过程中并没有加入,如果需要完善,就必须加入

同步锁。

时间: 2024-10-05 11:47:23

JavaSE学习52:细说多线程之Thread类和Runable接口的相关文章

Java 多线程——Thread类和Runable接口

在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口:Thread类是在java.lang包中定义的.一个类只要继承了Thread类同时覆写了本类中的run()方法就可以实现多线程操作了,但是一个类只能继承一个父类,这是此方法的局限, 下面看例子: [java] view plaincopy package org.thread.demo; class MyThread extends Thread{ private String name; public

细说多线程之Thread VS Runnable

[线程创建的两种方式] [线程的生命周期] ● 就绪:创建了线程对象后,调用了线程的start(). (注意:此时线程只是进入了线程队列,等待获取CPU服务,具备了运行的条件,但并不一定已经开始运行了).● 运行:处于就绪状态的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()里面的逻辑.● 终止:线程的run()执行完毕,或者线程调用了stop(),线程便进入终止状态.● 阻塞:一个正在执行的线程在某些情况下,由于某种原因而暂时让出了CPU资源,暂停了自己的执行, 便进入了阻塞状

细说多线程之Thread与Runnable

1:创建线程的两种方式: 继承Thread类 public class MyThread extends Thread { @Override public void run() { }} MyThread mt1 = new MyThread();mt1.start(); 实现Runnable 接口 public class MyThread implements Runnable { @Override public void run() { }} MyThread mt = new MyT

Java多线程的两种实现方式:继承Thread类 &amp; 实现Runable接口

------Java培训.Android培训.iOS培训..Net培训.期待与您交流! 创建和启动线程的两种传统方式: Java提供了线程类Thread来创建多线程的程序.其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象.每个Thread对象描述了一个单独的线程.要产生一个线程,有两种方法: ◆需要从Java.lang.Thread类派生一个新的线程类,重载它的run()方法: ◆实现Runnalbe接口,重载Runnalbe接口中的run()方法.

多线程之Thread+handler

应用开发中,经常会遇到需要多线程技术的时候,比如UI显示和数据处理分开. 在实际应用中经常会遇到这种场景,我们需要新开一个线程来监听数据状态(数据状态的改变,接收数据,发送数据等等),当发现数据状态改变时通知主线程(通过消息机制发送消息到主线程),主线程接收到通知之后进行处理(自定义消息处理接口). 具体实现如下: 准备线程的创建和消息的处理,这里需要用到android里面的Handler和Thread类,在主线程中添加两个成员Handler.Thread,并实现消息的发送和处理: privat

iOS多线程之Thread

多线程 ? Thread 是苹果官方提供的,简单已用,可以直接操作线程对象.不过需要程序员自己管理线程的生命周期,主要是创建那部分 优缺点 面向对象,简单易用 直接操作线程对象 需要自己管理线程生命周期(主要指创建) 偶尔会用的Thread,一般会用到它的方法:current.name等等 基本使用 创建线程 ``` 创建线程,手动启动 let thread = Thread(target: self, selector: #selector(makeMoney), object: ["Name

探Java多线程Thread类和Runnable接口之间的联系

首先复习一下Java多线程实现机制,Java实现多线程方法有如下这么几种: 1.继承了(extends)Thread类 2.实现了(implements)Runnable接口 也就是说  有如下两种情况 情况1: 继承Thread类.重写其方法run() .    然后new之.调用Start()方法 1 public class TestThread 2 { 3 private int i; 4 public static void main(String[] args) 5 { 6 // T

多线程-----Thread类与Runnable接口的区别

第一个继承Thread类来实现多线程,其实是相当于拿出三件事即三个卖早餐10份的任务分别分给三个窗口,他们各做各的事各卖各的早餐各完成各的任务,因为MyThread继承Thread类,所以在newMyThread的时候在创建三个对象的同时创建了三个线程:实现Runnable的, 相当于是拿出一个卖早餐10份的任务给三个人去共同完成,newMyThread相当于创建一个任务,然后实例化三个Thread,创建三个线程即安排三个窗口去执行. 一个类只能继承一个父类,存在局限:一个类可以实现多个接口.在

Java多线程和并发(三),Thread类和Runnable接口

目录 1.Thread和Runnable接口 三.Thread类和Runnable接口 1.Thread和Runnable接口 原文地址:https://www.cnblogs.com/xzmxddx/p/10362804.html