多线程
一、多线程的基本概念
1、什么是进程
一个进程对应一个应用程序。例如:在windows操作系统启动Word就代表启动了一个进程。在java环境下启动JVM,就代表启动了一个进程。现代的计算机都是支持多进程的,在同一个操作系统中国,可以同时启动多个进程。
2、多进程有什么作用
单进程计算机只能做意见事情。
玩电脑,一边玩游戏(游戏进程)一边听音乐(音乐进程)
对于单核计算机来讲,在同一个时间点上,游戏进程和音乐进程事同时运行的吗?不是。
因为计算机的cpu只能在某一个时间点上做一件事情,由于计算机将在“游戏进程”和"音乐进程"之间频繁的切换执行,切换速度极高,人类感觉游戏和音乐在同时执行。
多进程的作用不是提高执行速度,而是提高CPU的使用率
进程和进程之间的内存是独立的
3、什么是线程
线程是一个进程中的执行场景,一个进程可以启动多个线程。
4、多线程有什么作用
多线程不是为了提高执行速度,而是为了提高应用程序的使用率
线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程一个栈
5、java 程序的运行原理
java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,表示启动了一个进程,该进程会启动一个“主线程”,然后主线程去调用某个类的main方法,所以main方法运行在主线程中。在此之前所有的程序都是单线程的。
二、线程的创建和启动
/*
在java语言中实现多线程的第一种方式:
第一步:继承java.lang.Thread;
第二步:重写run方法
三个知识点:
如何定义线程
如何创建线程
如何调用线程
*/
public class ThreadTest02{
public static void main(Stirng[] args){
//创建线程
Thread t = new Processor();
//启动
t.start(); //这段代码执行瞬间结束,告诉JVM再分配一个新的栈给t线程
//run不需要程序员手动调用,系统线程启动之后自动调用run方法
//t.run(); //这是一个普通的方法调用,这样的程序只有一个线程,run方法结束之后,下面的程序才能继续执行
//这段代码在主线程中执行
for(int i = 0;i<10;i++){
System.out.println("main-->"+i);
}
//有了多线程之后,main方法结束只是主线程中没有方法栈帧了
//但是其他线程或其他栈中还有栈帧
//main方法结束,程序可能还在运行
}
}
//定义一个线程
class Processor extends Thread{
//重写run方法
public void run(){
for(int i = 0;i<100;i++){
System.out.println("run-->" + i);
}
}
}
/*
java中实现线程的第二种方式:
第一步:写一个类实现java.lang.Runnable;接口
第二部:实现run()方法
*/
public class ThreadTest03{
public static void main(String[] args){
//创建线程
Thread t = new Thread(new Processor());
//启动
t.start();
}
}
//定义线程(推荐使用这种方式)
class Processor implements Runnable{
public class run(){
for(int i = 0;i<10;i++){
System.out.println("run-->"+i);
}
}
}
三、线程的生命周期
四、线程的调度与控制
通常我们的计算机只有一个cpu,cpu在某一个时刻只能执行一条指令,线程只有得到cpu的时间片,也就是使用权,才能执行指令。在单cpu的机器上,线程不是并行运行的,只有在多个cpu上线程才可以并行运行。java虚拟机要负责线程的调度,取得cpu的使用权,目前有两种调度模型:分时调度模型和抢占式调度模型,java使用抢占式调度模型。
分时调度模型:所有线程轮流使用cpu的使用权,平均分配每个线程使用cpu的时间片
抢占式调度模型:给优先级高的线程分配相对比较多的时间片,如果线程的优先级相同,那么会随机分配时间片
/*
Thread的三个方法
1、获取当前线程对象
Thread.currentThread();
2、获取线程名字与给线程起名字
Thread.currentThread().getName();
Thread.currentThread().setName();
3、重新设置线程优先级(默认(NORM_PRIORITY)是5,最小(MIN_PRIORITY)1,最大(MAX_PRIORITY)10)
Thread.currentThread().setPriority(1~10);
*/
sleep
/*
1、Thread.sleep(毫秒);
2、sleep方法是一个静态方法
3、该方法的作用:阻塞当前线程,腾出CPU,让给其他线程
*/
public class ThreadTest06{
public static void main(String[] args){
Thread t1 = new Processor();
t1.setName("t1");
t1.start();
//阻塞主线程
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName+"--->"+i);
Thread.sleep(500);//这个sleep方法是一个静态方法,所以永远指向的是当前线程
}
}
//定义线程
class Processor extends Thread{
//Thread 中的run方法不抛出异常,所以重写run方法之后,在run方法的声明位置上不能使用throws
//所以run方法中的异常只能try。。。catch。。
public void run(){
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
try{
Thread.sleep(1000);//让当前线程阻塞1秒
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
/*
某线程正在休眠,如何打断它的休眠
以下方式依靠的是异常处理机制
*/
public class ThreadTest08{
public static void main(String[] args){
//需求:启动线程,5秒之后打断线程的休眠
Thread t = new Thread(new Processor());
//起名
t.setName("t");
//启动
t.start();
//5s之后
Thread.sleep(5000);
//打断t的休眠
t.interrupt();
}
}
class Processor implements Runnable{
public void run(){
try{
Thread.sleep(1000000000000L);
System.out.println("HelloWorld");
}catch(InterruptedException e){
//e.printStackTrace();
}
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread.getName()+"--->"+i);
}
}
}
/*
如何正确的更好的终止一个正在执行的线程
需求:线程启动后5s之后终止。
*/
public class ThreadTest09{
public static void main(String[] args){
Processor p = new Processor();
Thread t = new Thread(p);
t.setName("t");
//5秒之后终止
t.sleep(5000);
//终止
p.run = false;
}
}
class Processor implements Runnable{
boolean run = true;
public void run(){
for(int i = 0;i<10;i++){
if(run){
try{Thread.sleep(1000);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"-->"+i);
}else{
return;
}
}
}
}
yield
/*
Thread.yield();
1、该方法是一个静态方法
2、作用:给同一优先级的线程让位,但是让位时间不固定
3、和sleep方法相同,就是yield时间不固定
*/
public class ThreadTest10{
public static void main(String[] args){
Thread t = new Processor();
t.setName("t");
t.start();
//主线程
for(int i = 0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
class Processor{
public void run(){
for(int i = 0;i<100;i++){
System.out.println(Thread.currentThrad().getName()+"-->"+i);
if(i%20==0){
Thread.yield();
}
}
}
}
join(成员方法)
/*
线程的合并
*/
public class ThreadTest11{
public static void main(String[] args){
Thread t = new Thread(new Processor());
t.setName("t");
t.start();
//合并线程
t.join();//将t线程合并到主线程
//主线程
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
class Processor implements Runnable{
public void run(){
for(int i = 0;i<5;i++){
try{
Thread.sleep(1000);
}catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
五、线程的同步(加锁)
未使用线程安全机制
/*
t1 t2两个线程
异步编程模型:t1线程执行t1的,t2线程执行t2的,两个线程之间谁也不等谁
同步编程模型:t1线程和t2线程执行,t1线程必须等t2线程执行完毕后才能执行
什么时候要同步呢?为什么要引入线程同步?
1、为了数据的安全。尽管应用程序的效率减低了,但是为了保证数据是安全的,必须使用线程同步
2、什么条件下使用线程同步
第一:必须是多线程环境下
第二:多线程环境共享一个数据
第三:共享数据涉及到修改操作
以下程序演示取款例子,以下程序不使用线程同步机制,多线程同时对一个账户进行取款,会出现什么问题?
*/
public class ThreadTest12{
public static void main(String[] args){
//创建一个公共的账户
Account act = new Account("actno-001",5000.0);
//创建线程对同一个账户取款
Thread t1 = new Thread(new Processor(act));
Thread t2 = new Thread(new Processor(act));
t1.start();
t2.start();
}
}
//取款线程
class Processor implements Runnable{
//账户
Account act;
//Constructor
Processor(Account act){
this.act = act;
}
public void run(){
act.withdraw(1000.0);
System.out.println("取款1000.0成功,余额:"+act.getBalance());
}
}
//账户
class Account{
private String actno;
private double balance;
//Constructor
public Account(){}
public Account(String actno,double balance){
this.actno = actno;
this.balance = balance;
}
//setter and getter
public void setActno(String actno){
this.actno = actno;
}
public void setBalance(double balance){
this.balance = balance;
}
public String getActno(){
return actno;
}
public double getBalance(){
return balance;
}
//对外提供一个取款的方法
public void withdraw(double money){//对当前账户进行取款操作
double after = balance - money;
//延迟
try{Thread.sleep(1000)}catch(Exception e){}
//更新
this.setBalance(after);
}
}
使用线程安全机制
/*
以下程序使用线程同步机制保证数据安全
*/
public class ThreadTest13{
public static void main(String[] args){
//创建一个公共的账户
Account act = new Account("actno-001",5000.0);
//创建线程对同一个账户取款
Thread t1 = new Thread(new Processor(act));
Thread t2 = new Thread(new Processor(act));
t1.start();
t2.start();
}
}
//取款线程
class Processor implements Runnable{
//账户
Account act;
//Constructor
Processor(Account act){
this.act = act;
}
public void run(){
act.withdraw(1000.0);
System.out.println("取款1000.0成功,余额:"+act.getBalance());
}
}
//账户
class Account{
private String actno;
private double balance;
//Constructor
public Account(){}
public Account(String actno,double balance){
this.actno = actno;
this.balance = balance;
}
//setter and getter
public void setActno(String actno){
this.actno = actno;
}
public void setBalance(double balance){
this.balance = balance;
}
public String getActno(){
return actno;
}
public double getBalance(){
return balance;
}
//对外提供一个取款的方法
public void withdraw(double money){//对当前账户进行取款操作
//把需要同步的代码,放到同步语句块中
/*
原理:当线程遇到synchronized关键字,就去找对象锁,找到了就执行同步语句块,执行完后归还对象锁,找不到的话就等待(所有的对象都有一个对象锁)
*/
synchronized(this){//参数:共享对象
double after = balance - money;
//延迟
try{Thread.sleep(1000)}catch(Exception e){}
//更新
this.setBalance(after);
}
}
/*
//synchronized关键字添加到成员方法上,线程拿走的也是this的对象锁
public synchronized void withdraw(double money){//对当前账户进行取款操作
//把需要同步的代码,放到同步语句块中
/*
原理:当线程遇到synchronized关键字,就去找对象锁,找到了就执行同步语句块,执行完后归还对象锁,找不到的话就等待(所有的对象都有一个对象锁)
*/
*/
/*
double after = balance - money;
//延迟
try{Thread.sleep(1000)}catch(Exception e){}
//更新
this.setBalance(after);
}
*/
}
类锁:如果synchrnoized加在有static关键字的地方就是加了类锁
死锁
/*
死锁
*/
public class DeadLock{
public static void main(Stirng[] args){
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new Thread(new T1(o1,o2));
Thread t2 = new Thread(new T2(o1,o2));
t1.start()
t2.start()
}
}
class T1 implements Runnable{
Object o1;
Object o2;
T1(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized(o1){
Thread.sleep(1000);
synchronized(o2){
}
}
}
}
class T2 implements Runnable{
Object o1;
Object o2;
T2(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized(o2){
Thread.sleep(1000);
synchronized(o1){
}
}
}
}
六、守护线程
从线程分类上可以分为:用户线程和守护线程。守护线程是这样的,所有的用户线程结束生命周期,守护线程才会结束生命周期,只要有一个用户线程存在,那守护线程就不会结束,例如java中著名的垃圾回收器就是一个守护线程,只有应用程序中所有的程序结束,它才会结束。
/*
守护线程
其他所有用户线程结束,则守护线程退出
守护线程一般都是无限执行
*/
public class ThreadTest19{
public static void main(Stirng[] args){
Thread t1 = new Processor();
t1.setName("t1");
//将t1修改为守护线程
t1.setDaemon(true);
t1.start();
//主线程
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
Thread.sleep(1000);
}
}
}
class Processor{
public void run(){
int i = 0;
while(true){
i++;
System.out.println(Thread.currentThread().getName()+"-->"+i);
Thread.sleep(1000);
}
}
}
七、定时器的使用
/*
关于定时器的应用
作用:每隔一段固定的时间执行一段代码
*/
import java.text.*;
import java.util.*;
public class TimerTest01{
public static void main(String[] args){
//创建定时器
Timer t = new Timer();
//指定定时任务
t.schedule(new LogTimerTask(),//定时任务
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss").parse("2020-12-12 10:00:00 000"),//开始时间
10*1000//间隔
);
}
}
//指定定时任务
class LogTimerTask extends TimerTask{
public void run(){
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss").format(new Date()));
}
}
八、windows的任务计划
在windows中的任务计划程序可以设置定时任务
java.util.*;
public class TimerTest01{
public static void main(String[] args){
//创建定时器
Timer t = new Timer();
//指定定时任务
t.schedule(new LogTimerTask(),//定时任务
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss").parse("2020-12-12 10:00:00 000"),//开始时间
10*1000//间隔
);
}
}
//指定定时任务
class LogTimerTask extends TimerTask{
public void run(){
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss").format(new Date()));
}
}
## 八、windows的任务计划
在windows中的任务计划程序可以设置定时任务
[外链图片转存中...(img-xSqSpS06-1599805595791)]
参考自:https://www.bilibili.com/video/BV1kx411h7jv?p=223