11、多线程

目录

01、认识线程

02、编写线程类

1、使用Thread类创建线程

示例1:Thread类常用方法(部分)

示例2:使用继承Thread类的方式创建线程,在线程中输出1~20的整数

 2、使用Runnable接口创建线程

示例3:使用Runnable接口的方式擦行间线程,在线程中输出1~20的整数 

3、以上两种创建线程的方式比较

03、线程的状态

04、线程调度

1、线程优先级

2、线程休眠( sleeep()方法 )

示例4:sleep()方法

3、线程的强制运行( join()方法 )

示例5: join方法

 4、线程的礼让( yield()方法 )

示例6: yield()方法

5、sleep()方法与yield()方法的区别

05、线程同步

1、线程同步的必要性

2、多线程共享数据引发的问题

示例7-1:网络购票(多线程共享数据出现问题案例)

3、实现线程同步(解决多线程共享数据出现的问题)

(1)同步方法:使用synchronized修饰的方法控制对类成员变量的访问

                示例7-2:使用同步方法的网络购票

 (2)同步代码块:使用synchronized关键字修饰的代码块

                示例7-3: 使用synchronized关键字修饰的代码块

06、线程安全的类型

1、线程安全与非线程安全的比较

2、常见类型比较

Hashtable && HashMap

StringBuffer && StringBuilder


01、认识线程

进程:应用程序的执行实例,有独立的内存空间和系统资源

线程:CPU调度和分派的基本单位、进程中执行运算的最小单位,可完成一个独立的顺序控制流程

多线程:如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”,多线程交替占用CPU资源,而非真正的并发执行。

多线程的好处:

        (1)充分利用CPU的资源

        (2)简化编程模型

        (3)带来良好的用户体验

02、编写线程类

        每个程序至少自动拥有一个线程,称为主线程。当程序加载到内存时启动主线程。Java程序中的public static void main()方法是主线程的入口,会先执行这个方法。

        定义一个线程类 通常有两种方法,分别是:

        (1)继承java.lang.Thread类

        (2)实现java.lang.Runnable接口

1、使用Thread类创建线程

表 Thread类的常用方法
方法说明
currentThread()获取当前线程对象
getName()获取当前线程的名称
setName()设置线程的名称
getPriority()获取当前线程的优先级
setPriority()设置线程优先级
start()启动线程
void run()执行任务操作的方法
表 Thread类的字段
字段摘要说明
MAX_PRIORITY线程优先级最高值
MIN_PRIORITY线程优先级最低值
NORM_PRIORITY线程优先级默认值

        创建线程时继承Thread类并重写Thread类的run()方法。Thread类的run()方法是线程要执行操作任务的方法,所以线程要执行的操作代码都需要写在run()方法中,并通过调用start()方法来启动线程。

示例1:Thread类常用方法(部分)

package cn.bdqn.demo01;

public class ThreadDemo01 {

	public static void main(String[] args) {
		//main()方法是程序的主入口,是一个线程
		
		//currentThread():获取当前线程对象
		Thread thread = Thread.currentThread();
		
		//getName():获取当前线程的名称
		String name = thread.getName();
		System.out.println(name);
		
		//setName():设置线程的名称
		thread.setName("恒哥");
		System.out.println("设置后的线程名称为:"+thread.getName());
			
		//getPriority():获取当前线程的优先级
		int priority = thread.getPriority();
		System.out.println("当前线程的优先级:"+priority);
		
		//setPriority():设置线程的优先级
		thread.setPriority(8);
		System.out.println("设置后的线程优先级:"+thread.getPriority());
		
		System.out.println("线程优先级最高值"+Thread.MAX_PRIORITY);
		System.out.println("线程优先级最低值"+Thread.MIN_PRIORITY);
		System.out.println("线程优先级默认值"+Thread.NORM_PRIORITY);
		

	}

}

        输出结果:

        

示例2:使用继承Thread类的方式创建线程,在线程中输出1~20的整数

 解题思路:

(1)定义MyThread类继承Thread类,重写run()方法,在run()方法中实现数据输出

(2)创建线程对象

(3)调用start()方法启动线程

        MyThread类

package cn.bdqn.demo02;

//一个类要定义成线程类,可以通过继承Thread类来实现,然后重写Thread类里的run()方法
public class MyThread extends Thread {

	public MyThread(){
		
	}
	public MyThread(String name){
		super(name);
	}
	
	@Override
	public void run() {
		//在重写的run()方法中编写你要执行的代码:使用循环输出1-20
		for (int i = 1; i <= 20; i++) {
			System.out.println(Thread.currentThread().getName()+"--->"+i);
		}
	}
}

        Test测试类

package cn.bdqn.demo02;

public class Text {

	public static void main(String[] args) {
		//创建线程类对象
		MyThread mt1 = new MyThread("饲养员");
		MyThread mt2 = new MyThread("小猪");
		// mt1.run():只有主线程一条执行路径
		// mt1.start():多条执行路径,主线程和子线程并行交替执行
		
		//start()方法是启动线程的方法
		mt1.start();
		mt2.start();
		//注:当同时启动两个线程以后,会出现两个线程交替占用CPU执行代码的结果
	}

}

        输出结果:

 2、使用Runnable接口创建线程

(1)定义MyThread类实现Runnable接口

(2)实现run()方法,编写线程执行体

(3)创建线程对象,调用start()方法启动线程

        注:start()方法是thread类中的方法,而我们需要通过start()方法来调用run()方法,不能直接调用run()方法,但是Runnable接口中只有一个抽象方法run()方法,那么实现Runnable接口的类不能调用start()方法。

        解决方法:将实现Runnable接口的类对象作为参数传递给Thread构造方法,然后通过Thread类对象调用start()方法。

MyThread mt = new MyThread();

Thread thread3 = new Thread(mt, "恒哥")  

示例3:使用Runnable接口的方式擦行间线程,在线程中输出1~20的整数 

解题思路:

(1) 定义一个MyThread类实现java.lang.Runnable接口,并实现Runnable接口的run()方法,在run()方法中输出数据

(2)创建线程对象。

(3)调用start()方法启动线程。

         MyThread类

package cn.bdqn.demo03;

public class MyThread implements Runnable {

	@Override
	public void run() {
		//在重写的run()方法中编写你要执行的代码,使用循环输出1-20
		for (int i = 1; i <=20; i++) {
			//使用currentThread()方法获取当前的线程对象,并使用getName()方法获取当前线程的名称
			System.out.println(Thread.currentThread().getName()+"-->"+i);
		}

	}

}

        Test测试类

package cn.bdqn.demo03;

public class Test {

	public static void main(String[] args) {
		//创建线程类对象
		MyThread mt1 = new MyThread();
		//创建Thread类对象
		Thread thread1 = new Thread(mt1,"千里眼");
		Thread thread2 = new Thread(mt1,"顺风耳");
		/*
		 * start()方法是Thread类中的方法,而我们需要通过start()方法来调用run()方法,不能直接调用run()方法,
		 * 但是Runnable接口中只有一个抽象方法run()方法,那么实现Runnable接口的类不能调用start()方法
		 * 
		 * 解决方法:
		 * 	将实现Runnable接口的类对象作为参数传递给Thread构造方法,然后通过Thread类对象调用start()方法
		 * 
		 * 
		 * 
		 * */
		
		//start():启动线程
		thread1.start();
		thread2.start();

	}

}

3、以上两种创建线程的方式比较

        继承Thread类:

        (1)编写简单,可直接操作线程

        (2)适用于单继承

        实现Runnable接口

        (1)避免单继承局限性

        (2)便于共享资源

03、线程的状态

(1)新生状态(NEW Thread)

(2)就绪状态

(3)运行状态(Runnable)

(4)死亡状态(Daed)

(5)阻塞状态(Blocked)

04、线程调度

表 线程调度方法
方法说明
void setPriority(int newPriority)更改线程的优先级
static void sleep(long millis)(线程休眠)在指定的毫秒数内让当前正在执行的线程休眠
void join(强制执行)等待该线程终止
static void yield暂停当前正在执行的线程对象,并执行其他线程
void interrupt()中断线程
boolean isAlive()测试线程是否处于活动状态

1、线程优先级

        (1)线程优先级有1~10表示,1最低,10最高,默认优先级为5

        (2)优先级高的线程获得CPU资源的概率较大

System.out.println("线程优先级最高值:"+Thread.MAX_PRIORITY);//10
System.out.println("线程优先级最低值:"+Thread.MIN_PRIORITY);//1
System.out.println("线程优先级默认值:"+Thread.NORM_PRIORITY);//5

        问题:什么是线程优先级?它在线程调度中的作用是什么?

            答:

                ①线程优先级是反映线程获得CPU资源概率的问题。

                ②线程优先级在线程调度中的作用:提高获取CPU资源在线程调度中的概率

2、线程休眠( sleeep()方法 )

        (1)让线程暂时睡眠指定时长,线程进入阻塞状态

        (2)睡眠时间过后线程会再进入可运行状态

sleep()方法的语法格式:public static void sleep(millis)

示例4:sleep()方法

        下述代码可以看出:在执行主线程以后首先输出了“main线程开始运行...”,然后主线程等待5,秒后继续执行。

 关键代码:Wait类

package cn.bdqn.demo01;

public class Wait {
	/*
	 * 线程休眠
	 * (1)让线程暂时睡眠指定时长,线程进入阻塞状态
	 * (2)睡眠时间过后线程会再进入可运行状态
	 * 
	 * */
	public static void bySec(long s){ //s表示你想要休眠的秒数
		for(int i=0;i<s;i++){
			System.out.println(i+1+"秒");
			try {
				Thread.sleep(1000);//线程休眠1秒
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}

测试类Test类

package cn.bdqn.demo01;

public class Test {

	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName()+"线程开始运行...");
		Wait.bySec(5);
		System.out.println(Thread.currentThread().getName()+"程序结束运行...");
	}
}

        输出结果

3、线程的强制运行( join()方法 )

        (1)使当前线程暂停执行,等待其他线程结束后再继续执行本线程

join()方法有3种重载形式:

(1)public final void join()

(2)public final void join(long millis)

(3)public final void join(long millis,int nanos)

        ①millis:以毫秒为单位的等待时长

        ②nanos:要等待的附加纳秒时长

        ③需处理InterruptedException异常

示例5: join方法

        解析:主线程main运行5次后,使用join()方法使主线程main进入阻塞状态,开始执行子线程MyThread,直到子线程MyThread执行结束后再执行主线程main

         关键代码:MyThread线程类实现Runnable接口

package cn.bdqn.demo02;

public class MyThread implements Runnable {

	@Override
	public void run() {
		for(int i=0;i<20;i++){
			System.out.println(Thread.currentThread().getName()+"运行第"+(i+1)+"次");
		}
	}

}

         测试类:Test类

package cn.bdqn.demo02;

public class Test {

	public static void main(String[] args) {
		/*
		 * join():使当前线程暂停执行,等待其他线程结束后再继续执行本线程
		 * 
		 * 
		 * */
		
		MyThread mt = new MyThread();
		Thread t = new Thread(mt, "尼古拉斯");
		t.start();
		
		for(int i=0;i<10;i++){
			/*主线程main运行5次后,使用join()方法使主线程main进入阻塞状态,开始执行子线程MyThread,
			 * 直到子线程MyThread执行结束后再执行主线程main
			 * */
			if(i==5){  
				try {
					t.join();//阻塞主线程,子线程强制执行
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+"运行第"+(i+1)+"次");
		}

	}

}

        输出结果:

 4、线程的礼让( yield()方法 )

        (1)暂停当前线程,允许其他具有相同优先级的线程获得运行机会

        (2)该线程处于就绪状态,不转为阻塞状态

                注:只是提供一种可能,但是不能保证一定会实现礼让。

yield()方法的语法格式:public static void yield()

示例6: yield()方法

          关键代码:MyThread线程类实现Runnable接口

package cn.bdqn.demo03;

public class MyThread implements Runnable {
	/*
	 * 线程的礼让: 
	 * (1)暂停当前线程,允许其他具有相同或更高优先级的线程获得运行机会,若无相同或更高优先级的线程,则该线程继续执行
	 * (2)该线程处于就绪状态,不转为阻塞状态
	 */

	@Override
	public void run() {
		for (int i = 1; i <= 5; i++) {
			System.out.println(Thread.currentThread().getName() + "正在运行" + i);
			if (i == 3) {
				System.out.print("线程礼让:");
				Thread.yield();// 线程礼让,只是提供一种让其他线程获得CPU资源的机会,自己也会再次去抢占CPU资源
			}
		}

	}

}

        测试类:Test类

package cn.bdqn.demo03;

public class Test {

	public static void main(String[] args) {
		MyThread mt = new MyThread();
		Thread t1 = new Thread(mt, "线程A");
		Thread t2 = new Thread(mt, "线程B");
		t1.start();
		t2.start();
	}

}

        输出结果:

5、sleep()方法与yield()方法的区别

表 sleep()方法与yield()方法的区别
sleep()方法--线程休眠yield()方法--线程礼让

(1)让线程暂时睡眠指定时长,线程进入阻塞状态

 (2)睡眠时间过后线程会再进入可运行状态

(1)暂停当前线程,允许其他具有相同优先级的线程获得运行机会

(2)该线程处于就绪状态,不转为阻塞状态

即使没有其他等待运行的线程,当前线程也会等待指定的时间如果没有其他等待执行的线程,当前线程会马上恢复执行

 练习03:模拟多人爬山2-1

 练习04:线程的优先级

练习05:模拟叫号看病

05、线程同步

1、线程同步的必要性

        前面介绍的线程都是独立的。而且异步执行,也就是说每个线程都包含了运行时所需要的数据或方法,而不需要外部资源或方法,也不必关心其他线程的状态或行为。但是经常有一些同时运行的线程需要共享数据,此时就需要考虑其他线程的状态和行为,否则就不能保证程序运行结果的正确性。:

2、多线程共享数据引发的问题

        以下案例发现的问题:

        (1)不是从第1张票开始

        (2)存在多人抢到一张票的情况

        (3)有些票号没有被抢到

        (4).............

示例7-1:网络购票(多线程共享数据出现问题案例)

        需求说明:

        多用户实现网络购票,用户提交购票信息后

        (1)第一步:网站修改网站车票数据

        (2)显示出票反馈给用户

        解析:

        开始num=10,count=0,有一个线程,首先张三开始买票,执行run()方法,执行到if语句判断num<=0不满足,执行num--,count++ (此时num=9,count=1),(1)非常理想的状态是程序从头到尾执行到System.out.println()这里输出购票信息(输出此时的信息为:张三抢到了第1张票,还剩下9张票),但是当张三在执行到休眠的时候,失去了CPU资源,这个时候李四来了,执行run()方法,执行到if语句判断num<=0不满足,执行num--,count++ (此时num=8,count=2),(2)非常理想的状态是程序从头到尾执行到System.out.println()这里输出购票信息(输出此时的信息为:李四抢到了第2张票,还剩下8张票),但是李四在执行到休眠的时候,有可能被张三抢去了票,也有可能被王五抢到了票,当王五开始买票,执行run()方法,执行到if语句判断num<=0不满足,执行num--,count++ (此时num=7,count=3),但是这个时候张三或者李四休眠结束了,则抢占CPU资源,比如张三休眠结束了,将会进行执行System.out.println(),此时的num=7,count=3,则会进行输出张三抢到了第3张票,还剩下7张票,依次类推,这样就会实现一张票卖给多人的情况,也会出现有些票号没有被抢到,也会出现不是从第一张票开始买的情况,这个情况就是多线程操作共享数据造成的,在操作过程中CPU资源可能被其他线程抢去了。

         (1)通过Piao线程类实现Runnable接口

package cn.bdqn.demo06;

public class Piao implements Runnable{
	//声明一个变量用来表示票库中票的数据
	public int num=10;
	//声明一个变量用来表示用户抢到了第几张票
	public int count=0;
	
	public void run(){
		//实现抢票行为:修改票库里的票的数目和显示用户抢到了第几张票
		while(true){
			if(num<=0){
				break;
			}
			num--;
			count++;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"抢到了第"+count+"张票,还剩下"+num+"张票");
		}
	}
	

}

         (2)测试类Test类

package cn.bdqn.demo06;

public class Test {

	public static void main(String[] args) {
		Piao p = new Piao();
		Thread t1 = new Thread(p, "张三");
		Thread t2 = new Thread(p, "李四");
		Thread t3 = new Thread(p, "王五");
		t1.start();
		t2.start();
		t3.start();

	}

}

        输出结果:

3、实现线程同步(解决多线程共享数据出现的问题)

        线程同步的两种方法:

        (1)方法一:同步方法

        (2)方法二:同步代码块

(1)同步方法:使用synchronized修饰的方法控制对类成员变量的访问

        同步方法的语法格式:

        (1)访问修饰符 synchronized 返回类型 方法名(参数列表){}

                        例如:public synchronized void sale()

        (2)synchronized 访问修饰符 返回类型 方法名(参数列表){}

                synchronized是同步关键字

                访问修饰符是指:public、privatr等

示例7-2:使用同步方法的网络购票

         (1)通过Piao线程类实现Runnable接口

package cn.bdqn.demo07;

public class Piao implements Runnable {
	//声明一个变量用来表示库票中票的数据
	public int num=10;
	//声明一个变量用来表示用户抢到了第几张票
	public int count=0;
	public boolean flag = false;//声明一个标志位
	
	@Override
	public void run() {
		while(!flag){//当条件为真时,执行循环操作
			sale(); //调用方法
		}
	}
	public synchronized void sale(){
		
		//当票库里的票小于0的时候,不在进行循环
		if(num<=0){
			flag = true;//此时flag=true,将其值返回,此时结束while循环
			return;
		}
		num--;
		count++;
		try {
			Thread.sleep(1000); //线程休眠1秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"抢到了第"+count+"张票,还剩下"+num+"张票");
	}

}

         (2)测试类Test类

package cn.bdqn.demo07;

public class Test {

	public static void main(String[] args) {
		Piao p =new Piao();
		Thread t1 = new Thread(p, "张三");
		Thread t2 = new Thread(p, "李四");
		Thread t3 = new Thread(p, "王五");
		t1.start();
		t2.start();
		t3.start();

	}

}

        输出结果:

 (2)同步代码块:使用synchronized关键字修饰的代码块

        同步代码快的语法格式:

        (1)synchronized(syncObject){

                        //需要同步的代码

                }

                ①syncObject为需要同步到对象,通常为this

                ②效果与同步方法相同

       

         多个并发线程访问同一资源的同步代码块时:

        (1)同一时刻只能有一个线程进入synchronized(this)同步代码块

        (2)当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定。

        (3)当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码。

示例7-3: 使用synchronized关键字修饰的代码块

          (1)通过Piao线程类实现Runnable接口

package cn.bdqn.demo08;

public class Piao implements Runnable {
	// 声明一个变量用来表示库存中票的数据
	public int num = 10;
	// 声明一个变量用来表示用户抢到了第几张票
	public int count = 0;
	public boolean flag = false;

	@Override
	public void run() {

		while (!flag) {
			synchronized (this) {

				// 当库票里的票<0的时候,不在进行循环
				if (num <= 0) {
					flag = true;
					break;
				}
				num--;
				count++;
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "抢到了第"
						+ count + "张票,还剩下" + num + "张票");
			}
		}
	}
}

        (2)测试类Test类

package cn.bdqn.demo08;

public class Test {

	public static void main(String[] args) {
		Piao p = new Piao();
		Thread t1 = new Thread(p, "张三");
		Thread t2 = new Thread(p, "李四");
		Thread t3 = new Thread(p, "王五");
		t1.start();
		t2.start();
		t3.start();

	}

}

        输出结果:

06、线程安全的类型

1、线程安全与非线程安全的比较

        (1)ArrayList为非线程安全类型

                ArrayList类的add()方法为非同步方法

                当多个线程向同一个ArrayList对象添加数据时,可能出现数据不一致问题

表 线程安全与非线程安全的比较
方法是否同步效率比较适合场景
线程安全多线程并发共享资源
非线程安全单线程

2、常见类型比较

        Hashtable && HashMap

        (1)Hashtable

                继承关系

                        实现了Map接口,Hashtable继承Dictionary类

                线程安全,效率较低

                键和值都不允许为null

        (2)HashMap

                继承关系

                        实现了Map接口,继承AbstractMap类

                非线程安全,效率较高

                键和值都允许为null

        StringBuffer && StringBuilder

                前者线程安全,后者非线程安全

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小猪VS恒哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值