Java多线程的使用

Java多线程的使用

Java多线程的七种状态

在这里插入图片描述

状态描述
新建(NEW)线程刚被创建时的状态
就绪(RUNNABLE)线程正在竞争CPU使用权
运行(RUNNING)线程获取了CPU使用权,正在运行
阻塞(BLOCKED)线程为了等待某个对象的锁,而暂时放弃CPU使用权,且不再参与争夺。直到条件满足(超时退出、被中断或唤醒)时,从新回到就绪状态,参与CPU争夺。
等待(WAITING)线程无限等待(wait)某个对象的锁
计时等待(TIME_WAITING)线程在一段时间内等待某个对象的锁,或主动休眠(sleep),或等待另一个线程结束(join)。除非被中断,时间一到线程将自动回到就绪状态。
终止(DEAD)run()方法执行完毕、执行过程中抛出异常、受到外界干预中断执行
  • 轻量级阻塞与重量级阻塞
    • 轻量级阻塞:能够被中断的阻塞
      • WAITING
      • TIME_WAITING
    • 重量级阻塞:不可被中断的阻塞
      • BLOCKED

Thread实例方法及静态方法

方法类型方法名称描述状态转换
实例方法t.isDaemon判断是都是守护线程-
实例方法t.setDaemon(true)设置为守护线程(必须在线程启动前)-
实例方法t.setPriority(int)设置线程优先级(必须在线程启动前)-
实例方法t.start()启动线程t,开始参数CPU使用权的竞争新建状态(NEW) -> 就绪状态(RUNNABLE)
实例方法t.isAlive()线程t是否存活-
实例方法t.interrupt()线程t设置中断标志如果线程处于等待或计时等待状态会抛出异常,并且线程被唤醒
实例方法t.isInterrupt()判断线程t是否有中断标志-
静态方法Thread.currentThread()获取当前线程的信息-
静态方法Thread.yield()使当前线程让出CPU运行状态(RUNNING) -> 就绪状态(RUNNABLE)
静态方法Thread.sleep(int)使当前线程进入休眠状态运行状态(RUNNING)-> 计时休眠状态(TIME_WAITING) -> 就绪状态(RUNNABLE)
静态方法Thread.currentThread().isInterrupt()获取当前线程中断标志,并清除标志-

sleep与yield的区别

  • 关于状态
    • sleep后进入到计时等待状态,在一段时间后变成就绪状态
    • yield直接进入就绪状态。
  • 关于优先级
    • sleep后其他所有优先级线程均有机会获取到CPU
    • yield后只有相同或更高优先级才有机会获取到CPU
  • 关于异常
    • sleep需要声明InterruptedException异常
    • yield无需异常
  • 缺陷
    • 循环中使用yield后,当前线程可能立即又抢到了线程,造成其他线程得不到执行。

创建多线程

在Java的JDK开发包中,已经自带了对多线程技术的支持,可以很方便的使用多线程编程。

实现多线程的方式主要有两种,两种方式创建的线程在工作时的性质是一样的,没有本质区别。

  • 继承Thread类
  • 实现Runnable接口
    • 建议使用此种实现方式
      • 线程池只接受Runnable对象和Callable对象
      • 易于扩展

继承Thread类

Thread类的结构如下,从源码中可以发现,Thera的类实现了Runnable接口,它们之间具有多态关系。

public class Thread implements Runnable {
    ...
}

实现Runnable接口

使用继承Thread类的方式实现多线程时,最大的局限就是不可再继承其他类,因为Java的特点是单根继承,所有需要继承其他类时,应使用实现Runnable接口的方式。

public class MyRunnable implements Runnable {

	@Override
	public void run() {
		// run()执行结束,当前线程终止
		System.out.println("运行中");
	}
}

运行Runnable实现类的两种方法:

  • Runnable实现类做为参数构造Thread对象实例。
  • 将Runnable实现类提交到线程池中运行。

启动多线程 start()

  • 启动线程
    • 线程状态从 新建 -> 就绪
  • 通知"线程规划器"此线程已经准备就绪,等待调用线程规划器调用该线程对象的run()方法。
    • 直接调用run()为同步操作,与多线程无关。
  • 线程启动的顺序与执行start()方法的顺序无关。
    • 先提交的线程不一定先之先执行。

检查线程是否存活 isAlive()

isAlive()的功能是判断当前的进程是否存活。

  • 存活:线程已经启动且尚未停止。即处于正在运行或准备开始运行的状态,表示此线程是"存活"的。
public class MyThread extends Thread {
	
	@Override
	public void run() {
		System.out.println("run:" + this.isAlive());
	}

	public static void main(String[] args) throws InterruptedException {
		MyThread myThread = new MyThread();
		System.out.println("begin == " + myThread.isAlive());
		myThread.start();
		// Thread.sleep(1000); // 如果此处main线程休眠1s,则myThread线程必定执行完毕,myThread.isAlive()必定为false
		System.out.println("end == " + myThread.isAlive()); // 线程尚未执行完毕,所以为true
	}

}
// 结果
begin == false
end == true
run:true

休眠线程 sleep()

  • sleep()的作用是在指定的时机毫秒数内让当前"正在执行的线程"休眠(暂停执行)。
  • 这个正在执行的线程是this.currntThread()返回的线程。
public class MyThread extends Thread {
	
	@Override
	public void run() {
		try {
			System.out.println("run threadName=" + this.currentThread().getName() + " begin");
			Thread.sleep(2000);
			System.out.println("run threadName=" + this.currentThread().getName() + " end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}


	public static void main(String[] args) throws InterruptedException {
		MyThread myThread = new MyThread();
		System.out.println("begin == " + System.currentTimeMillis());
		myThread.start();
		System.out.println("end == " + System.currentTimeMillis());
	}

}
// 结果
begin == 1584418257689
end == 1584418257689
run threadName=Thread-0 begin
run threadName=Thread-0 end

由于main线程与Thread线程是异步执行的,所以先打印begin和end。而MyThread是随后运行的。

停止线程

停止线程是在多线程开发时很重要的技术点,此技术点可对线程的停止进行有效的处理。

  1. stop()、destory()函数
    • 官方明确不建议使用
  2. 使用中断标志中断线程。
    • interrupt():设置中断标志

设置中断标志 interrupt()

  • 大多数停止一个线程使用interrupt(),尽管方法的名称是停止,但这个方法不会终止一个正在运行的线程,还需加入一个判断才可完成线程的停止。
  • 调用interrupt()仅仅是在当前线程中打了一个停止的"标记",并不是真的停止线程。

从以下代码打印的信息可以看出,线程并没有真的停止。

public class MyThread extends Thread {
	
	@Override
	public void run() {
		for (int i = 0; i < 500000; i++) {
				System.out.println(i);
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		MyThread thread = new MyThread();
		thread.start();
		Thread.sleep(100);
		thread.interrupt();
	}

}

判断线程是否是停止状态

  • 实例方法
    • t.isInterrupted():测试线程对象Thread是否已经是中断状态,不会清除状态标志。
  • 静态方法
    • Thread.interrupted():测试当前线程(代码所在线程)是否已经中断,执行后具有将标志状态清除为false的功能。

Thread.interrupted()

从以下代码可以看出,线程的中断状态由Thread.interrupted()方法清除了中断标志。

public class MyThread extends Thread {
	
	public static void main(String[] args) throws InterruptedException {
		Thread.currentThread().interrupt();
		System.out.println("是否停止1? = " + Thread.interrupted()); // true
		System.out.println("是否停止2? = " + Thread.interrupted()); // false
	}

}

t.isInterrupted()

测试线程对象Thread是否已经是中断状态,不会清除状态标志。

public class MyThread extends Thread {
	
	@Override
	public void run() {
		for (int i = 0; i < 500000; i++) {
//				System.out.println(i);
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		MyThread thread = new MyThread();
		thread.start();
		Thread.sleep(1000);
		thread.interrupt();
		System.out.println("是否停止1? = " + thread.isInterrupted()); // true
		System.out.println("是否停止2? = " + thread.isInterrupted()); // true
	}

}

通过中断标志与抛异常停止线程

建议使用此形式停止异常,因为可以将异常向上抛,使得线程停止的事件得以传播。

public class MyThread extends Thread {

	@Override
	public void run() {
		try {
			for (int i = 0; i < 500000; i++) {
				if (this.interrupted()) {
					System.out.println("已经停止了,我退出了");
					throw new InterruptedException();
				}
				System.out.println(i);
			}
			System.out.println("for循环外代码");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			System.out.println("进入catch");
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		MyThread thread = new MyThread();
		thread.start();
		Thread.sleep(500);
		thread.interrupt();
	}

}

通过中断标志与return停止线程

public class MyThread extends Thread {

	@Override
	public void run() {
		while (true) {
			if (this.isInterrupted()) {
				System.out.println("停止了");
				return;
			}
			System.out.println(System.currentTimeMillis());
		}
	}

	public static void main(String[] args) throws InterruptedException {
		MyThread thread = new MyThread();
		thread.start();
		Thread.sleep(2000);
		thread.interrupt();
	}

}

处于轻量级阻塞状态(WAITING、TIME_WAITING)的进程调用中断

处于轻量级阻塞状态的进程调用中断,会抛出InterruptedException异常,并清除停止标志。主线程并不会进入catch中。

public class MyThread extends Thread {

	@Override
	public void run() {
		try {
			System.out.println("run begin");
			Thread.sleep(200000);
			System.out.println("run end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			System.out.println("进入catch");
			e.printStackTrace();
		}
        System.out.println("end");
	}

	public static void main(String[] args) throws InterruptedException {
		MyThread thread = new MyThread();
		thread.start();
		Thread.sleep(2000);
		thread.interrupt();
	}

}
// 结果
run begin
end
进入catch
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.sijing.netty.MyThread.run(MyThread.java:9)

放弃运行 Thread.yield()

yield()的作用是放弃的当前的CPU资源,将它让给其他的任务去占用CPU运行时间片段。但放弃的时间不确定。也有可能刚刚放弃,马上有获得CPU时间片。

public class MyThread extends Thread {
	
	@Override
	public void run() {
		long beginTime = System.currentTimeMillis();
		int count = 0;
		for (int i = 0; i < 5000000; i++) {
			Thread.yield(); // 是否开启此方法,方法的用时不等
			count = count + i;
		}
		long endTime = System.currentTimeMillis();
		System.out.println(endTime - beginTime);
	}

	public static void main(String[] args) throws InterruptedException {
		MyThread thread = new MyThread();
		thread.start();
	}

}

获取当前线程的信息 Thread.currentThread()

Thread.currentThread()返回当前代码段被哪个线程调用的信息。

public class TestBean {
	
	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName());  // main
	}
	
}

结果说明main方法被名为mian的线程调用。

public class MyThread extends Thread {
	
	public MyThread() {
		System.out.println("调用构造方法的线程为:" + Thread.currentThread().getName());
	}


	@Override
	public void run() {
		System.out.println("调用run()方法的线程为:" + Thread.currentThread().getName());
	}


	public static void main(String[] args) {
		MyThread myThread = new MyThread(); // 调用构造方法的线程为:main
		myThread.start(); // 调用run()方法的线程为:Thread-0
        myThread.run(); // 调用run()方法的线程为:main
	}

}

上述结果说明

  • MyThread的构造方法由main线程执行
  • 通过启动子线程调用run()由名为Thread-0的线程执行,run()是自动调用的。
  • 通过对象调用run(),有main进程执行。

Thread.currentThread() 与 this

Thread.currentThread() 表示执行它的线程。
this表示当前线程对象。

public class MyThread extends Thread {
	
	public MyThread() {
		System.out.println("Thread.currentThread().getName():" + Thread.currentThread().getName());
		System.out.println("Thread.currentThread().isAlive():" + Thread.currentThread().isAlive());
		System.out.println("this.getName():" + this.getName());
		System.out.println("this.isAlive():" + this.isAlive());
		
	}
	
	@Override
	public void run() {
		System.out.println("run:" + this.isAlive());
	}


	public static void main(String[] args) throws InterruptedException {
		MyThread myThread = new MyThread();
	}

}
// 结果
Thread.currentThread().getName():main
Thread.currentThread().isAlive():true
this.getName():Thread-0
this.isAlive():false
1. 建立三个线程,并且同时运行它们。当运行时输出线程的名称。 实验步骤: (1)、创建类sy6_1 (2)、创建三个线程,调用start()方法启动这三个线程 (3)、保存文件,调试并编译运行程序。 参考程序运行效果: 2. 实现3个类:Storage、Counter和Printer。 Storage类应存储整数。 Counter应创建线程,线程从0开始计数(0,1,2,3…)并将每个值存储到Storage类中。 Printer类应创建一个线程,线程读取Storage类中的值并打印值。编写程序创建Storage类的实例,并创建一个Counter对象和Printer对象操作此实例。 实验步骤: (1)、创建三个类Counter, Printer,Storage (2)、创建TestCounter类,在该类中定义main函数,在main函数中定义Storage对象、Counter对象和 Printer对象,创建Counter线程和Printer线程并启动 (3)、保存文件,调试并编译运行程序。 参考程序运行效果: 3. 修改实验1第2题的程序,添加适当代码,以确保每个数字都恰好只被打印一次。 实验步骤: (1)、创建三个类Counter, Printer,Storage (2)、 创建TestCounter类,在该类中定义main函数,在main函数中定义Storage对象、Counter1对象和 Printer对象,创建Counter线程和Printer线程并启动 (3)、在定义Storage类中的setValue(int i) 和getValue ()方法时使用synchronized关键字,将其定义为同步方法 (4)、保存文件,调试并编译运行程序。 参考程序运行效果:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值