18.多线程
进程和线程的区
- 每个进程都有独立的代码和数据空间,进程的切换会有很大的开销
- 同一类线程共享代码和数据空间,每个线程有独立运行的栈和程序计数器,线程切换的开销小
多进程和多线程
- 多进程:在操作系统中能同时运行多个任务(程序)
- 多线程:在同一应用程序中有多个顺序流同时执行
多线程实现的两种方式
- 继承Thread类 —— java.lang.Thread
- 实现Runnable接口 —— java.lang.Runnable
run( )方法 — 线程运行体
要将一段代码(线程体)在一个新的线程上运行,该代码应该在一个线程类的run( )函数中
- 写一个类implements Runnable接口,且必须覆盖Runnable接口中的run( )方法
- 写一个类extends Thread类,且必须重写Thread类的run( )方法
18.1 线程状态及其生命周期
一个 Thread 对象在它的整个生存期中能以几种不同的状态存在
start( ) — 方法使线程处于可以运行的状态,但 不一定意味着该线程立即开始运行
包含等待的线程状态
18.2 继承Thread类实现线程类
- 定义线程类继承Thread类
- 覆盖run( )方法 public void run( )
- run( ) 与 start( )
package com.neusoft.thread;
/**
* 项目: javaadvice
* 类名: MyGc
* 创建时间: 2020/11/24 9:05
* 描述 :
* 作者 : 张金山
* QQ : 314649444
* Site: https://jshand.gitee.io
*/
public class MyGc extends Thread {
@Override
public void run() {
while (true){
System.out.println("Gc ... "+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.neusoft.thread;
/**
* 项目: javaadvice
* 类名: App
* 创建时间: 2020/11/24 9:07
* 描述 :
* 作者 : 张金山
* QQ : 314649444
* Site: https://jshand.gitee.io
*/
public class App {
public static void main(String[] args) {
// new MyGc().run(); 错误的启动方式
new MyGc().start(); //启动线程
while (true){
System.out.println("Main ... "+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
18.3 实现Runnable接口实现线程类
- 定义线程类实现Runnable接口
- 线程共享同样 的数据和代码
- 覆盖Runnable接口中的唯一的方法 public void run( )
- 使用Runnable接口可以避免由于JAVA的单继承性带来的局限
- 适合多个相同的程序代码的线程去处理同一资源情况,把线程同程序的代码、数据有效的分离 推荐使用实现Runnable接口
package com.neuedu.thread;
/**
* 项目:java02
* 创建时间: 2021/11/24 16:31
* 作者 :jshand
* 描述 :
*/
public class Program extends Object implements Runnable {
int day = 1;
int sleep = 1000;
private String name;
public Program(String threadName,int sleep) {
this.name = threadName;
this.sleep = sleep;
}
//多线程是执行的 调度函数
@Override
public void run() {
while (true){
System.out.println(name+"工作 "+(day++));
//休眠1000毫秒
try {
Thread.sleep(sleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.neuedu.thread;
/**
* 项目:java02
* 创建时间: 2021/11/24 16:24
* 作者 :jshand
* 描述 :
*/
public class Runner {
public static void main(String[] args) {
// Work w1 = new Work("Java开发人员" , 1000);
// Work w2 = new Work("Python开发人员",3000);
//
// //启动线程
// w1.start();
// w2.start();
Program p1 = new Program("Java开发人员" , 1000);
Program p2 = new Program("Python开发人员",3000);
//启动线程
new Thread(p1).start();
new Thread(p2).start();
}
}
18.4 线程类中的主要方法
- 线程的优先级
- 线程的休眠
package com.neusoft.thread;
/**
* 项目: javaadvice
* 类名: MyTicket
* 创建时间: 2020/11/24 9:24
* 描述 :
* 作者 : 张金山
* QQ : 314649444
* Site: https://jshand.gitee.io
*/
public class MyTicket implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Ticket ... "+System.currentTimeMillis()+"\tpre\t"+Thread.currentThread().getPriority());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.neusoft.thread;
/**
* 项目: javaadvice
* 类名: App
* 创建时间: 2020/11/24 9:07
* 描述 :
* 作者 : 张金山
* QQ : 314649444
* Site: https://jshand.gitee.io
*/
public class App {
public static void main(String[] args) {
// new MyGc().run();
// new MyGc().start(); //启动线程
Thread t1 = new Thread(new MyTicket());
t1.setPriority(Thread.MIN_PRIORITY);
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true){
System.out.println("监督员 ... "+System.currentTimeMillis()+"\tt1isAlive\t"+t1.isAlive());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程的中断
javapackage com.neusoft.thread; /** * 项目: javaadvice * 类名: MyTicket * 创建时间: 2020/11/24 9:24 * 描述 : * 作者 : 张金山 * QQ : 314649444 * Site: https://jshand.gitee.io */ public class MyTicket implements Runnable { private boolean isStop = false; @Override public void run() { while(true){ System.out.println("Ticket ... "+System.currentTimeMillis()+"\tpre\t"+Thread.currentThread().getPriority()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(isStop) { break; } } } public boolean isStop() { return isStop; } public void setStop(boolean stop) { isStop = stop; } }
javapackage com.neusoft.thread; /** * 项目: javaadvice * 类名: App * 创建时间: 2020/11/24 9:07 * 描述 : * 作者 : 张金山 * QQ : 314649444 * Site: https://jshand.gitee.io */ public class App { public static void main(String[] args) { MyTicket ticket = new MyTicket(); Thread t1 = new Thread(ticket); t1.setPriority(Thread.MIN_PRIORITY); t1.start(); for (int i = 0;; i++) { System.out.println("监督员 ... "+System.currentTimeMillis()+"\tt1isAlive\t"+t1.isAlive()); try{ if(i==5 ){ // t1.stop(); // t1.interrupt(); //设置状态,通过状态修改程序的执行星狂 ticket.setStop(true); } Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
18.5 线程同步
- 有时两个或多个线程可能会试图同时访问一个资源
- 例如,一个线程可能尝试从一个文件中读取数据,而另一个线程则尝试在同一文件中修改数据 在此情况下,数据可能会变得不一致
- 为了确保在任何时间点一个共享的资源只被一个线程使用,使用了“同步”
- 当一个线程运行到需要同步的语句后,CPU不去执行其他线程中的、可能影响当前线程中的下一句代码的执行结果的代码块,必须等到下一句执行完后才能去执行其他线程中的相关代码块,这就是线程同步
18.5.1 锁
- 对象的锁标志
- 每个对象都有一个锁标志 使用synchronized可与锁标志交互
- synchronized关键字的用法有两种:
- synchronized语句
- synchronized方法
- synchronized语句: synchronized (obj)
一旦一个包含同步方法(用synchronized修饰)的线程被CPU调用,其他线程就无法调用相同对象的同步方法。当一个线程在一个同步方法内部,所有试图调用该方法的同实例的其他线程必须等待
18.5.2 代码实现
package com.neusoft.thread;
/**
* 项目: javaadvice
* 类名: SellTicket
* 创建时间: 2020/11/24 10:28
* 描述 :
* 作者 : 张金山
* QQ : 314649444
* Site: https://jshand.gitee.io
*/
public class SellTicket implements Runnable {
private String name = "";
public static int num = 50;
private Object obj = new Object();
public SellTicket(String name) {
this.name = name;
}
@Override
public void run() {
while (num>0) {
System.out.println(name+"在卖票\t "+getTicket());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//方法级别的同步锁
// private synchronized int getTicket(){
// return num--;
// }
//对象级别的锁
private int getTicket(){
synchronized (obj){
return num--;
}
}
public static void main(String[] args) {
new Thread(new SellTicket("王小花")).start();
new Thread(new SellTicket("牛晓磊")).start();
}
}
注意事项:
受到synchronized保护的程序代码块和方法中,要访问的对象属性必须设定为private,因为如果不设定为private,那么就可以用不同的方式来访问它,这样就达不到保护的效果了
18.5.3 实现同步的两种方式—两种方式的优缺点
synchronized方法
- 优点:
- 可以显示的知道哪些方法是
- 被synchronized关键字保护的
- 缺点:
- 方法中有些内容是不需要同步的,如果该方法执行会花很长时间,那么其他人就要花较多时间等待锁被归还;
- 只能取得自己对象的锁,有时候程序设计的需求,可能会需要取得其他对象的锁;
synchronized代码块
- 优点:
- 可以针对某段程序代码同步,不需要浪费时间在别的程序代码上;
- 可以取得不同对象的锁;
- 缺点:
- 无法显示的得知哪些方法
- 是被synchronized关键字保护的;
18.5.4 死锁
两个线程,彼此在等待对方占据的锁
锁的归还几种方式:
- 基本上执行完同步的程序代码后,锁就会自动归还;
- 用break语句跳出同步的语句块,不过这对于写在方法声明的synchronized没有作用;
- 遇到return语句;
- 遇到了异常;
18.6 练习
1、利用Thread实现,要求多线程求解某范围素数每个线程负责1000范围:线程1找1-1000;线程 2 找 1001-2000;线程 3 找2001-3000。编程程序将每个线程找到的素数及时打印。 [必做题]
2、利用Runnable实现,要求多线程求解某范围素数每个线程负责1000范围:线程1找1-1000;线程 2 找 1001-2000;线程 3 找2001-3000。编程程序将每个线程找到的素数及时打印。 [必做题]
3、编写一个Java程序(包括一个主程序类,一个线程类。在主程序类中创建2个线程,将其中一个线程的优先级设为10,另一个线程的优先级设为6。让优先级为10的线程打印200次“线程1正在运行”,优先级为6的线程打印200次“线程2正在运行”。 [选做题]
4、编写一个计时器,每隔一秒钟,在控制台打印出最新时间。 [必做题]
素数
package com.neusoft.thread;
/**
* 项目: javaadvice
* 类名: Sushu
* 创建时间: 2020/11/24 11:44
* 描述 : 素数
* 作者 : 张金山
* QQ : 314649444
* Site: https://jshand.gitee.io
*/
public class Sushu extends Thread {
int start = 0;
int end = 0;
public Sushu(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public void run() {
for (int i = start; i <=end; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean is = true;
for (int j = 2; j <i ; j++) {
if( i %j == 0){
is = false;
break;
}
}
if(is){
System.out.println("素数\t"+i);
}
}
}
public static void main(String[] args) {
new Sushu(1,1000).start();
new Sushu(1001,2000).start();
new Sushu(2001,3000).start();
}
}
线程200次
package com.neusoft.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 项目: javaadvice
* 类名: TimeThread
* 创建时间: 2020/11/24 11:46
* 描述 :
* 作者 : 张金山
* QQ : 314649444
* Site: https://jshand.gitee.io
*/
public class TimeThread implements Runnable {
String name ;
public TimeThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(name+ "正在运行");
try {
Thread.sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new TimeThread("线程1"));
t1.setPriority(Thread.MAX_PRIORITY);
Thread t2 = new Thread(new TimeThread("线程2"));
t2.setPriority(6);
t1.start();
t2.start();
}
}
计时器
public static void main(String[] args) {
new Thread(()->{
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
while(true){
Date now = new Date();
String time = simpleDateFormat.format(now);
System.out.println(time);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
