本文主要是介绍高级编程--第五章 多线程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1、目标
理解线程的概念
掌握线程的创建和启动
了解线程的状态
掌握线程调度的常用方法
掌握线程的同步
理解线程安全的类型
2、进程
3、多线程
3.1 什么是多线程
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
如果一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程
多线程交替占用CPU资源,而非真正的并行执行
线程的生命周期
线程是一个动态执行的过程,他有一个从产生到死亡的过程
线程的状态
- 新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
- 就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
- 死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
线程优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
多线程好处
充分利用CPU的资源
简化编程模型
带来良好的用户体验
3.2 主线程
java提供了三种创建线程的方法
1、通过实现Runnable接口
2、通过继承Thread类本身
3、通过Callable和Future创建线程
Thread类
java提供了java.lang.thread类支持多线程编程
主线程
main()方法即为主线程入口
产生其他子线程的线程
必须最后完成执行,因为它执行各种关闭动作
public static void main(String args[]) {Thread t= Thread.currentThread(); System.out.println("当前线程是: "+t.getName()); t.setName("MyJavaThread"); System.out.println("当前线程名是: "+t.getName());
}
使用线程步骤
3.2.1 通过继承Thread类创建线程
定义Mythread类继承Thread类
重写run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
//Mythread类
package Test01;public class Mythread extends Thread{@Overridepublic void run() {for (int i = 1;i<100;i++){System.out.println(Thread.currentThread().getName()+ ":" + i);}}
}
//测试类
package Test01;public class Test {public static void main(String[] args) {Mythread mt =new Mythread();mt.start();
// mt.run();}
}
多个线程是交替执行的,不是真正的“并行”
线程每次执行时长由分配的CPU时间片程度决定
常见问题
启动线程是否可以直接调用run()方法?
调用run方法只有线程一条执行路径 调用start方法有多条执行路径,主线程和子线程并行交替执行
3.2.2 实现Runnalbe接口创建线程
定义MyRunnable类实现Runnable接口
实现run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
//MyRunnable
package Test02;public class MyRunnalbe implements Runnable{@Overridepublic void run() {for (int i = 1;i<100;i++){System.out.println(Thread.currentThread().getName()+ ":" + i);}}
}
//测试类
package Test02;public class Test {public static void main(String[] args) {MyRunnalbe mr1 = new MyRunnalbe();Thread mythread = new Thread(mr1);mythread.start();}}
3.2.3 通过Callable和Future创建线程
1、创建Callable接口的实现类,并实现call()方法,该call方法将作为线程执行体,并且有返回值。
2、创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call方法的返回值。
3、使用FutrueTask对象作为Thread对象的target创建并启动新线程。
4、调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
package Test03;import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;public class Test {public static void main(String[] args) {CallableThreadT ctt = new CallableThreadT();FutureTask<Integer> ft = new FutureTask<>(ctt);for (int i = 0;i < 100;i++){System.out.println(Thread.currentThread().getName() + "的循环变量i的值" + i);if (i == 20){new Thread(ft,"有返回值的线程").start();}}try{System.out.println(ft.get());}catch (Exception e){e.printStackTrace();}}
}
package Test03;import java.util.concurrent.Callable;public class CallableThreadT implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int i = 0;for (;i<100;i++){System.out.println(Thread.currentThread().getName() + ":" + i);}return null;}
}
三种方法对比
继承Thread类编写简单,可直接操作线程适用于单继承
实现Runnable接口避免单继承局限性便于共享资源
- 1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
- 2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
推荐使用实现Runnable接口方式创建线程
4、线程调度
线程调度指按照特定机制为多个线程分配CPU的使用权
方 法 | 说 明 |
void setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程 |
boolean isAlive() | 测试线程是否处于活动状态 |
setPriority
package Test01;public class priority {public static void main(String[] args) {//线程优先级由1~10表示,1最低,默认为5//优先级搞得线程获得CPU资源的概率大Thread t1 = new Thread(new Mythread(),"线程A");Thread t2 = new Thread(new Mythread(),"线程B");t1.setPriority(Thread.MAX_PRIORITY);t2.setPriority(Thread.MIN_PRIORITY);t1.start();t2.start();}
}
sleep
package Test01;public class sleep {public static void main(String[] args) {//sleep方法,设置休眠时间//让进程展示睡眠指定时长,线程进入阻塞状态//睡眠时间过后线程会在进入可运行状态//public static void sleep(long mills)//mills为休眠时长,以毫秒为单位//调用sleep方法需要处理InterruptedException异常for (int i= 0;i < 5;i++){System.out.println(i + 1 + "秒");try{//休眠一秒Thread.sleep(1000);}catch (InterruptedException e){e.printStackTrace();}}}
}
join
package Test01;public class join {public static void main(String[] args) {//强制执行当前进程,join写在哪个线程,就阻塞谁//public final void join()//public final void join(long mills)//public final void join(long mills,int nanos)//mills:以毫秒为单位的等待时长//nanos:要等待的附加纳秒时长//需处理interruptedException异常Thread temp = new Thread(new Mythread());temp.start();for (int i = 0;i < 20;i++){if (i == 5){//当i=5时就会终止main,转而运行Thread-1/0try {temp.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + "运行:" + i);}}
}
yield
package Test01;public class Mythread extends Thread{@Overridepublic void run() {for (int i = 1;i<5;i++){System.out.println(Thread.currentThread().getName()+ ":" + i);if(i == 3){System.out.println("线程礼让");//线程礼让//yield展厅当前线程,允许其他具有相同优先级的线程获得运行机会//该线程处于就绪状态,不转为阻塞状态//只提供一种可能,但是不能保证一定会实现礼让//public static void yield();Thread.yield();}}}
}
interrupt
(总结一下:调用interrupt()方法,立刻改变的是中断状态,但如果不是在阻塞态,就不会抛出异常;如果在进入阻塞态后,中断状态为已中断,就会立刻抛出异常)
多线程共享数据引发的问题
多个线程操作同一共享资源时,将引发数据不安全问题
多个线程操作同一共享资源时,将引发数据不安全问题
5、线程同步
5.1 同步方法
使用synchronized修饰的方法控制对类成员变量的访问
访问修饰符 sunchronized 返回类型 方法名 (参数列表){...}
or
sunchronized 访问修饰符 返回类型 方法名 (参数列表){...}
//synchronized就是为当前的线程声明一把锁
5.2 同步代码块
使用synchronized关键字修饰的代码块
synchronized(syncObject){//需要同步的代码
}//其中syncObject为需同步的对象、通常为this
//效果与同步方法相同
public void run() {while (true) {synchronized (this) { //同步代码块// 省略修改数据的代码......// 省略显示信息的代码......
}}}
多个并发线程访问同一资源的同步代码块时
同一时刻只能有一个线程进入synchronized(this)同步代码块
当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
线程安全的类型
查看ArrayList类的add()方法的定义
ArrayList类的add方法为非同步方法
当多个线程向同一个ArrayList对象添加数据时,可能出现数据不一致问题
| 方法是否同步 | 效率比较 | 适合场景 |
线程安全 | 是 | 低 | 多线程并发共享资源 |
非线程安全 | 否 | 高 | 单线程 |
为达到安全性和效率的平衡,可以根据实际场景来选择合适的类型
常见类型对比
Hashtable && HashMap
Hashtable
继承关系
实现了Map接口,Hashtable继承Dictionary类
线程安全,效率较低
键和值都不允许为null
HashMap
继承关系
实现了Map接口,继承AbstarctMap类
非线程安全,效率较高
键和值都允许为null
StringBuffer && StringBuilder
前者线程安全、后者线程非安全
练习
练习一
需求说明创建两个子线程,每个线程均输出20次消息数字、“你好”、线程名观察多个线程交替执行的过程
HelloThrea
package exercise01;public class HelloThread extends Thread{@Overridepublic void run() {for (int i = 0;i < 20;i++){System.out.println(i + "、你好,来自线程" + Thread.currentThread().getName());}}
}
Test
package exercise01;public class Test {public static void main(String[] args) {HelloThread ht1 = new HelloThread();HelloThread ht2 = new HelloThread();ht1.start();ht2.start();}
}
练习二
需求说明修改上机练习1,要求线程类使用实现Runnable接口的方式创建,并修改测试类
HelloThread
package exercise02;public class HelloThread implements Runnable{@Overridepublic void run() {for (int i = 0;i < 20;i++){System.out.println(i + "、你好,来自线程" + Thread.currentThread().getName());}}
}
Test
package exercise02;import exercise02.HelloThread;public class Test {public static void main(String[] args) {HelloThread ht1 = new HelloThread();Thread t1 = new Thread(ht1);Thread t2 = new Thread(ht1);t1.start();t2.start();}
}
练习三
需求说明每个线程代表一个人可设置每人爬山速度每爬完100米显示信息爬到终点时给出相应提示
测试类
package exercise03;public class Test {public static void main(String[] args) {Thread t1 = new Thread(new ClimbThread(5,10),"年轻人");Thread t2 = new Thread(new ClimbThread(10,10),"老人");t1.start();t2.start();}
}
ClimbThread
package exercise03;public class ClimbThread extends Thread {private int time;private int num;public ClimbThread(int time, int num) {this.time = time;this.num = num;}public int getTime() {return time;}public void setTime(int time) {this.time = time;}public int getNum() {return num;}public void setNum(int num) {this.num = num;}@Overridepublic void run() {for(int i = num-1;i>0;i--){try {Thread.sleep(((long) this.time *(num-i)));System.out.println(Thread.currentThread().getName() + "爬完一百米");} catch (InterruptedException e) {e.printStackTrace();}}}
}
练习四
需求说明显示主线程、子线程默认优先级将主线程设置为最高优先级、子线程设置为最低优先级并显示
测试类
package exercise04;import java.util.Scanner;public class main {public static void main(String[] args) {Thread t = new Thread(new ChildThread());System.out.println("******修改前优先级******");System.out.println("主线程:" + Thread.currentThread().getName() + "、优先级为: " + Thread.currentThread().getPriority());System.out.println("子线程:" + t.getName() + "、优先级为: " + t.getPriority());System.out.println("修改主优先级与子线程优先级");Scanner sc = new Scanner(System.in);Thread.currentThread().setPriority(sc.nextInt());t.setPriority(sc.nextInt());System.out.println("******修改后优先级******");System.out.println("主线程:" + Thread.currentThread().getName() + "、优先级为: " + Thread.currentThread().getPriority());System.out.println("子线程:" + t.getName() + "、优先级为: " + t.getPriority());}
}
Child
package exercise04;public class ChildThread extends Thread{@Overridepublic void run() {System.out.println(" " + Thread.currentThread().getName());}
}
练习五
需求说明某科室一天需看普通号50个,特需号10个特需号看病时间是普通号的2倍开始时普通号和特需号并行叫号,叫到特需号的概率比普通号高当普通号叫完第10号时,要求先看完全部特需号,再看普通号使用多线程模拟这一过程
CummonNumber
package exercise05;public class CummonNumber extends Thread{private Thread VIpNumber1;public CummonNumber(Thread VIpNumber1){this.VIpNumber1 = VIpNumber1;}public Thread getVIpNumber1() {return VIpNumber1;}public void setVIpNumber1(Thread VIpNumber1) {this.VIpNumber1 = VIpNumber1;}@Overridepublic void run() {for (int i = 0;i<40;i++){System.out.println("普通号:" + (i + 1) + "号病人开始看病" );if (i == 9){try {VIpNumber1.join();} catch (InterruptedException e) {e.printStackTrace();}}try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}
}
VIpNumber
package exercise05;public class VIpNumber extends Thread{@Overridepublic void run() {for (int j = 0; j<10;j++){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("特需号:" + (j + 1)+ "号病人在看病");}}
}
Test
package exercise05;public class Test {public static void main(String[] args) {VIpNumber v1 = new VIpNumber();Thread t1 = new Thread(new CummonNumber(v1)) ;v1.setPriority(10);t1.setPriority(1);v1.start();t1.start();}
}
练习六
需求说明多人参加1000米接力跑每人跑100米,换下个选手每跑10米显示信息
Test
package exercise06;public class Test {public static void main(String[] args) {Run r1 = new Run();Thread t1 = new Thread(r1,"一号");Thread t2 = new Thread(r1,"二号");Thread t3 = new Thread(r1,"三号");t1.start();t2.start();t3.start();}
}
Run
package exercise06;public class Run extends Thread{private int sum = 300;private int num = 100;private Boolean flag =false;//这里加入syncronized使该方法一次只能由一个线程调用,来实现选手逐个交棒跑,而不是一起跑@Overridepublic synchronized void run() {
// while(true){
// if (flag){
// break;
// }else {
// go();
// }
// }if (!flag){go();}}public void go(){System.out.println(Thread.currentThread().getName() + "拿到了接力棒");for (int i = 0;i<=100;i++){if (i%10 == 0){System.out.println(Thread.currentThread().getName() + "选手跑了" + i + "米");}num -= 1;sum -= 1;if (sum <= 0){System.out.println("跑完了");flag = true;break;}}}
}
练习七
“桃跑跑”、“张票票”、“黄牛党”共同抢10张票限“黄牛党”只能抢一张票
Test
package exercise07;public class Test {public static void main(String[] args) {Ticket ticket = new Ticket();Thread t1 = new Thread(ticket,"陶泡泡");Thread t2 = new Thread(ticket,"里跳跳");Thread t3 = new Thread(ticket,"黄牛党");t1.start();t2.start();t3.start();}
}
Ticket
package exercise07;public class Ticket extends Thread{private int sum = 10;
// private Boolean flag = false;//这里不在这里使用synchronized是因为这里如果给润添加该修饰,会导致只有第一个进入的线程能获得票,而其他的线程不会货的票@Overridepublic void run() {while(true){//这里限制了进入方法体的数量,不会让他们同时进入方法体,而是一个一个进行访问synchronized (this) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}if (sum == 0) {
// flag = true;break;}}System.out.println(Thread.currentThread().getName() + "抢到了一张票" + "剩余" + sum + "张");sum -= 1;if ("黄牛党".equals(Thread.currentThread().getName())){break;}}}
}
这篇关于高级编程--第五章 多线程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!