Java基础(24)之多线程

1. 实现多线程

1.1 进程

在这里插入图片描述

1.2 线程

在这里插入图片描述

1.3 多线程的实现方式(方式一:继承Thread类)

在这里插入图片描述
在这里插入图片描述
代码示例:
定义类MyThread:

//1:定义一个类MyThread继承Thread类
public class MyThread extends Thread {
	// 2:在MyThread类中重写run()方法
	// 为什么要重写run方法,是因为在类中,并不是所有代码都需要执行多线程的,
	// 所有就有了run方法,需要执行多线程的代码写在run方法中。
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(i);
		}
	}
}

定义测试类:

public class MyThreadDemo {
	public static void main(String[] args) {
		// 3:创建MyThread类的对象
		// 多线程就创建多个
		MyThread my1 = new MyThread();
		MyThread my2 = new MyThread();

		// 4:启动线程
//		my1.run();	//这样调用run方法是错误,并没有启动线程。
//		my2.run();

		// 正确方式
		// void start ()导致此线程开始执行; Java虛拟机调用此线程的run方法
		my1.start();
		my2.start();
	}
}

在前面了解到多线程,这个程序也是多线程,那么在运行程序的时候,应该是两个一起执行,而不是执行完一个再执行另一个。
运行结果:
在这里插入图片描述

1.4 设置和获取线程名称

在这里插入图片描述
代码示例:

String getName():返回此线程的名称
定义线程类:

public class MyThread extends Thread {

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			//String getName():返回此线程的名称
			System.out.println(getName() + ";" + i);
		}
	}
}

测试类:

public class MyThreadDemo {
	public static void main(String[] args) {
		
		MyThread my1 = new MyThread();
		MyThread my2 = new MyThread();
		
		my1.start();
		my2.start();
	}
}

运行结果:
在线程类中,并没有设置线程名,而控制器显示输出了Thread-0和Thread-1。
说明在Thread类中有默认的线程名,而且第一个线程类是0,后面就会加一。
在这里插入图片描述
void setName(String name):将此线程的名称更改为等于参数name
线程类不变

public class MyThreadDemo {
	public static void main(String[] args) {

		MyThread my1 = new MyThread();
		MyThread my2 = new MyThread();
		
		//void setName(String name):将此线程的名称更改为等于参数name
		my1.setName("飞机");
		my2.setName("坦克");

		my1.start();
		my2.start();
	}
}

运行结果:
这次的输出结果就是我们设置的值了。
在这里插入图片描述
除了使用set方法设置线程名称,使用构造方法也是可以的
使用构造方法设置线程名称,必须在线程类中创建有参构造方法,然后将参数传到父类的(Thread)的有参构造方法。

线程类:

public class MyThread extends Thread {

	public MyThread() {
		super();
	}
	
	public MyThread(String name) {
		//调用Thread类的构造方法,将参数传递过去。
		super(name);
	}

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

测试类:

public class MyThreadDemo {
	public static void main(String[] args) {

		MyThread my1 = new MyThread("飞机");
		MyThread my2 = new MyThread("坦克");

		my1.start();
		my2.start();
	}
}

运行结果:
在这里插入图片描述
那么如果获取main方法所在线程的名称那?
使用public static Thread currentThread0:返回对当前正在执行的线程对象的引用

public class MyThreadDemo {
	public static void main(String[] args) {
		//public static Thread currentThread0:返回对当前正在执行的线程对象的引用
		System.out.println(Thread.currentThread().getName());
		
	}
}

运行结果:
由此可见,main方法的线程名是main。
在这里插入图片描述

1.5 线程调度

在这里插入图片描述
MIN_PRIORITY:最小线程优先级,1
MAX_PRIORITY:最大线程优先级:10
NORM_PRIORITY:默认线程优先级:5

1.6 线程控制

在这里插入图片描述

代码示例:

static void sleep(long millis):使当前正在执行的线程停留 (暂停执行)指定的毫秒数

设置了sleep,在线程的执行过程中就会等待设置的时间过后,再执行。

创建线程类:

public class Thread1 extends Thread {
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(getName() + ";" + i);
			try {
                  //static void sleep(long millis):使当前正在执行的线程停留 (暂停执行)指定的毫秒数
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

创建测试类:

public class File1Demo {
	public static void main(String[] args) {

		Thread1 t1 = new Thread1();
		Thread1 t2 = new Thread1();
		Thread1 t3 = new Thread1();
		
		t1.setName("曹操");
		t2.setName("刘备");
		t3.setName("孙权");
		
		t1.start();
		t2.start();
		t3.start();

	}
}

运行结果:

这里就不放图了,线程输出应该是每停留1000毫秒(一秒)再输出一次。

void join():等待这个线程死亡

设置了join()方法,其他的线程都会等待这个线程死亡,才会执行。

创建线程类:

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

创建测试类:

public class File1Demo {
	public static void main(String[] args) {

		Thread1 t1 = new Thread1();
		Thread1 t2 = new Thread1();
		Thread1 t3 = new Thread1();

		t1.setName("康熙");
		t2.setName("四阿哥");
		t3.setName("八阿哥");

		t1.start();
		try {
			// void join():等待这个线程死亡
			t1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.start();
		t3.start();

	}
}

运行结果:

这次加入了join()方法后,明显是t1(康熙)执行的最快,因为加入了这个方法后,其他的线程都要等他执行完(死亡)后才能执行。

在这里插入图片描述

void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

设置了守护线程后,主线程死亡一段时间,守护线程也要死亡。

创建线程类:

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

创建测试类:

public class File1Demo {
	public static void main(String[] args) {

		Thread1 t1 = new Thread1();
		Thread1 t2 = new Thread1();

		t1.setName("张飞");
		t2.setName("关羽");

		// 设置主线程(main)为刘备
		Thread.currentThread().setName("刘备");

		// 设置守护线程
		// void setDaemon (boolean on): 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虛 拟机将退出
		t1.setDaemon(true);
		t2.setDaemon(true);

		t1.start();
		t2.start();

		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + ";" + i);
		}

	}
}

运行结果:

在运行结果中,主线程刘备在运行第10次后死亡了,而守护他的线程也在执行一段后死亡了,并没有执行完毕。

主线程死亡后其他守护线程并不会立即死亡,而是一段时间后。

在这里插入图片描述

1.7 线程生命周期

正常的线程生命周期:
在这里插入图片描述
还有一种情况就是在运行的时候其他线程抢走了CPU的执行权的话,它就会回到就绪状态,然后再抢CPU的执行权,如果抢到了就继续执行。
在这里插入图片描述
还有一种情况就是在运行的时候执行了sleep()方法,执行后线程就会进入到阻塞状态,等到了设定的时间之后,它就会回到就绪状态,继续和其他的线程抢CPU的执行权。
在这里插入图片描述

1.8 多线程的实现方式(方式二:实现Runnable接口)

在这里插入图片描述
在这里插入图片描述
代码示例:

创建MyRunnable类

//1:定义一个类MyRunnable实现Runnable接口
public class MyRunnable implements Runnable {
	
	// 2:在MyRunnable类中重写run()方法
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			// 这里并不能使用getName方法,因为实现的是Runnable接口,和Thread并没有关系
			// 使用想要使用必须先得到当前正在执行的线程,然后再调用getName方法
			System.out.println(Thread.currentThread().getName() + ";" + i);
		}
	}

}

创建测试类:

public class MyRunnableDemo {

	public static void main(String[] args) {

		// 3:创建MyRunnable类的对象
		MyRunnable myRunnable = new MyRunnable();

		// 4:创建Thread类的对象,把MyRunnable对象作为构造方法的参数
		//Thread (Runnable target, String name),添加Runnable和它的线程名
		Thread t1 = new Thread(myRunnable, "飞机");
		Thread t2 = new Thread(myRunnable, "坦克");

		// 5:启动线程
		t1.start();
		t2.start();

	}

}

运行结果:
和继承Thread类的效果是一样的,但是比继承Thread更加的灵活。
在这里插入图片描述

2. 线程同步

案例:卖票

在这里插入图片描述
代码示例:

创建SellTicket类:

//1:定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;
public class SellTicket implements Runnable {
	
	//表示有一百张票
	private int tickets = 100;

	// 2:在SellTicket类中重写run()方法实现卖票,代码步骤如下
	@Override
	public void run() {

		// c:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
		while (true) {
			// A:判断票数大于0,就卖票,并告知是哪个窗口卖的
			if (tickets > 0) {
				System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
				// B:卖了票之后,总票数要减1
				tickets--;
			}
		}
	}

}

创建SellTickDemo类:

//3:定义一一个测试类SellTicketDemo,里面有main方法,代码步骤如下
public class SellTickDemo {
	public static void main(String[] args) {
		// A:创建SellTicket类的对象
		SellTicket sellTicket = new SellTicket();

		// B:创建三个Thread类的对象,把sellTicket对象作为构造方法的参数,并给出对应的窗口名称
		Thread t1 = new Thread(sellTicket, "窗口1");
		Thread t2 = new Thread(sellTicket, "窗口2");
		Thread t3 = new Thread(sellTicket, "窗口3");

		// C:启动线程
		t1.start();
		t2.start();
		t3.start();
	}

}

运行结果:
在这里插入图片描述

2.1 卖票案例的思考

在这里插入图片描述
代码示例:

SellTick类:

public class SellTicket implements Runnable {

	private int tickets = 100;

	@Override
	public void run() {

		while (true) {

			if (tickets > 0) {
				try {
					// 通过sleep方法来模拟出票时间
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
				tickets--;
			}

		}

	}

}

SellTickDemo类:

public class SellTickDemo {
	public static void main(String[] args) {

		SellTicket sellTicket = new SellTicket();

		Thread t1 = new Thread(sellTicket, "窗口1");
		Thread t2 = new Thread(sellTicket, "窗口2");
		Thread t3 = new Thread(sellTicket, "窗口3");

		t1.start();
		t2.start();
		t3.start();

	}

}

运行结果:
在这里插入图片描述
在这里插入图片描述
出现了相同的票数,而且从第100张票直接就到了第97张票,还有出现了负数的情况。
这是为什么那?
解析:

// 出现负数的票
	while(true)
	{
		// tickets=1
		// t1, t2, t3
		// 假设t1线程抢到CPU的执行权
		if (tickets > 0) {
			try {
				// 通过sleep方法来模拟出票时间
				Thread.sleep(100);
				// t1线程休息100毫秒
				// t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里的时候,t2线程 休息100毫秒
				// t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			// 假设线程按照顺序醒过来
			// t1抢到了CPU的执行权,在控制台输出:窗口1正在出售第1张票
			// 假设t1继续拥有CPU的执行权,就会执行tickets--;操作, tickets = 0;
			// t2抢到了CPU的执行权,在控制台输出:窗口1正在出售第o张票
			// 假设t2继续拥有CPU的执行权,就会执行tickets--;操作,tickets = -1;
			// t3抢到了CPU的执行权,在控制台输出:窗口3正在出售第-1张票
			// 假设t2继续拥有CPU的执行权,就会执行tickets--;操作, tickets = -2;[、
			System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
			tickets--;
		}
	}
}

2.2 卖票案例数据安全问题的解决

在这里插入图片描述

2.3 同步代码块

在这里插入图片描述
加上锁之后,线程进入synchronized内部之后就上了锁,其他的线程就进不去了,即使是内部的线程休眠了,内部只能有一个线程。
代码示例:

线程类:

public class SellTicket implements Runnable {

	private int tickets = 100;
	
	private Object obj = new Object();

	@Override
	public void run() {
		while (true) {
			//上锁,锁必须是同一把
			synchronized (obj) {
				if (tickets > 0) {
					try {
						Thread.sleep(100);

					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
					tickets--;
				}
			}
		}

	}
}

测试类:

public class SellTickDemo {
	public static void main(String[] args) {

		SellTicket sellTicket = new SellTicket();

		Thread t1 = new Thread(sellTicket, "窗口1");
		Thread t2 = new Thread(sellTicket, "窗口2");
		Thread t3 = new Thread(sellTicket, "窗口3");

		t1.start();
		t2.start();
		t3.start();

	} 

}

运行结果:
这次就正常输出了,没有重复的票,也没有负数的票。
在这里插入图片描述

2.4 同步方法

在这里插入图片描述
在这里插入图片描述
代码示例:

同步方法:

public class SellTicket implements Runnable {

	private int tickets = 100;
	private int x = 0;

	@Override
	public void run() {
		while (true) {
			if (x % 2 == 0) {
				//锁对象是this
				synchronized (this) {
					if (tickets > 0) {
						try {
							Thread.sleep(100);

						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
						tickets--;
					}
				}
			} else {
				sellTicket();
			}
			x++;
		}
	}
	
	//同步方法
	private synchronized void sellTicket() {
		if (tickets > 0) {
			try {
				Thread.sleep(100);

			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
			tickets--;
		}
	}
}

同步静态方法:

public class SellTicket implements Runnable {

	private static int tickets = 100;
	private int x = 0;

	@Override
	public void run() {
		while (true) {
			if (x % 2 == 0) {
				//锁对象是,类名.class,这里用到了反射
				synchronized (SellTicket.class) {
					if (tickets > 0) {
						try {
							Thread.sleep(100);

						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
						tickets--;
					}
				}
			} else {
				sellTicket();
			}
			x++;
		}
	}

	// 同步静态方法
	private static synchronized void sellTicket() {
		//将tickets 也变成静态
		if (tickets > 0) {
			try {
				Thread.sleep(100);

			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
			tickets--;
		}
	}
}

2.5 线程安全的类

synchronized() 关键字的类,就是线程安全的类

线程安全类线程不安全类
StringBufferStringBuilder
VectorArrayList
HashtableHashMap

在这里插入图片描述
在Collections(工具类)中有一个方法可以返回指定类型的线程安全
在这里插入图片描述

2.6 Lock锁

在这里插入图片描述
代码示例:

创建线程类:

public class SellTicket implements Runnable {

	private int tickets = 100;
	// 创建一个Lock的实例,因为Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化、
	// ReentrantLock (): 创建一个ReentrantL ock的实例
	private Lock lock = new ReentrantLock();

	@Override
	public void run() {

		while (true) {
			// 加入try...finally的原因是,如果在运行中发生了错误,释放锁的操作照样会执行。
			try {
				// 获得锁
				lock.lock();
				if (tickets > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
					tickets--;
				}
			} finally {
				// 释放锁
				lock.unlock();
			}
		}

	}

}

创建测试类:

public class SellTickDemo {
	public static void main(String[] args) {

		SellTicket sellTicket = new SellTicket();

		Thread t1 = new Thread(sellTicket, "窗口1");
		Thread t2 = new Thread(sellTicket, "窗口2");
		Thread t3 = new Thread(sellTicket, "窗口3");

		t1.start();
		t2.start();
		t3.start();

	} 

}

运行结果:

没有问题
在这里插入图片描述

3. 生产者消费者

3.1 生产者消费者模式概述

生产者消费者模式详解
在这里插入图片描述
等待:生产者生产好了数据,消费者没有去消费,这个时候生产者就会提醒消费者去消费。
唤醒:消费者去消费的时候,发现没有数据,这个时候消费者就去提醒生产者生产数据。
在这里插入图片描述

3.2 生产者消费者案例

在这里插入图片描述
代码示例:

创建奶箱类:

//1:奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
public class Box {

	// 奶的数量
	private int milk;
	// 定义一个成员变量,表示奶箱的状态
	private boolean state = false;

	// 存储牛奶,加入synchronized 防止线程安全问题
	public synchronized void put(int milk) {
		// 如果有奶等待消费
		if (state) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		// 如果没有奶就生产奶
		this.milk = milk;
		System.out.println("送奶工将第" + milk + "瓶奶放入奶箱");

		// 生产完毕之后,修改奶箱状态
		state = true;

		// 唤醒其他等待的线程
		notifyAll();

	}

	// 获取牛奶,加入synchronized 防止线程安全问题
	public synchronized void get() {
		// 如果没有牛奶等待生产
		if (!state) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		// 如果有牛奶就消费牛奶
		System.out.println("用户拿到第" + milk + "瓶奶");

		// 消费完毕之后,修改奶箱状态
		state = false;

		// 唤醒其他等待的线程
		notifyAll();

	}

}

创建生产者类:

//2:生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
public class Producer implements Runnable {

	private Box b;

	public Producer(Box b) {
		this.b = b;
	}

	@Override
	public void run() {
		for (int i = 1; i <= 5; i++) {
			b.put(i);
		}
	}

}

创建消费者类:

//3:消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
public class Customer implements Runnable {

	private Box b;

	public Customer(Box b) {
		this.b = b;
	}

	@Override
	public void run() {
		while (true) {
			b.get();
		}
	}

}

创建测试类:

//4:测试类(BoxDemo):里面有main方法,main方法 中的代码步骤如下
public class BoxDemo {
	public static void main(String[] args) {
		// A:创建奶箱对象,这是共享数据区域
		Box b = new Box();

		// B:创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
		Producer p = new Producer(b);

		// C:创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
		Customer c = new Customer(b);

		// D:创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
		Thread t1 = new Thread(p);
		Thread t2 = new Thread(c);

		// E:启动线程
		t1.start();
		t2.start();

	}
}

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值