java多线程与应用
线程
一个进程中可以包含多个子任务同时运行,比如:在使用360安全卫士进行全盘扫描时,后台可以同步更新病毒库,同时检测正在运行的其他程序;其中的每一个子任务都称之为一条线程,因此,进程中至少包含一个线程,即线程是进程中的一条执行路径。举个通俗例子:比如去食堂排队吃饭是一个进程,但是食堂的窗口有很多,每个窗口售卖不同的餐品,有卖包子的,有卖面条,有卖麻辣香锅等,每一个窗口我们可以认为是这个进程中的一条线程。
线程状态
每一条线程都有各自的生命周期,并且线程对象都存在以下几种状态:
- 初始:线程刚创建时候的状态
- 就绪:当线程准备运行时候的状态
- 运行:当CPU分配时间片给当前线程,线程进入运行态
- 阻塞:当CPU将时间片从当前线程分配到其他线程时,当前线程进入阻塞态
- 销毁:当线程执行完毕后销毁
线程的创建与启动
java中提供对线程的支持,java中创建线程包含四种方式,其中有两种是基本的创建方式,另外两种是在JDK1.5之后新增的并发编程包中,如下:
- 实现Runnable接口
- 继承Thread类
- 实现Callable接口,创建FutureTask
- 使用ExecutorService,Callable等相关接口
1.实现Runnable接口(实现run方法)
public class MyThread implements Runnable{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(name+":--->"+i);
}
}
public static void main(String[] args) {
MyThread mt1 = new MyThread("t1");
MyThread mt2 = new MyThread("t2");
// 方法调用
// mt.run();
//创建线程对象
Thread t1 = new Thread(mt1);
Thread t2 = new Thread(mt2);
//线程启动
t1.start();
t2.start();
}
}
2.继承Thread类(重写run方法)
public class MyThread2 extends Thread{
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(this.getName()+"---->"+i);
}
}
public static void main(String[] args) {
MyThread2 mt1 = new MyThread2();
MyThread2 mt2 = new MyThread2();
mt1.start();
mt2.start();
}
}
综合以上两种线程创建的方式,区别:
实现Runnable接口更灵活,类还能在实现其他接口或继承其他类,但是线程的创建和启动依然需要Thread类完成。
继承Thread类,相对第一种更简单,可以直接创建子类对象并启动线程,但是耦合度较高,子类不能再对其他类继承。
关于线程的启动:
必须通过调用Thread类的start方法完成,不能直接通过线程对象调用run方法(实际还是单线程的执行方法:普通方法调用)
Thread类详解
Thread类是java.lang包中用于创建线程对象的类,JVM允许同时运行多个Thread对象,Thread类从Runnable实现,因此Thread类中也对run方法做了实现,通常需要创建自定义线程时,一般需要重写run方法;Thread类中提供了一个常用构造器和方法用于操作线程对象:
1.常见构造器:
- Thread()
- Thread(String name)
- Thread(Runnable r)
- Thread(Runnable r,String name)
2.常见方法
- static int activeCount() 获取活动线程数(包含主线程)
- static Thread currentThread() 获取当前线程对象
- long getId() 获取线程的标识符
- String getName() 获取线程名称
- getPriority() 获取线程优先级(1-10)
- setPriority(int p)设置线程优先级(MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY)
- interrupt() 中断线程对象(并非真正中断,实际是添加中断标记)
- isInterrupted() 判断线程是否中断
- sleep(long time) 让休眠指定毫秒,时间到达会自动唤醒
public class MyThread4 extends Thread{
public void run() {
try {
for (int i = 0; i < 100; i++) {
// sleep((int)(Math.random()*100));
System.out.println(this.getId()+"--"+this.getName()+":"+i+"---优先级:"+this.getPriority());
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
MyThread4 mt1 = new MyThread4();
MyThread4 mt2 = new MyThread4();
// MyThread4 mt3 = new MyThread4();
// MyThread4 mt4 = new MyThread4();
//设置线程优先级
mt1.setPriority(Thread.MAX_PRIORITY);
mt2.setPriority(Thread.MIN_PRIORITY);
mt1.start();
mt2.start();
// mt3.start();
// mt4.start();
//统计激活线程总数
System.out.println("线程总数:"+Thread.activeCount());
//获取当前线程对象的名称
System.out.println(Thread.currentThread().getName());
}
}
线程终止
线程在运行过程中可能由于满足到一些特定条件后需要中断当前线程,因此,如何安全有效的终止线程的运行就成为一个比较重要的问题了,java多线程编程中,线程的中断包含以下几种方式:
- 标记中断法
- 异常中断法
标记中断法
这是一个较为推荐的中断方式,原理为:在线程执行前声明一个结束标记,当执行过程中满足了结束标记时,通过相关的逻辑执行结束:
public class ThreadEnd extends Thread{
@Override
public void run() {
boolean isOver = false;
int i = 0;
while(!isOver){
System.out.println(this.getName()+"-->"+i);
i++;
if(i >= 50000){
isOver = true;
return;
}
}
System.out.println("确定结束了么?这是线程内部的输出!");
}
public static void main(String[] args) {
ThreadEnd te = new ThreadEnd();
te.start();
}
}
异常中断法
异常中断法即通过异常的抛出将正在执行的线程中断:
public class ThreadEnd2 extends Thread{
@Override
public void run() {
try {
for (int i = 0; i < 1000000; i++) {
System.out.println(this.getName()+"-->"+i);
if(this.isInterrupted()){
throw new InterruptedException("当前线程被中断!!!");
}
}
} catch (InterruptedException e) {
System.out.println("线程终止");
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ThreadEnd2 te2 = new ThreadEnd2();
te2.start();
Thread.sleep(2000);
//中断线程
te2.interrupt();
}
}
另外还有一种可以直接使用Thread类中的stop(过时的)方法,强行终止线程,但是stop极为不安全,当使用stop方法终止线程时,将会导致对象锁被释放,从而产生一些不可预知后果。
守护线程
守护线程也称之为后台线程,守护线程的主要作用于为其他线程提供服务,当守护线程守护的主线程结束后,守护线程也将结束(皮之不存毛将焉附),比如,文件下载时,一条主线程实现文件拷贝,另一条子线程用于计算下载进度,该条子线程主要为下载线程提供服务,因此计算下载进度的线程可以定义为守护线程;只需要调用Thread类提供的setDaemon()方法即可实现,java中要将其他线程设置为守护线程,例如:
后台线程类:
public class BackService extends Thread{
@Override
public void run() {
int i = 1;
while(true){
try {
System.out.println("后台线程(守护线程)正在执行:"+i);
i++;
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
主任务线程:
public class ThreadDeamon extends Thread{
@Override
public void run() {
//创建线程对象
BackService bs = new BackService();
//设置当前线程为守护线程
bs.setDaemon(true);
bs.start();
try {
for (int i = 0; i < 1000; i++) {
sleep(2);
System.out.println("执行线程---->"+i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new ThreadDeamon().start();
}
}
以上程序中如果主任务线程执行结束了,则不论守护线程是否达到结束条件,都会随之终止。
通过实例得出,所谓守护线程即将普通线程设置为后台运行即可(调用setDaemon方法)
多线程编程
多线程实现文件修改监听
public class FileLinstener2 implements Runnable{
private File file;
public FileLinstener2(File file) {
this.file = file;
}
@Override
public void run() {
//获取当前文件最后修改时间
long time = file.lastModified();
while(true){
try {
long now = file.lastModified();
if(now != time){
//文件被修改
System.out.println(file.getName()+" file changed,"+getTime(now));
//将原来的时间设置为最新时间
time = now;
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**格式化日期*/
public String getTime(long time){
DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = new Date(time);
return fmt.format(d);
}
public static void main(String[] args) {
//目标目录
File file = new File("C:\\Users\\mrchai\\Desktop\\tempfile");
//获取目录中所有子文件
File[] files = file.listFiles();
for (File f : files) {
//为每一个File对象启动一条监听线程
FileLinstener2 lis = new FileLinstener2(f);
Thread t = new Thread(lis);
t.start();
}
}
}
实例
使用一个线程实现文件拷贝,开启另一个线程计算并显示当前拷贝的进度
FileCopy类:
package com.softeem.lesson21.example2;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* 使用一个线程实现文件拷贝,开启另一个线程计算并显示当前拷贝的进度
* @author max
*
*/
public class FileCopy extends Thread{
private File source;
private File target;
private FileProgressLis progressLis;
public FileCopy(File source, File target) {
super();
this.source = source;
this.target = target;
}
public void copy(File source,File target) throws IOException {
//获取源文件的字节输入流
RandomAccessFile raf_read = new RandomAccessFile(source,"r");
//获取目标文件的输出流
RandomAccessFile raf_write = new RandomAccessFile(new File(target,source.getName()),"rw");
byte[] b = new byte[1024];
int len = 0;
//统计当前总拷贝的字节数
long current = 0;
System.out.println("开始拷贝");
while((len = raf_read.read(b))!= -1) {
//记录每一次的读取的字节并叠加
current+=len;
progressLis.setNowSize(current);
raf_write.write(b,0,len);
}
raf_write.close();
raf_read.close();
System.out.println("拷贝完成");
}
@Override
public void run() {
try {
progressLis = new FileProgressLis(source.length());
progressLis.setDaemon(true);
progressLis.start();
//开始拷贝
copy(source,target);
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileProgressLis类
package com.softeem.lesson21.example2;
import java.math.BigDecimal;
import java.text.DecimalFormat;
public class FileProgressLis extends Thread{
private long total;
private long nowSize;
public FileProgressLis(long total) {
super();
this.total = total;
}
public long getNowSize() {
return nowSize;
}
public void setNowSize(long nowSize) {
this.nowSize = nowSize;
}
@Override
public void run() {
double size1 =total;
while(nowSize < total) {
double size2 = nowSize;
//将相除之后结果格式化百分比的显示方式
String progress = new DecimalFormat("##.0%").format(size2/size1);
System.out.println("拷贝进度:"+progress);
}
}
}
TestCopy类
package com.softeem.lesson21.example2;
import java.io.File;
public class TestCopy {
public static void main(String[] args) {
File f1 = new File("D:\\java高级\\20190731\\video\\09_守护线程实例.avi");
File f2 = new File("D:\\javacode");
new FileCopy(f1,f2).start();
}
}