Java学习第二十九天——多线程_线程控制
1.线程控制
1.1 线程控制之休眠线程(掌握)
A:线程休眠:
public static void sleep(long millis) 线程休眠
B:案例演示: 线程休眠
public static void main(String[] args) throws InterruptedException {
//
System.out.println("主线程开始执行了");
Thread.sleep(2000);
System.out.println("主线程下面的代码");
//线程休眠:可以让当前正在执行的线程, 睡一会。
//Thread(String name) 分配新的 Thread 对象。
//通过有参构造,可以给线程起个名字
MyThread th1 = new MyThread("A线程");
MyThread th2 = new MyThread("B线程");
th1.start();
th2.start();
}
public class MyThread extends Thread{
public MyThread(){}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000); //单位是毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
String name1 = Thread.currentThread().getName();
System.out.println(name1 + "===" + i);
}
}
}
1.2 线程控制之加入线程(掌握)
A:加入线程:
public final void join()
意思就是: 等待该线程执行完毕了以后,其他线程才能再次执行
注意事项: 在线程启动之后,在调用方法
B:案例演示: 加入线程
public static void main(String[] args) throws InterruptedException {
/* A:
加入线程:
public final void join ()
意思就是:等待该线程执行完毕了以后, 其他线程才能再次执行
注意事项:
在线程启动之后, 在调用方法
join ()可以让多个线程并发执行,变成串行(挨个排队执行,不用抢)
*/
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
MyThread th3 = new MyThread();
th1.setName("刘备");
th2.setName("关羽");
th3.setName("张飞");
th1.start();
//注意:在线程启动之后, 在调用join()方法
th1.join();
th2.start();
th2.join();
th3.start();
th3.join();
}
public class MyThread extends Thread{
public MyThread(){}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i <20; i++) {
String name1 = Thread.currentThread().getName();
System.out.println(name1 + "===" + i);
}
}
}
1.3 线程控制之礼让线程(了解)
A:礼让线程:
public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。
B:案例演示: 礼让线程
按照我们的想法,这个礼让应该是一个线程执行一次,但是通过我们的测试,效果好像不太明显.
那是为什么呢?
这个礼让是要暂停当前正在执行的线程,这个暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有
抢占到CPU的执行权,那么这个时候这个线程应该再次和其他线程抢占CPU的执行权.
public static void main(String[] args) throws InterruptedException {
/* A:
加入线程:
public final void join ()
意思就是:等待该线程执行完毕了以后, 其他线程才能再次执行
注意事项:
在线程启动之后, 在调用方法
join ()可以让多个线程并发执行,变成串行(挨个排队执行,不用抢)
*/
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
th1.setName("刘备");
th2.setName("关羽");
th1.start();
th2.start();
}
public class MyThread extends Thread{
public MyThread(){}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i <2000; i++) {
String name1 = Thread.currentThread().getName();
//线程礼让
Thread.yield();
System.out.println(name1 + "===" + i);
}
}
}
1.4 线程控制之守护线程(了解)
A:守护线程:
public final void setDaemon(boolean on):
将该线程标记为守护线程,当用户线程结束后,那么守护线程,就要马上结束。
B:案例演示: 守护线程
public static void main(String[] args) throws InterruptedException {
Thread.currentThread().setName("刘备:主线程");
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"=="+i);
}
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
th1.setName("张飞");
th2.setName("关羽");
//设置为守护线程 当主线程死亡后,守护线程要立马死亡掉。
//注意:setDaemon(true)该方法必须在启动线程前调用。
th1.setDaemon(true);
th2.setDaemon(true);
th1.start();
th2.start();
System.out.println(Thread.currentThread().getName()+"退出了");
}
public class MyThread extends Thread{
public MyThread(){}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i <2000; i++) {
String name1 = Thread.currentThread().getName();
System.out.println(name1 + "===" + i);
}
}
}
Java用户线程和守护线程
1.用户线程和守护线程的区别
用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。
2.用户线程和守护线程的适用场景
由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。
3.创建守护线程
调用线程对象的方法setDaemon(true),设置线程为守护线程。
1)thread.setDaemon(true)必须在thread.start()之前设置。
2)在Daemon线程中产生的新线程也是Daemon的。
3)不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。
因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。
4.Java守护线程和Linux守护进程
两者不是一个概念。Linux守护进程是后台服务进程,没有控制台。
在Windows中,你可以运行javaw来达到释放控制台的目的,在Unix下你加&在命令的最后就行了。所以守护进程并非一定需要的。
1.5 线程控制之中断线程(了解)
A:中断线程
public final void stop(): 停止线程的运行
public void interrupt(): 中断线程(这个翻译不太好),查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞
public static void main(String[] args) throws InterruptedException {
MyThread th1 = new MyThread();
th1.setName("张飞");
th1.start();
Thread.sleep(2000);
//让线程死亡
// th1.stop();
//清除线程的阻塞状态
th1.interrupt();
}
public class MyThread extends Thread{
public MyThread(){}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
try {
//线程休眠,就是线程处于了一种阻塞状态
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i <2000; i++) {
String name1 = Thread.currentThread().getName();
System.out.println(name1 + "===" + i);
}
}
}
2.线程程序实现方式
2.1多线程程序实现的方式2)(掌握)
A:实现Runnable接口 这种方式扩展性强 实现一个接口 还可以再去继承其他类
a:如何获取线程名称
b:如何给线程设置名称
c:实现接口方式的好处
可以避免由于Java单继承带来的局限性。
B:案例演示: 多线程程序实现的方式2
public static void main(String[] args) {
//创建线程的第二种方式
//1. 定义一个类实现 Runnable 接口。
//2.重写该接口 run 方法。
// 3.然后可以分配该类的实例,
//4在创建 Thread 对象时作为一个参数来传递并启动。
/* Thread(Runnable target)
分配新的 Thread 对象。*/
// Runnable 任务 接口应该由那些打算通过某一线程执行其实例的类来实现。
MyRunnable myRunnable = new MyRunnable();
Thread th1 = new Thread(myRunnable);
th1.setName("A线程");
th1.start();
Thread th2 = new Thread(new MyRunable2());
th2.setName("B线程");
th2.start();
}
public class MyRunnable implements Runnable{
//让线程来执行的方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//System.out.println(this.getName()+"==="+i);
Thread th = Thread.currentThread();
System.out.println(th.getName() + "===" + i);
}
}
}
public class MyRunable2 implements Runnable{
@Override
public void run() {
System.out.println("另外一个任务");
}
}
2.2 多线程程序实现的方式3(掌握)
A:实现 Callable 接口。
相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
B:执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
C:实现步骤
1.创建一个类实现Callable 接口
2.创建一个FutureTask类将Callable接口的子类对象作为参数传进去
3.创建Thread类,将FutureTask对象作为参数传进去
4.开启线程
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程的第三种方式
//我们有一种需求:当子线程执行完,我想获取子线程执行完之后的结果。
MyCallable myCallable = new MyCallable(100);
FutureTask<Integer> task = new FutureTask<>(myCallable);
Thread th = new Thread(task);
th.start();
//获取线程执行完之后返回的结果
Integer integer = task.get();
System.out.println(integer);
MyCallable myCallable2 = new MyCallable(1000);
FutureTask<Integer> task2 = new FutureTask<>(myCallable2);
Thread th2 = new Thread(task2);
th2.start();
Integer integer1 = task2.get();
System.out.println(integer1);
//Runnable 任务 让线程来执行 run() 没有返回值,这个方法不能抛出异常,只能抓
//Callable 任务 让线程来执行 call() 有返回值,而且可以抛出异常。
}
public class MyCallable implements Callable<Integer> {
private int num;
public MyCallable(int num) {
this.num = num;
}
//call()方法 让线程来执行的方法
@Override
public Integer call() throws Exception {
//System.out.println("线程执行了此代码");
//求1-num之间的和
int sum=0;
for (int i = 1; i <= num; i++) {
sum+=i;
}
return sum;
}
}
3.多线程之买电影票
3.1 继承Thread类的方式卖电影票案例(理解)
A:案例演示
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过继承Thread类实现
分析:
a: 三个窗口其实就是3个线程
b: 定义票的数量100张
c: 创建线程对象,启动线程. 每卖一张这个票数应该–
public static void main(String[] args) {
//三个串口,设计为三个线程 并发执行 切换速度很快感觉三个线程在同时买票
CellThread th1= new CellThread("窗口1");
CellThread th2 =new CellThread("窗口2");
CellThread th3 =new CellThread("窗口3");
th1.start();
th2.start();
th3.start();
}
public class CellThread extends Thread {
//把票这个数据 设置为共享变量,让三个线程来共享
static int piao = 100;
public CellThread(String name) {
super(name);
}
@Override
public void run() {
while (true){
if(piao>=1){
System.out.println(this.getName()+"正在出售第:"+(piao--)+" 张票");
}
}
}
}
3.2 实现Runnable接口的方式卖电影票(理解)
A:案例演示
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过实现Runnable接口实现
public static void main(String[] args) {
//创建了一次任务 100
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3= new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
public class CellRunnable implements Runnable{
//这个票让三个线程共享
static int piao=100;
@Override
public void run() {
while (true) {
if (piao >= 1) {
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
}
}
}
}
3.3 买电影票出现了同票和负数票的原因分析(理解)
A:加入延迟
我们前面讲解过电影院售票程序,从表面上看不出什么问题,但是在真实生活中,
售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟
改实现接口方式的卖票程序,每次卖票延迟100毫秒
B:出现问题了问题
public static void main(String[] args) {
//创建了一次任务 100
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3= new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
//我们写的这段售票代码,出现了线程安全问题。多线程的环境下,在对共享数据进行操作时,有可能会出现线程安全问题。
//为什么会出现线程安全问题,以及我们如何来解决他,且听下回分解。
}
public class CellRunnable implements Runnable{
//这个票让三个线程共享
static int piao=100;
@Override
public void run() {
while (true) {
if (piao >= 1) {
//模拟一下真实的售票环境,有网络延迟。
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
}
}
}
}