java语言入门 (四) 多线程(4)

本章知识目标

1.0 掌握线程的概念,java线程调度思想,优先级及线程的状态转换关系
2.0 了解Thread类的常用方法
3.0 掌握编写线程的两种方法
4.0 掌握线程资源同步处理的方法
5.0 掌握JDK任务定时调度执行方法

java 多线程其实就是多个线程轮流占用cpu 每个任务占用的时间非常短
效果是每个任务并发执行

1.0 java线程的概念

1.1 多线程与多进程

进程是对操作系统来说 一次可以运行多个程序 就想你可以听歌的同时在这敲代码 这两个玩意就是进程
线程是对同一个程序来说,就像浏览器,视频软件 你看剧的同时还可以去看看简介 翻翻评论,但是这不影响看剧,大概就这意思。

1.2 线程的状态

新建线程的一个完整的生命周期经历一下五个状态

新建状态 通过start()方法进入 就绪状态 等待cpu调度他 进入 运行状态

进入巡行状态的线程会有以下三个去处

  1. 这个线程执行完毕,也就是run()方法执行完毕,进入终止状态
  2. 因为线程是分时间片执行的,所以这个时间片执行完后就不会占用cpu进入就绪状态,等待被调度
  3. 线程因为等待资源而进入的阻塞状态 可以这么理解 你有一个线程在吃饭,但是饭没了,你得等着
    直到饭盆有饭你才可以继续吃 进入到就绪状态
    所以线程的状态就是 1.0 新建 2.0 就绪 <---- (阻塞)------> 3.0 运行 5.0结束 大概这意思
1.3线程调度与优先级

java对于优先级高的优先调度,相同的优先级按照先到先服务来

下面几种情况当前线程会放弃cpu

  1. 当前时间片用完
  2. 线程调用了sleep()或者yield 方法
  3. 进行IO访问,或者等待某一资源,调用了wait()方法
  4. 有高优先级的线程参与了调度

主线程的默认优先级是5
Thread.MIN_PRIORIPTY 优先级是 1
Thread.MAX_PRIORIPTY 优先级是 10
Thread.NORM_PRIORIPTY 优先级是 5

2.0 java多线程的编程方法

编写java多线程的方法有两种

  1. 继承Thread 类
  2. 实现Runable接口

无论用那两种方法,都要在程序中编写run() 方法,线程要运行时要完成的任务在run()里完成

2.1Thread类简介

Thread类综合了java线程所需要拥有的属性和方法
他的构造方法为

public Thread(ThreadGroup group,Runnable target,String name)   //可以缺少一些参数

group 指的是线程所属的线程组,target为实际执行线程体的目标对象 name 为线程名

2.2 继承Thread类实现多线程
package xiancheng;
//这个就是线程的任务,属于一个线程任务
public class Thread_1 extends Thread{
	private int time;
	private String id;
	public Thread_1(int time,String id){
		this.time  = time;
		this.id = id;
	}
	public void run(){
		try {
			for(int i=0;i<100;i++){
				System.out.println("这是线程"+id+"数字是"+i);
			}
			System.out.println("这个线程执行了"+time+"s");
			System.out.println("这是一个"+id+"线程");
			Thread.sleep(time);
		} catch (InterruptedException e) {
			System.out.println("中断异常");
		}
	}

}



//我们在另一个类里调用这个线程  也可以在本类写主函数调用  随便了
package xiancheng;

public class Test {
	public static void main(String[] args) {
		Thread_1 thread_1 = new Thread_1(1, "线程 1");
		Thread_1 thread_2 = new Thread_1(1, "线程2 ");
		thread_1.start();
		thread_2.start();
	}
}


结果
这里写图片描述

可以看出线程是交替执行的
这里实际上是三个线程,主线程 我们建的两个线程。

2.3 实现Runnable接口实现多线程
package xiancheng;

public class Thread_1 implements Runnable{
	private int time;
	private String id;
	public Thread_1(int time,String id){
		this.time  = time;
		this.id = id;
	}
	public void run(){
		try {
			for(int i=0;i<100;i++){
				System.out.println("这是线程"+id+"数字是"+i);
			}
			System.out.println("这个线程执行了"+time+"s");
			System.out.println("这是一个"+id+"线程");
			Thread.sleep(time);
		} catch (InterruptedException e) {
			System.out.println("中断异常");
		}
	}

}


//调用函数
package xiancheng;

public class Test {
	public static void main(String[] args) {
		new Thread(new Thread_1(10, "线程1")).start();
		new Thread(new Thread_1(10, "线程2")).start();
		
	}
}

结果:
这里写图片描述

两种方式的区别和联系

1.0 当有的线程需要继承的别的类但是还是要实现多线程的时候 就回去实现Runnable接口
2.0 在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口;Thread类是在java.lang包中定义的。一个类只要继承了Thread类同时覆写了本类中的run()方法就可以实现多线程操作了,但是一个类只能继承一个父类,这是此方法的局限。
3.0 在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下好处:
避免点继承的局限,一个类可以继承多个接口。
适合于资源的共享

4.0 Thread类也是Runnable接口的子类。


3.0 线程资源的同步处理

首先说一下啥叫临界区 就是说个线程共享的数据称为临界资源
由于线程的调用时系统级别的 我们无法控制线程的执行过程 这就谁出现下面的问题

以栈的操作为例 线程A执行入栈操作,步骤如下

  1. 存入数据
  2. 栈顶指针 + 1

线程B执行出栈操作

  1. 找到栈顶指针
  2. 读数据
  3. 栈顶指针 - 1

那么问题来了 线程A在执行第一步后 停止,线程2执行了第 1 2 步,则因为A没有给栈顶指针+1
所以线程B读的栈顶指针是错误的。这样程序就会出问题
那怎么办呢
我们讲一个关键字 synchronzied 对象锁

package xiancheng;

public class Thread_1 implements Runnable{

	
	@Override
	public void run() {
		synchronized (this) {
			for(int i = 0;i< 100;i++){
				System.out.println(Thread.currentThread().getName()+" "+i);
			}
		}
		
		
	}
	
	
}


//主函数
package xiancheng;

public class Test {
	public static void main(String[] args) {
		Thread_1 thread_1 = new Thread_1();
			
		new Thread(thread_1,"线程1").start();
		new Thread(thread_1,"线程2").start();
	
	}
}
结果:
线程2 0
线程2 1
线程2 2
线程2 3
线程2 4
线程2 5
线程2 6
线程2 7
线程2 8
线程2 9
线程2 10
线程2 11
线程2 12
线程2 13
线程2 14
线程2 15
线程2 16
线程2 17
线程2 18
线程2 19
线程2 20
线程2 21
线程2 22
线程2 23
线程2 24
线程2 25
线程2 26
线程2 27
线程2 28
线程2 29
线程2 30
线程2 31
线程2 32
线程2 33
线程2 34
线程2 35
线程2 36
线程2 37
线程2 38
线程2 39
线程2 40
线程2 41
线程2 42
线程2 43
线程2 44
线程2 45
线程2 46
线程2 47
线程2 48
线程2 49
线程2 50
线程2 51
线程2 52
线程2 53
线程2 54
线程2 55
线程2 56
线程2 57
线程2 58
线程2 59
线程2 60
线程2 61
线程2 62
线程2 63
线程2 64
线程2 65
线程2 66
线程2 67
线程2 68
线程2 69
线程2 70
线程2 71
线程2 72
线程2 73
线程2 74
线程2 75
线程2 76
线程2 77
线程2 78
线程2 79
线程2 80
线程2 81
线程2 82
线程2 83
线程2 84
线程2 85
线程2 86
线程2 87
线程2 88
线程2 89
线程2 90
线程2 91
线程2 92
线程2 93
线程2 94
线程2 95
线程2 96
线程2 97
线程2 98
线程2 99
线程1 0
线程1 1
线程1 2
线程1 3
线程1 4
线程1 5
线程1 6
线程1 7
线程1 8
线程1 9
线程1 10
线程1 11
线程1 12
线程1 13
线程1 14
线程1 15
线程1 16
线程1 17
线程1 18
线程1 19
线程1 20
线程1 21
线程1 22
线程1 23
线程1 24
线程1 25
线程1 26
线程1 27
线程1 28
线程1 29
线程1 30
线程1 31
线程1 32
线程1 33
线程1 34
线程1 35
线程1 36
线程1 37
线程1 38
线程1 39
线程1 40
线程1 41
线程1 42
线程1 43
线程1 44
线程1 45
线程1 46
线程1 47
线程1 48
线程1 49
线程1 50
线程1 51
线程1 52
线程1 53
线程1 54
线程1 55
线程1 56
线程1 57
线程1 58
线程1 59
线程1 60
线程1 61
线程1 62
线程1 63
线程1 64
线程1 65
线程1 66
线程1 67
线程1 68
线程1 69
线程1 70
线程1 71
线程1 72
线程1 73
线程1 74
线程1 75
线程1 76
线程1 77
线程1 78
线程1 79
线程1 80
线程1 81
线程1 82
线程1 83
线程1 84
线程1 85
线程1 86
线程1 87
线程1 88
线程1 89
线程1 90
线程1 91
线程1 92
线程1 93
线程1 94
线程1 95
线程1 96
线程1 97
线程1 98
线程1 99

可以看出来 给对象加锁的时候 一个程序在执行线程的时候,不允许其他线程执行 也就是需要锁的内容执行完毕后该线程才会失去对cpu的使用

这里的对象锁就是
synchronized (this) {
for(int i = 0;i< 100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
中的this this 代表这个类的对象,也就是说对于这个类实例化的同一个对象进行操作这个锁才会起作用,若果我们把主函数改为

	public static void main(String[] args) {
//		Thread_1 thread_1 = new Thread_1();
			
		new Thread(new Thread_1(),"线程1").start();
		new Thread(new Thread_1(),"线程2").start();
	
	}

那么锁不会有效果,因为这是两个对象了,这时候我们需要的是类锁

package xiancheng;

public class Thread_1 implements Runnable{

	
	@Override
	public void run() {
		synchronized (Thread_1.class) {
			for(int i = 0;i< 100;i++){
				System.out.println(Thread.currentThread().getName()+" "+i);
			}
		}
		
		
	}
	
	
}

方法锁与对象锁差不多
锁的详解点击这里

3.1 wait()和notify()方法

在我看来这相当于操作系统中的PV操作,如果你有操作系统的基础,那么这很容易理解,
下面看个例子吧 我们来看过独木桥问题,河的两边的人要过河,但是独木桥一次就让一个人上,所有当一个线程占用桥的时候,其他的线程必须等待 也就是wait(),过河之后唤起其他等待的线程

//首先我们写上桥和下桥的方法

package xiancheng;

public class Bride {
	private boolean flag = false;//桥没有被占用
	//上桥方法  如果桥被占用 就进入循环  线程挂起,如果没被占用则 把桥的状态改为被占用,说明有人上桥了
	//可以理解为见到wait() 方法,这个
	public synchronized	void upBridge(){
		while(flag){
			try {
				wait();//桥被占用就等待
			} catch (InterruptedException e) {
				System.out.println("处理中断异常");
				e.printStackTrace();
			}
		}
		flag = true;//桥被占用
		
	}
	//下桥方法  下桥后唤醒等待的线程,可以理解从上面的上桥方法中的wait()
	public synchronized void downBridge(){
		notify();//唤醒当前对象的线程
		flag = true;//桥没有被占用
	}
}

//下面这个是线程   用来产生线程 模拟人
package xiancheng;

public class Thread_2 implements Runnable {
	
	@Override
	public void run() {
		Bride bride = new Bride();
		bride.upBridge();
		System.out.println(Thread.currentThread().getName() );
		try {
			Thread.sleep(1000);//模拟过桥时间
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		bride.downBridge();//下桥了
	}
	
	
}

//主函数
package xiancheng;

public class Test {
	public static void main(String[] args) {
		for(int i = 0;i < 5;i++){
			new Thread(new Thread_2(),"南边第"+i+"个人过桥呢。。。。").start();
		}
		
		for(int i = 0;i < 3;i++){
			new Thread(new Thread_2(),"北边第"+i+"个人过桥呢。。。。").start();
		}
	
	}
}

notify() 函数是唤醒所有的等待线程

4.0 死锁

向上面的例子,假如上桥方法一直在等待,而下桥方法中迟迟没有释放 也就是 notify()没有执行

那么upBridge 就会一直等待从而不会执行 就产生了死锁

5.0 java的定时任务处理

java 提供了 java.util.Timer 也叫定时器类 可以定时调度 TimerTask 类型的任务.

Timer类中有schedule()来安排任务

task 代表任务 delay 代表延迟时间 period代表间隔时间 time代表启动时间

schedule(TimerTask task,long delay);

schedule(TimerTask task,Date time);

scheduleAtFixedRate(TimerTask task,long delay,long period);

scheduleAtFixedRate(TimerTask task,Date time,long period);

来个例子

package timerTask;

import java.util.Timer;
import java.util.TimerTask;

public class t1 extends TimerTask{

	@Override
	public void run() {
		System.out.println("假装有程序在运行");
		
	}

	
	public static void main(String[] args) {
		t1 t1 = new t1();
		Timer timer = new Timer();
		timer.schedule(t1, 10000, 2000);//程序运行开始 10秒后开始执行任务  每2秒执行一次
	}
}
结果
//10秒后才开始显示的  下面的每2秒执行一次
假装有程序在运行
假装有程序在运行
假装有程序在运行
假装有程序在运行

Java并发编程内容非常多 建议专门买一本书看

欢迎评论交流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值