第九章 多线程
9.1 多线程
这里只是讲一下多线程基础,后面Java高级会讲juc、多线程高级等
1、什么是多线程?
同一个程序同时做多个事情。
程序:为了完成某个任务,功能,而选择一种编程语言(例如:Java)编写的一组指令的集合。
进程:当程序启动时,操作系统会给这个程序分配一块独立的内存空间,以及相关的资源,
每一个程序启动后有一个独立的进程对它进行管理。每一个进程都有自己唯一编号。
操作系统是以进程为单位来管理资源。
线程:线程是进程中的其中一条执行路径。同一个进程中可以存在1个或多个的线程。
例如:qq进程,它里面有多个线程
(1)收消息线程
(2)发消息线程
(3)传输文件的线程
...
最早的计算机是单任务单进程==>计算机同一时刻只能运行一个程序。
后面出现了多任务多进程的系统==>计算机能同时运行多个程序。
此时是单个CPU阶段的话,多个进程需要来回“切换”,在进程之间切换时,
需要给进程做拍照处理,记录当前进程进行到那一步了,
回头切换回来时,从这个位置继续执行。
因为进程的内存等资源是不共享的,所以切换时等操作成本比较高。
当我们多个任务具有相关性时,采用多线程,切换的成本更低一点,
因为同一个进程的多个线程之间可以共享“堆、方法区”内存。
如果多个CPU的话,多个进程可以同时进行,不需要切换,前提是进程数量<=CPU的数量。
如果多个CPU的话,多个线程可以同时进行,不需要切换,前提是线程数量<=CPU的数量。
线程是CPU调度的最小单位。
一个进程至少有一个线程的。
并行:
多个任务同时在多个CPU上运行。不管是微观角度还是宏观角度,都是同时运行的。
并发:
多个任务“同时”在运行,其实是CPU在多个任务之间快速的“切换”,
给“人”的感觉是同时的。
9.2 编写多线程程序
9.2.1 继承Thread类
2、Java程序:
(1)main线程:主线程
(2)后台还有一些其他服务线程:GC线程、类加载线程等等
(3)还可以手动再启动一些其他线程
3、如何手动启动其他的线程
(1)继承Thread
(2)实现Runnable
(3)实现Callable(高级)
(4)线程池(高级)
4、继承Thread类的方式步骤
(1)编写线程类,继承Thread
(2)必须重写public void run()方法
这个线程要干什么,就在run()里面写什么,称为线程体
(3)创建自定义线程类的对象
(4)启动线程
注意启动线程,调用start()方法,不要手动调用run()。
如果手动调用run()就不是多线程程序,成了单线程程序。
演示:
单独启动一个线程,输出1-100之间的偶数
public class EvenThread extends Thread {
@Override
public void run() {
//输出1-100之间的偶数
for(int i=2; i<=100; i+=2){
System.out.println("偶数i = " + i);
}
}
}
public class TestThread {
/*public static void main(String[] args) {
EvenThread evenThread = new EvenThread();
evenThread.run();//当成普通对象调用方法,而不是多线程在工作
//串行,先运行完上面的run()方法的所有代码,再运行下面的代码
//在main线程中输出1-100的奇数
for(int i=1; i<=100; i+=2){
System.out.println("奇数i = " + i);
}
}*/
public static void main(String[] args) {
EvenThread evenThread = new EvenThread();
evenThread.start();//从父类Thread类继承的
//并行或并发,上面的线程的run方法和下面的代码是“同时”进行
//在main线程中输出1-100的奇数
for(int i=1; i<=100; i+=2){
System.out.println("奇数i = " + i);
}
}
}
9.2.2 实现Runnable接口
5、实现Runnable接口的方式
(1)编写线程类,实参Runnable接口
(2)必须重写public void run()方法
这个线程要干什么,就在run()里面写什么,称为线程体
(3)创建线程类对象
(4)启动线程,需要借助Thread类的对象,才能调用start方法启动线程
6、两种方式的区别
(1)实现接口没有单继承限制
(2)其他的区别后面再说
public class EvenRunnable implements Runnable {
@Override
public void run() {
//输出1-100之间的偶数
for(int i=2; i<=100; i+=2){
System.out.println("偶数i = " + i);
}
}
}
public class TestRunnable {
public static void main(String[] args) {
EvenRunnable evenRunnable = new EvenRunnable();
// evenRunnable.start();//错误,因为EvenRunnable没有start方法,它的父类是Object
//父接口Runnable也没有start方法
Thread thread = new Thread(evenRunnable);
thread.start();
//在main线程中输出1-100的奇数
for(int i=1; i<=100; i+=2){
System.out.println("奇数i = " + i);
}
}
}
9.2.3 匿名内部类
public class TestThreadRunnable {
public static void main(String[] args) {
//使用匿名内部类分别继承Thread和实现Runnable接口
//一个输出1-100的偶数,一个输出1-100的奇数
new Thread(){//继承Thread类的方式
@Override
public void run() {
//输出1-100之间的偶数
for(int i=2; i<=100; i+=2){
System.out.println("偶数i = " + i);
}
}
}.start();
/*Runnable runnable = new Runnable(){
@Override
public void run() {
//输出1-100之间的奇数
for(int i=1; i<=100; i+=2){
System.out.println("奇数i = " + i);
}
}
};
Thread thread = new Thread(runnable);
thread.start();*/
new Thread(new Runnable(){
@Override
public void run() {
//输出1-100之间的奇数
for(int i=1; i<=100; i+=2){
System.out.println("奇数i = " + i);
}
}
}).start();
}
}
9.3 Thread类的方法
系列一、
7、Thread类的API方法
(1)String getName():获取线程的名字
(2)void setName(String name):设置线程名字
(3)public static Thread currentThread():获取当前线程对象,执行这句代码的线程对象
(4)public static void sleep(long time):线程休眠,运行这句代码的线程会休眠xx时间,单位是毫秒
(5)public void join():线程加塞,线程阻塞,
加塞是 a.join(),代表a线程会把当前线程(运行这句代码的线程)给阻塞了,
即当前线程被加塞了,直到a线程完事。
public void join(long time)
加塞是 a.join(时间),代表a线程会把当前线程(运行这句代码的线程)给阻塞了,
即当前线程被加塞了,直到时间到了,当前线程恢复。
public class TestMethod {
public static void main(String[] args) {
ThreadDemo threadDemo1 = new ThreadDemo();
ThreadDemo threadDemo2 = new ThreadDemo();
ThreadDemo threadDemo3 = new ThreadDemo("线程3");
ThreadDemo threadDemo4 = new ThreadDemo();
threadDemo4.setName("线程4");
threadDemo1.start();
threadDemo2.start();
threadDemo3.start();
threadDemo4.start();
// System.out.println(getName());//错误,(1)TestMethod没有继承Thread类(2)main方法是静态方法,不能直接调用非静态方法
Thread currentThread = Thread.currentThread();
System.out.println("主线程的名字: " + currentThread.getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("其他代码");
}
}
class ThreadDemo extends Thread{
public ThreadDemo() {
}
public ThreadDemo(String name) {
super(name);
}
@Override
public void run() {
System.out.println(getName());//getName()从父类Thread类继承的
}
}
public class TestJoin {
public static void main(String[] args) {
PrintNum p = new PrintNum();
p.start();
PrintChar printChar = new PrintChar();
printChar.start();
try {
// p.join();
p.join(3000);//阻塞当前线程,main线程,因为这句代码由main线程调用
//和printChar线程无关。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main");
}
}
class PrintNum extends Thread{
@Override
public void run() {
for(int i=1; i<=10; i++){
System.out.println("i = " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class PrintChar extends Thread{
@Override
public void run() {
for(char c = 'a'; c<='z'; c++){
System.out.println("c = " + c);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
系列二、
1、Thread类API
部分构造器:
- public Thread() :分配一个新的线程对象。
- public Thread(String name) :分配一个指定名字的新的线程对象。
- public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
- public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
方法们:
- public void run() :此线程要执行的任务在此处定义代码。
- public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
- public String getName() :获取当前线程名称。
- public void setName(String name):设置当前线程的名称。
如果不设置线程名称,有默认名字,Thread-编号。
- public static Thread currentThread() :返回当前正在执行的线程对象的引用。
- public final int getPriority() :返回线程优先级
- public final void setPriority(int newPriority) :改变线程的优先级
说明:当线程的优先级比较高时,可以获得更多的CPU调度的机会。
但是不代表线程优先级低的完全没有机会。
优先级的值范围是:【1,10】
- public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
- public static void yield():yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次,
希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,完全有可能的情况是,
当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。
- public void join() :等待该线程终止。
public void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。
public void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
public class TestMethod {
public static void main(String[] args) {
Thread currentThread = Thread.currentThread();
//因为是main线程在执行上面这句代码,所以得到的是main线程对象
System.out.println(currentThread);
MyRunnable my = new MyRunnable();
Thread t = new Thread(my);
t.setName("t线程");
t.start();
Thread t2 = new Thread(my);
t2.setName("t2线程");
t2.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
Thread currentThread = Thread.currentThread();
//因为这句代码是在MyRunnable的run方法中,那么这里获取的是t线程对象或t2线程对象
System.out.println("当前线程名称:" + currentThread.getName());
}
}
public class TestPriority {
public static void main(String[] args) {
/*
一个线程打印1-100奇数,一个线程打印1-100的偶数,
把打印奇数的线程的优先级设置为最高,
把打印偶数的线程的优先级设置为最低,
*/
Thread even = new Thread(){
@Override
public void run() {
for(int i=2; i<=100; i+=2){
System.out.println("偶数:" + i);
}
}
};
Thread odd = new Thread(){
@Override
public void run() {
for(int i=1; i<=100; i+=2){
System.out.println("奇数:" + i);
}
}
};
// even.setPriority(100);//java.lang.IllegalArgumentException非法参数异常
/*
查找某个类的源码的快捷键:
(1)Ctrl + n:打开搜索框,输入类名
(2)Ctrl + F12:打开某个类的成员列表
(3)如果类名和方法名已使用,直接按Ctrl键+单击类名或方法名,也可以定位源码
public final static int MIN_PRIORITY = 1; 最低优先级
public final static int NORM_PRIORITY = 5; 普通优先级
public final static int MAX_PRIORITY = 10; 最高优先级
*/
even.setPriority(10);
odd.setPriority(1);
even.start();
odd.start();
}
}
public class TestYield {
public static void main(String[] args) {
/*
在main线程中输出1-100的偶数,
在另一个线程中输出1-100的奇数
*/
Thread even = new Thread(){
@Override
public void run() {
for(int i=2; i<=100; i+=2){
System.out.println("偶数:" + i);
}
}
};
even.setPriority(10);
Thread.currentThread().setPriority(1);
even.start();
for(int i=1; i<=100; i+=2){
System.out.println("奇数:" + i);
if(i==5){
Thread.yield();//当前线程暂停,让出CPU
}
}
}
}
系列三、
@Deprecated
public final void stop()已过时。
如何解决让某个线程提前停止的操作?
public class TestStop {
static boolean flag = true;
public static void main(String[] args) {
/*
两个线程:
1、一个线程输出1-100的偶数
2、另一个线程输出1-100的奇数
3、偶数线程是连续打印数字
4、奇数线程是每隔1毫秒打印一个数字
5、当偶数线程打印完了,可以奇数线程提前停下来,就算奇数线程没有打印完所有的奇数
思路之一:标记法
*/
Thread even = new Thread(){
@Override
public void run() {
for(int i=2; i<=100; i+=2){
System.out.println("偶数:" + i);
}
flag = false;
}
};
Thread odd = new Thread(){
@Override
public void run() {
for(int i=1; i<=100 && flag; i+=2){
System.out.println("奇数:" + i);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
even.start();
odd.start();
}
}
系列四、
- public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。
当正在运行的线程都是守护线程时,Java 虚拟机退出。
守护线程是一种特殊的线程,特殊在它不能独立存在。当JVM中,所有非守护线程都结束了,那么守护线程会自动结束。
public class TestDaemon {
public static void main(String[] args) {
//主线程先启动PrintNumber线程,并把PrintNumber线程对象设置为守护线程
PrintNumber p = new PrintNumber();
p.setDaemon(true);
p.start();
}
/* @Test
public void test(){
PrintNumber p = new PrintNumber();
p.start();
}*/
}
class PrintNumber extends Thread{
@Override
public void run() {
int num = 1;
while(true){
System.out.println("num = " + num);
num++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
系列五、
- public void interrupt() 中断线程
public class Sporter extends Thread {
private long timePerMeter;
private long restTime;
private long time;
private static volatile boolean flag = true;//这里是static,所有运动员共享
//volatile表示每一个线程不缓存该变量的值,直接从主存里面读取该变量的值,
//如果缓存了,也及时和主存中的值同步,保证一致性
private int distance;//记录运动员跑了多少米
public Sporter(String name, long timePerMeter, long restTime) {
super(name);
this.timePerMeter = timePerMeter;
this.restTime = restTime;
}
@Override
public void run() {
long start = System.currentTimeMillis();
for(int i=1; i<=30 && flag; i++){
try {
Thread.sleep(timePerMeter);//模拟跑1米的时间
} catch (InterruptedException e) {
// e.printStackTrace();
System.err.println(getName()+"线程被中断了,异常信息:" + e);
continue;
}
System.out.println(getName() + "跑了" +i + "米");
distance++;
if(i==10 || i==20){//i%10==0,会包含30米
System.out.println(getName() + "开始休息....");
try {
Thread.sleep(restTime);//模拟休息时间
} catch (InterruptedException e) {
// e.printStackTrace();
System.err.println(getName()+"线程被中断了,异常信息:" + e);
continue;
}
}
}
if(distance == 30){
System.out.println(getName() +"已经到达终点");
flag = false;
}
long end = System.currentTimeMillis();
time = end - start;
}
public long getTime() {
return time;
}
public int getDistance() {
return distance;
}
public static boolean isFlag() {
return flag;
}
}
public class Exercise5 {
public static void main(String[] args) {
Sporter rabbit = new Sporter("兔子",100,10000);
Sporter tortoise = new Sporter("乌龟",1000,1000);
rabbit.start();
tortoise.start();
while(true){
// if(Sporter.isFlag()==false){
if(!Sporter.isFlag()){
rabbit.interrupt();
tortoise.interrupt();
break;
}
}
System.out.println("兔子跑了:" + rabbit.getDistance()+",耗时:" + rabbit.getTime());
System.out.println("乌龟跑了:" + tortoise.getDistance()+",耗时:" + tortoise.getTime());
if(rabbit.getDistance() > tortoise.getDistance()){
System.out.println("兔子赢");
}else if(rabbit.getDistance() < tortoise.getDistance()){
System.out.println("乌龟赢");
}else {
if (rabbit.getTime() < tortoise.getTime()) {
System.out.println("兔子赢");
} else if (rabbit.getTime() > tortoise.getTime()) {
System.out.println("乌龟赢");
} else {
System.out.println("平局");
}
}
}
}