多线程基础
进程和线程
进程
简单来说,每一个应用程序都是一个进程。在创建时需要分配独立的资源以及cpu的执行时间,如果只有一个cpu,不可能做到真正的同时执行。
抢占式多任务:抢到cpu的执行时间,就可以执行。
协作式多任务:如果某个程序正在执行,其他的程序想执行,需要真正执行的程序同意才行。
windows3.x,macOS9以及以前的版本都是协作式,如果程序有问题,可能导致只有这一个程序在执行。后面的系统几乎都使用的抢占式。
线程
一个进程可以有多个线程,至少有一个线程,线程是轻量级的,线程之间共享资源,可以相互协作,能提升性能,但是会带来安全问题。
区别:
关键点就一个,资源独立和资源共享。
多线程的基本实现
使用Thread类
步骤:
1. 继承Thread类
2. 重写run方法
3. 创建子类的对象,调用start方法
基本实现:
public class MyThreadDemo {
public static void main(String[] args) {
// 创建子类对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// 调用start方法
t1.start();
t2.start();
}
}
// 在同一个文件中创建了多个类,只能跟文件名同名的类能使用public
/**
* 实现多线程的步骤
* @author
*
*/
class MyThread extends Thread{
@Override
public void run() {
// 得到当前线程的名称
String name = Thread.currentThread().getName();
// 多线程需要执行的任务
for (int i = 0; i < 100; i++) {
System.out.println(name + "====" + i);
}
}
}
实现售票业务:
/**
* 多个售票员卖票的场景
* @author
*
*/
public class MyThreadDemo1 {
public static void main(String[] args) {
// 创建线程
Seller zhangsan = new Seller();
zhangsan.setName("张三");
Seller lisi = new Seller();
lisi.setName("李四");
// 开始卖票
zhangsan.start();
lisi.start();
}
}
class Seller extends Thread{
static int tickets = 10;
@Override
public void run() {
// 获得线程(售票员)的名称
String name = Thread.currentThread().getName();
while(tickets > 0) {
tickets--;
System.out.println(name + "卖出一张票...., 剩余票数为:" + tickets);
}
}
}
使用Runnable接口
步骤:
1. 实现接口
2. 重写接口中定义的方法
3. 创建实现的对象
4. 使用上面的对象来创建Thread类的对象,并调用start方法
Thread类和Runnable的比较:
1. 使用Thread代码简单,但是占据了继承的位置,无法再继承其他类
2. 使用Runnable接口代码较复杂,但是还可以继承其他类,实现其他接口
3. 得到当前线程名称的方式不一样,继承Thread类也可以使用super.getName(),
但是实现接口只能使用Thread.currentThread().getName()方式。
start方法和run方法区别:
run方法只是重写了线程需要去执行的任务,不包含线程的执行过程。所以当你需要执行线程的时候不能直接调用run方法。而需要调用start方法,start方法表示线程已经进入就绪状态,就等着cpu给执行时间。
基本用法:
/**
* 使用Runnable接口实现多线程
* @author
*
*/
public class MyRunnableDemo {
public static void main(String[] args) {
// 创建runnable的对象
MyRunnable r = new MyRunnable();
// 创建线程对象
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
// 启动线程
t1.start();
t2.start();
// 主线程
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + "====" + i);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
// 得到线程名称
String name = Thread.currentThread().getName();
// 业务实现
for (int i = 0; i < 100; i++) {
System.out.println(name + "====" + i);
}
}
}
实现售票员卖票:
/**
* 使用runnable实现共享数据的操作(售票员问题)
* @author
*
*/
public class MyRunnableDemo1 {
public static void main(String[] args) {
MyRunnable1 r1 = new MyRunnable1();
// 创建三个售票员
Thread t1 = new Thread(r1);
t1.setName("张三");
Thread t2 = new Thread(r1);
t2.setName("李四");
Thread t3 = new Thread(r1);
t3.setName("王五");
// 开始卖票
t1.start();
t2.start();
t3.start();
}
}
class MyRunnable1 implements Runnable{
static int tickets = 10;
@Override
public void run() {
String name = Thread.currentThread().getName();
while(tickets >0) {
tickets--;
try {
// 休眠
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "卖出一张票,还剩"+tickets+"张票");
}
}
}
注意:在main中直接写的代码,执行在主线程中,该线程的名称为main。
多线程的基本方法
线程的名称
见前面代码中的setName,如果不设置名称,系统会自动分配名称,分配的规则是:Thread-0,Thread-1。
线程的休眠
指让当前线程休眠一段时间,以便让其他的线程能够抢占到cpu的执行时间,当休眠时间结束后,当前线程也会处于就绪状态开始抢占执行时间。时间单位:毫秒。
/**
* 测试线程的休眠
* @author
*
*/
public class MySleepDemo {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
/**
在main中执行的代码,是在主线程中执行的,当休眠1秒,应该会有其他的线程来抢占cpu的执行时间,
但此处只有一个主线程,所以呈现出倒计时效果。
*/
try {
// 休眠1000毫秒,即1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i+1);
}
}
}
线程的优先级
线程可以通过优先级的设置,来使得优先级高的线程能够更多的抢占cpu的执行时间。并不是绝对的。 优先级分为10级。有三个常量:MIN_PRIORITY值为1,MAX_PRIORITY值为10,NORM_PRIORITY值为5(默认值)。
public class MyThreadDemo {
public static void main(String[] args) {
// 创建子类对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setPriority(Thread.MAX_PRIORITY); // 设置为最高优先级
t2.setPriority(Thread.MIN_PRIORITY); // 设置为最低优先级
// 调用start方法
t1.start();
t2.start();
}
}
注意:优先级高会比优先级低的优先抢占执行时间。但是也不代表优先级低就无法抢占时间,只是比例低而已。
线程的合并
join,将一个线程加入到另一个线程中。
/**
* 线程的合并
* @author
*
*/
public class MyJoinDemo {
public static void main(String[] args) {
// 创建一个线程
Thread t1 = new MyJoinThread();
// 使用主线程去打印数据
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
// 当执行到20的时候
if(i == 20) {
try {
// t1线程进入就绪状态
t1.start();
// 使用合并的方式(,加入,插队),t1执行完后才会接着执行main
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name + "=====" + i);
}
}
}
class MyJoinThread extends Thread{
@Override
public void run() {
String name = super.getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + "=====" + i);
}
}
}
线程的生命周期
线程从创建到死亡的过程,中间经历的状态,以及状态转换。
使用new来创建线程,调用start方法,线程进入就绪状态,随即开始抢占cpu的执行时间,抢到时间即进入运行状态,当运行完毕则死亡。 当线程遇到sleep或者join时进入阻塞状态,当sleep的时间结束或者join进来的线程执行完毕,当前线程又回到就绪状态,继续抢占cpu时间。
多线程的使用
线程的通信
线程与线程间建立联系,一个线程能够去与另一个线程进行等待唤醒操作。 使用方法wait(), notify()和notifyAll();
/**
* wait也能让线程暂停下来(阻塞)
* 它没有参数,它是Object类自带的对象方法
* 在暂停线程的同时会释放锁,其他的线程也能够持有锁
* 线程需要等到唤醒才能继续执行下
* 线程可以使用任意对象进行wait,也需要通过该对象进行唤醒notify,
* 如果想一次唤醒该对象wait的所有线程,可以使用notifyAll,建议使用notifyAll,避免有忘记唤醒的线程
* 注意:
* 1. 必须要在线程进入wait后,去notify才有效果,先notify是没有意义的
* 2. 一般情况下,无论是wait还是notify都应该在锁里面执行,会抛出java.lang.IllegalMonitorStateException异常
* 3. 可以使用任意对象(要注意唯一性)作为锁或者wait,notify的执行者,但是锁的对象和wait或notify的对象要保持一致,否则会抛出java.lang.IllegalMonitorStateException异常
* @author
*
*/
public class MyWaitDemo {
static Object obj = new Object();
public static void main(String[] args) {
Runnable r1 = new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName(); // 得到当前线程的名称
for (int i = 0; i < 1000; i++) {
System.out.println(name + "====" + i);
if(i == 500) {
synchronized (this) {
try {
System.out.println(name + "到达了500------------------------------卡住了");
// 当达到500时,休眠10秒
obj.wait();
System.out.println(name + "20秒休息完毕----------------------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
System.out.println(name + "线程执行完毕==============");
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r1);
t1.start();
t2.start();
System.out.println("5秒后我来救你们=========");
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.println(i + "=========");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (obj) {
obj.notifyAll();
// r1.notify();
}
}
}
生产消费模式
设计模式是解决软件某一类业务的方案,该方案经过许多项目的验证,得出经验的总结叫设计模式。 23种设计模式是90年代4人组提出并编写的内容,应对是当时软件行业的业务问题的分类。到现在,设计模式远远不止23种,例如,mvc模式就不是23种之一。
所谓的生产消费模式就是利用多线程的wait和notify的特征实现的,会创建生产者和消费者两类对象,生产者只负责生产,消费者只负责消费,以达到解耦的目的。
/**
* 生产消费模式案例
* @author
*
*/
public class MyProduceConsumeDemo {
public static void main(String[] args) {
Storage s = new Storage();
for (int i = 0; i < 3; i++) {
Producer p = new Producer(s);
p.setName("厂房" + i);
p.start();
}
for (int i = 0; i < 10; i++) {
Consumer c = new Consumer(s);
c.setName("4S店,编码:" + i);
c.start();
}
}
}
/**
* 生产厂房在生产汽车
*/
class Producer extends Thread{
private Storage s; // 持有仓库对象
public Producer(Storage s) { // 构造方法
this.s = s;
}
@Override
public void run() {
String name = super.getName(); // 当前厂房的名称
while(true) {
try {
Thread.sleep(300); // 每次生产需要3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "生产了一辆汽车");
synchronized (s) { // 操作共享数据,进行锁定
s.count++; // 数量+1
System.out.println("仓库里共有" + s.count + "辆汽车");
if(s.count > 9) { // 当仓库的数量大于一定的量,通知所有的4s店来拉车
System.out.println("快爆仓了,赶紧来拉货====================");
s.notifyAll();
}
}
}
}
}
/**
* 4s在拉去汽车
*/
class Consumer extends Thread{
private Storage s;
public Consumer(Storage s) {
this.s = s;
}
@Override
public void run() {
String name = super.getName(); // 当前4s店的名称
while(true) {
try {
Thread.sleep(500); // 每次拉走汽车需要5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "拉走了一辆汽车");
synchronized (s) { // 操作共享数据,进行锁定
if(s.count > 0) { // 如果仓库还有车,就拉走
s.count--; // 数量-1
System.out.println("仓库里共有" + s.count + "辆汽车");
}else {
System.out.println(name + "白跑一趟...........");
try {
// 等待汽车的生产
System.out.println(name + "开始等待--------");
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
// 仓库
class Storage{
public Integer count = 0; // 汽车数量
}