多线程

77 阅读7分钟

多线程

1. 进程

进行中的应用程序,计算机分配资源的最小单位

只有程序在运行状态下,才称之为进程

2. 线程

线程的包含在进程之中的,属于计算机运算执行的最小单位

一个进程至少包含一个线程,否则无法运行

3. 线程执行

多线程在单个核心的CPU下是随机轮流交替执行的

线程的执行存在不确定性

线程的执行决定权在CPU,不在线程

4. 并发和并行

并发:是指同时发生,轮流交替执行

并行:真正意义上的同时执行

5. 线程名称

通过setName设置名称

通过getName获取名称

main方法所属线程名默认为main

package com.qfedu.test1;
/**
 * 	获取当前线程名称 
 * @author WHD
 *
 */
public class Test1 {
	public static void main(String[] args) {
		Thread currentThread = Thread.currentThread();
		
		System.out.println(currentThread.getName()); // 获取线程名字
		
		currentThread.setName("hello world"); // 设置线程名字
		
		System.out.println(currentThread.getName());
	}
}

6. 创建线程的方式

6.1 继承Thread类

1.继承Thread类

2.重写run方法

启动线程调用start方法

package com.qfedu.test1;
/**
 * 	实现多线程的方式1:继承Thread类
 * @author WHD
 *
 */
public class Test2 extends Thread{
	
	/**
	 * 	run方法中代码为我们当前线程要执行的逻辑代码
	 * 	但是代码的执行决定权不在线程 在CPU
	 */
	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			System.out.println(Thread.currentThread().getName() + "---" + i);
		}
	}
	
	public static void main(String[] args) {
		Test2 th1 = new Test2();
		
		th1.setName("线程A");
		
		th1.start(); // 向CPU发出准备就绪信号  等待被执行
		
		
		Test2 th2 = new Test2();
		
		th2.setName("线程B");
		
		th2.start();	
}

6.2 实现Runnable接口

1.实现Runnable接口

2.重写run方法‘

Runnable实现类可作为参数构造Thread实例

package com.qfedu.test2;
/**
 * 	创建线程方式2:实现Runnable接口
 * @author WHD
 *
 */
public class Test1 implements Runnable{
	int num = 10;
	
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("线程名称:" + Thread.currentThread().getName() + "---" + i);
		}
	}
	
	public static void main(String[] args) {
		Test1 test1 = new Test1();
		
		Thread th1 = new Thread(test1, "线程A");
		
		Thread th2 = new Thread(test1, "线程B");
		
		th1.start();
		
		th2.start();
	}	
}

7. 两种创建方式区别?

继承Thread类

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

  	适用于单继承

实现Runnable接口

  避免单继承局限性

  便于共享资源

推荐使用实现Runnable接口方式创建线程

8. 面试题:调用start和run方法的区别?

因为线程执行的决定权不在线程,而在CPU,所以线程只能向CPU发出准备就绪信号,等待被执行

所以调用start方法,就是发出信号,等待CPU开启新的线程

调用run方法,不会开启新的线程

**调用start开启新线程,调用run不会开启新线程**

9. 线程的状态

线程的状态.png

package com.qfedu.test2;
/**
 * 	线程的状态
 * @author WHD
 *
 */
public class Test2 extends Thread{
	@Override
	public void run() { // 运行
		System.out.println("开始执行run方法");
		try {
			Thread.sleep(3000); // 阻塞
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("线程名字:" + Thread.currentThread().getName());
	} // 死亡
	
	public static void main(String[] args) {
		Test2 th1 = new Test2(); // 创建状态
		
		th1.start(); // 就绪
	}
}

10. 线程调度常用方法

10.1. 线程优先级

默认为5 最高为10 最低为1

package com.qfedu.test3;
/**
 * 	线程优先级
 * 	默认为5 最高为10 最低为1
 * @author WHD
 *
 */
public class Test1{
	public static void main(String[] args) {
		Thread mainThread = Thread.currentThread();
		
		System.out.println(mainThread.getPriority());
		
		mainThread.setPriority(Thread.MAX_PRIORITY);
		
		System.out.println(mainThread.getPriority());
	}
}
package com.qfedu.test3;

public class Test2 extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "---" + i);
		}
	}
		
	public static void main(String[] args) {
		Test2 th1 = new Test2();
		
		th1.setPriority(Thread.MAX_PRIORITY);
		
		th1.setName("----线程A----");
		
		Test2 th2 = new Test2();
		
		th2.setPriority(1);
		
		th2.setName("线程B");
		
		
		th1.start();
		th2.start();		
	}	
}

10.2 线程休眠

sleep(long millis) 线程休眠方法

10.3 线程插队

插队 join

一个线程插队另外一个线程

join() 等待插队线程执行完 被插队线程再执行

join(int millis) 等待插队线程指定时间 时间一到 继续轮流交替

package com.qfedu.test3;
/**
 * 	插队 join  
 * 	一个线程插队另外一个线程  
 * 
 * 	join() 等待插队线程执行完 被插队线程再执行
 * 	join(int millis) 等待插队线程指定时间 时间一到 继续轮流交替
 * @author WHD
 *
 */
public class Test3 extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "---" + i);
		}
	}
	
	public static void main(String[] args) {
		Test3 th1 = new Test3();
		th1.setName("线程A");
		th1.start();
		
		for (int i = 0; i < 50; i++) {
			if(i == 10) {
				try {
					th1.join(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
						System.out.println(Thread.currentThread().getName() + "*********" + i);
		}
	}
}

10.4 线程礼让

yield 礼让

礼让不能够保证一定会礼让

package com.qfedu.test3;
/**
 * 	yield 礼让 
 * 	礼让不能够保证一定会礼让  
 * @author WHD
 *
 */
public class Test4 extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			if(i == 10) {
				System.out.println("---------线程礼让---------");
				Thread.yield();
			}
			System.out.println(Thread.currentThread().getName());
		}
	}
	
	public static void main(String[] args) {
		Test4 th1 = new Test4();
		th1.setName("线程A*****");
		
		Test4 th2 = new Test4();
		th2.setName("线程B");
		
		
		th1.start();
		th2.start();	
	}	
}

11. 练习题

11.1 模拟爬山

爬山类

每个线程代表一个人

可设置每人爬山速度

每爬完100米显示信息

爬到终点时给出相应提示

package com.qfedu.test4;
/**
 * 	爬山类
 * 	每个线程代表一个人
 *	可设置每人爬山速度
 *	每爬完100米显示信息
 *	爬到终点时给出相应提示
 *
 *	
 * @author WHD
 *
 */
public class ClimbThread extends Thread{
	int time; // 爬100耗时
	int length; // 总长度 赋值1000
	
	@Override
	public void run() {
		while(length > 0) {
			try {
				Thread.sleep(time);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			length -= 100;
			System.out.println(Thread.currentThread().getName() + "爬了100米,还剩余" + length);
		}
		
		System.out.println("恭喜:" + Thread.currentThread().getName() + "爬到了山顶");
	}
	
	public ClimbThread(int time,int length,String name) {
		super(name);
		this.time = time;
		this.length = length;
	}
	
	public static void main(String[] args) {
		ClimbThread youngMan = new ClimbThread(500, 1000, "练习两年半的实习生");
		
		ClimbThread oldMan = new ClimbThread(1000, 1000, "老年人");
		
		youngMan.start();
		oldMan.start();		
	}
}

11.2 模拟看病

某科室一天需看普通号50个,特需号10个

特需号看病时间是普通号的2倍

开始时普通号和特需号并行叫号,叫到特需号的概率比普通号高

当普通号叫完第10号时,要求先看完全部特需号,再看普通号

使用多线程模拟这一过程

思路:创建当前类为特需号类 ,main方法中main线程作为普通号

package com.qfedu.test4;
/**
 * 	某科室一天需看普通号50个,特需号10个
 *	特需号看病时间是普通号的2倍
 *	开始时普通号和特需号并行叫号,叫到特需号的概率比普通号高
 *	当普通号叫完第10号时,要求先看完全部特需号,再看普通号
 *	使用多线程模拟这一过程
 *
 *	思路:创建当前类为特需号类 ,main方法中main线程作为普通号
 * @author WHD
 *
 */
public class Special extends Thread{
	@Override
	public void run() {
		for (int i = 1; i <= 10; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + i + "号在看病");
		}
		System.out.println("特需号看病完成*********************************");
	}
	
	public static void main(String[] args) throws InterruptedException {
		Special s = new Special();
		s.setName("特需号");
		s.setPriority(Thread.MAX_PRIORITY);
		s.start();
		
		Thread.currentThread().setName("普通号");
		for (int i = 1; i <= 50; i++) {
			if(i == 11) {
				s.join();
			}
			Thread.sleep(500);
			System.out.println(Thread.currentThread().getName() + i + "号在看病");
		}
		System.out.println("普通号看病完毕###############");	
	}
}

12.synchronized 关键字

synchronized:同步

应用场景:

1.修饰代码块 表示同一时间只能有一个线程访问此代码块

2.修饰方法 同一时间只能有一个线程访问此方法

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

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

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

package com.qfedu.test5;
/**
 * 	使用多线程模拟买票的操作
 * 	三个线程(三个人)抢10张票,必须保证:
 * 	1.票号不能重复
 * 	2.不能超卖
 * 	
 * 
 * 出现的问题:会出现重复的票号 和 出现超卖的问题 都是因为
 * 	循环的判断和 数据的修改 没有绑定在一起   所以解决方案:将条件判断和数据修改 绑定在一起(上锁)
 * 
 * 	synchronized:同步
 * 	应用场景:
 * 	1.修饰代码块 表示同一时间只能有一个线程访问此代码块
 * 	2.修饰方法 同一时间只能有一个线程访问此方法
 * @author WHD
 *
 */
public class BuyTicket3 implements Runnable{
	int count = 10;
	Object obj = new Object();

	@Override
	public void run() {
		while(true) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized(obj) { // 这里不是必须写this 但是必须保证多个线程锁定的是同一个对象 才有同步的效果
				if(count == 0) {
					break;
				}
				count --;
				System.out.println(Thread.currentThread().getName() + "抢到第"+ (10 - count) +"张票,还剩余" + count + "张票");
			}
		}
	}
	
	public static void main(String[] args) {
		BuyTicket3 buyTicket = new BuyTicket3();
		
		Thread th1 = new Thread(buyTicket, "赵四");
		
		Thread th2 = new Thread(buyTicket, "广坤");
		
		Thread th3 = new Thread(buyTicket, "大拿");
		
		th1.start();
		
		th2.start();
		
		th3.start();		
	}	
}
package com.qfedu.test5;
/**
 * 	使用多线程模拟买票的操作
 * 	三个线程(三个人)抢10张票,必须保证:
 * 	1.票号不能重复
 * 	2.不能超卖
 * 	
 * 
 * 出现的问题:会出现重复的票号 和 出现超卖的问题 都是因为
 * 	循环的判断和 数据的修改 没有绑定在一起   所以解决方案:将条件判断和数据修改 绑定在一起(上锁)
 * 
 * 	synchronized:同步
 * 	应用场景:
 * 	1.修饰代码块 表示同一时间只能有一个线程访问此代码块
 * 	2.修饰方法 同一时间只能有一个线程访问此方法
 * @author WHD
 *
 */
public class BuyTicket4 implements Runnable{
	int count = 10;
	Object obj = new Object();

	@Override
	public synchronized void run() {
		while(true) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if(count == 0) {
				break;
			}
			count --;
			System.out.println(Thread.currentThread().getName() + "抢到第"+ (10 - count) +"张票,还剩余" + count + "张票");
		}
	}

	public static void main(String[] args) {
		BuyTicket4 buyTicket = new BuyTicket4();

		Thread th1 = new Thread(buyTicket, "赵四");

		Thread th2 = new Thread(buyTicket, "广坤");

		Thread th3 = new Thread(buyTicket, "大拿");

		th1.start();

		th2.start();

		th3.start();
	}
}