Java学习09-多线程-1
文章目录
1 Java线程的概念
1.1 程序 进程 线程
- 程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
- 进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
- 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间 并行执行多个线程,就是支持多线程的。
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数(pc),线程切换的开销小一个进程中的多个线程共享相同的内存单元/内存地址空间->它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
1.2 单核CPU 和多核CPU
- 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。间单元特别短,因此感觉不出来。
- 如果是多核的话,才能更好的发挥多线程的效率。
- 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
1.3使用多线程的优点
- 提高应用程序的响应。对图形化界面更有意义(在一个应用中同时做多件事),可增强用户体验。
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
1.4何时需要多线程
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
- 需要一些后台运行的程序时。
2 线程的创建和使用
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。
2.1 Thread类
2.1.1Thread类的特性:
- 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为
- 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
2.1.2构造器:
- Thread() :创建新的Thread对象
- Thread(String threadname): :创建线程并指定线程实例名
- Thread(Runnable target) :指定创建线程的目标对象,它实现了Runnable接口中的run方法
- Thread(Runnable target, String name) :创建新的Thread对象
2.1.3Thread类的有关方法:
- void start(): 启动线程,并执行对象的run()方法
- run(): 线程在被调度时执行的操作,将创建的线程要执行的操作声明在此方法中
- String getName(): 返回当前线程的名称
- void setName(String name):设置该线程名称
- static Thread currentThread(): 静态方法,返回执行当前代码的线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
- static void yield() : 线程让步
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
若队列中没有同优先级的线程,忽略此方法 - join() : 在线程A中调用线程B的jion()方法,此时线程A就进入阻塞状态,直到线程B完全执行完以后,线程A才结束阻塞状态。
- static void sleep(long millis) :(指定时间:毫秒)
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
抛出InterruptedException异常 - stop(): 强制线程生命期结束,不推荐使用,已过时。
- boolean isAlive() : 返回boolean,判断线程是否还活着
2.2创建线程的两种方式
2.2.1方式一:继承Thread类
- 定义子类继承Thread类。
- 子类中重写Thread类中的run方法。–将此线程执行的操作写在run()方法中。
- 创建Thread子类对象,即创建了线程对象。
- 调用线程对象start()方法:启动线程,调用run方法。
【例】
//定义子类继承Thread类。
class MyThread extends Thread {
//子类中重写Thread类中的run方法
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//创建Thread子类对象
MyThread t1 = new MyThread();
// 调用线程对象start方法
t1.start();
//如下的操作仍然是在main线程中执行的
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(i + "*********************");
}
}
}
}
//main()方法主线程开始
//主线程new了一个对象
//主线程调用start()方法
//t1线程开始,遍历100之内的偶数
start()方法的作用:
- 启动当前线程
- 调用当前线程的run()方法
【注意】
- 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
- run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
- 想要启动多线程,必须调用start()方法。
- 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。
【练习】
/**
* 练习:创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
*/
public class ThreadDemo {
public static void main(String[] args) {
MyThread1 my1 = new MyThread1();
MyThread2 my2 = new MyThread2();
my1.start();
my2.start();
}
}
class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0)
System.out.println(Thread.currentThread().getName() + ";" + i);
}
}
}
class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0)
System.out.println(Thread.currentThread().getName() + ";" + i);
}
}
}
2.2.2方式二:实现Runnable 接口(推荐)
- 定义子类,实现Runnable接口。
- 子类中实现Runnable接口中的抽象run()方法。
- 创建实现类的对象。
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
- 调用Thread类的start()方法:开启线程,调用Runnable子类接口的run()方法。
【例】
//1. 定义子类,实现Runnable接口。
class MThread implements Runnable {
@Override
public void run() {
//2. 子类中实现Runnable接口中的抽象run()方法。
for (int i = 0; i < 100; i++) {
if (i % 2 == 0)
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3.创建实现类的对象
MThread mThread = new MThread();
// 4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
Thread t1 = new Thread(mThread);
t1.setName("线程1");
//5. 调用Thread类的start()方法
t1.start();
//再启动一个线程,遍历100以内的偶数
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();//t1和t2共用一个run()
}
}
2.2.3创建多线程方式的比较
区别:
- 继承Thread:线程代码存放Thread子类run方法中。
- 实现Runnable:线程代码存在接口的子类的run方法。
实现Runnable 接口方式的好处: - 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
2.3线程的调度
线程的优先级:
- 线程的优先等级
- MAX_PRIORITY :10 ----------最高优先级
- MIN _PRIORITY :1 ------------最低优先级
- NORM_PRIORITY :5 ----------默认优先级
- 涉及的方法
- getPriority() : :返回线程优先值
- setPriority(int newPriority) : :改变线程的优先级
- 说明
- 线程创建时继承父线程的优先级
- 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
【例】
1.使用继承Thread方式,多窗口卖票
/**
* 例:创建三个窗口卖票,总票数为100张
*/
class Window extends Thread {
private static int ticket = 100;//此处用static的原因:
//如果不用static,那么new了三个对象,每个对象都有100张票,这显然不符合题意
//当将ticket定义为static之后,不管new了多少个对象,这些对象都公用一个ticket,即共用100张票
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(getName() + ":卖票,票号为:" + ticket);
ticket--;
}else {
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1=new Window();
Window t2=new Window();
Window t3=new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
//注:该代码存在线程的安全性问题,该问题将在Java学习09-多线程2中讲解
2.实现Runnable方式,多窗口卖票
/**
* 例:创建三个窗口卖票,总票数为100张
*/
class Window1 implements Runnable {
private int ticket = 100;//此处的ticket不用定义为static,因为只nwe了一个Window1,w1作为参数给Thread构造器
//t1,t2,t3公用一个ticket
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--
} else {
break;
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w1 = new Window1();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1 ");
t2.setName("窗口2 ");
t3.setName("窗口3 ");
t1.start();
t2.start();
t3.start();
}
}
//注:该代码存在线程的安全性问题,该问题将在Java学习09-多线程2中讲解