黑马程序员——Java学习之多线程分析

时间:2023-02-20 09:53:51

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-

要学习多线程,需要了解什么是”线程”?要了解线程,首先先了解一下什么是”进程”?
线程在游戏中的应用体现是最为明显的,当你使用一个账号登录时,在进行游戏的过程中,我们游戏角色的生死存亡就是挂在一个线程上。如果是攻城式的游戏,即自带小兵攻击,我们经常玩游戏的应该有感受,当你被消灭时,你的小兵也会跟着死掉,这其实就是在后面会提到的daemon守护线程的操作,这个会在后面展现出来!来来,入门伸头看看咯!

什么是进程?一般来说一个应用程序开始运行,它就是一个主”进程”。它由操作系统管理;每一个进程都有它自己的内存空间和系统资源。
什么是线程?线程,是由某个进程单独启动的一个独立运行代码块。一个线程,一旦启动之后,同主进程一样,一起竞争系统资源。一个进程,可以开出很多的线程。一个线程就是一个单独的执行路径。多个线程,就是多个执行路径,所有的这些执行路径同时竞争系统资源;
单线程和多线程:
1).单线程:一个应用程序只有一条执行路径。(之前我们开发的所有程序)
2).多线程:一个应用程序开出多个线程,也就是具有多个执行路径。
Java程序运行的原理:
1).由虚拟机加载启动的main()方法所在类。
2).同时,虚拟机在后台也会运行一些其他的程序:例如:垃圾回收器。
3).java虚拟机也是一个多线程的程序;
什么是并行和并发?
前者是逻辑上同时发生,指在”某一个时间内”同时运行多个程序。
后者是物理上同时发生,指在”某一个时间点”同时运行多个程序。
那么,我们能不能实现真正意义上的并发呢,是可以的,多个CPU就可以实现,不过你得知道如何调度和控制它们。

/*
* 线程的实现方式一:
* 步骤:
* 1.定义一个类,继承自java.lang.Thread
* 2.重写里面的run()方法;
* 3.在测试类中,实例化自定义类的对象,并调用它的start()方法,启动一个线程;
*
* 线程的实现方式二:
* 步骤:
* 1.定义一个类,实现Runnable接口;
* 2.重写里面的run()方法;
* 3.在测试类中:
* 1).实例化自定义类的对象;
* 2).Thread t = new Thread(自定义类对象引用);
* t.start();
* 简化的写法:
* new Thread(自定义类对象引用).start( );

package com.myTest.demo02_线程的实现方式一;
public class Demo {
public static void main(String[] args) {
//3.在测试类中,实例化自定义类的对象,并调用它的start()方法,启动一个线程;
MyThread t = new MyThread();
t.start();//启动线程,会执行run()方法
// t.run();//普通的方法调用,并不能作为一个线程启动。
// t.start();//对于同一个线程对象,不能多次的start();异常:IllegalThreadStateException
for(int j = 0;j < 20 ;j++){
System.out.println("j = " + j);
}
System.out.println("当前main()方法 的线程名称:" +
Thread.currentThread().getName());
System.out.println("main方法执行完毕!");
}
}
线程的实现方式一;

//1.自定义类,继承自Thread。代表我们的类就是一个Thread
public class MyThread extends Thread{

//2.重写Thread中的run()方法;在线程启动后,需要做的事情,写在run()方法中;
@Override
public void run() {
for(int i = 0;i < 20;i++){
System.out.println("i = " + i);
}
}
}

线程的调度_sleep 休眠;

线程的休眠:Thread –> public static void sleep(long millis)

import java.text.SimpleDateFormat;
import java.util.Date;

public class Demo {
public static void main(String[] args) {
/*for(int i = 1 ;i <= 10;i++){
//每秒打印一次
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = sdf.format(date);
System.out.println("当前时间: " + dateStr);
//休息1秒
try {
//当前线程休眠
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/

//启动线程
MyThread t = new MyThread();
t.start();
}
}

import java.text.SimpleDateFormat;
import java.util.Date;

public class MyThread extends Thread {
@Override
public void run() {
for(int i = 1 ;i <= 10;i++){
//每秒打印一次
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = sdf.format(date);
System.out.println("当前时间: " + dateStr);
//休息1秒
try {
//当前线程休眠
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

默认情况下,当前进程开出的所有线程都是”非守护线程”:当主进程结束,会等待所有线程结束,应用程序才会结束;

让开出的线程作为”守护线程”:Thread –>public final void setDaemon(boolean on):如果为true,为守护线程

守护线程:当主进程结束时,所有的”守护线程”也会结束。(但不会立即结束,通常会有个小缓冲)

后台线程_守护线程_setDaemon;

public class Demo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();

t1.setName("兵1");
t2.setName("兵2");

t1.setDaemon(true);
t2.setDaemon(true);

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

//主进程是将军
for(int i = 1 ;i <= 10;i++){
System.out.println("将军杀敌:" + i);
}
System.out.println("将军杀敌结束,收工!!");
}
}


public class MyThread extends Thread{
@Override
public void run() {
System.out.println("我是:" + this.getName() + " ,我要杀敌100.");
for(int i = 0;i < 100;i++){
System.out.println(this.getName() + " 杀敌:" + (i + 1));
}
System.out.println(this.getName() + " 杀完了!收工!!");
}
}

停止线程_interrupt;

  • 停止线程:
  • public final void stop():
    public void interrupt():
public class Demo {
public static void main(String[] args) {
//启动MyThread,MyThread中的run会执行20秒,我们可以在主进程中,让
//MyThread执行3秒后,如果没有停止,那么就强行停止它
MyThread t = new MyThread();
t.start();

/*try {
Thread.sleep(1000 * 3);//等待3秒,MyThread会执行3秒
} catch (InterruptedException e) {
e.printStackTrace();
}*/

//强行去停止MyThread
// t.stop();//停止线程,但此方法已过时;
t.interrupt();//stop的替换方法;去使MyThread的run()抛出一个异常,在异常中,我们去结束线程执行;

}
}

public class MyThread extends Thread{
@Override
public void run() {

for(int i = 0;i < 20 ;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("线程抛出异常,出现意外情况了,我结束执行!");
break;
}
System.out.println("i = " + i);
}

}
}
  • 线程的实现方式二:
  • 步骤:
  • 1.定义一个类,实现Runnable接口;
  • 2.重写里面的run()方法;
  • 3.在测试类中:
  • 1).实例化自定义类的对象;
  • 2).Thread t = new Thread(自定义类对象引用);
  • t.start();
  • 简化的写法:
  • new Thread(自定义类对象引用).start();
public class Demo {
public static void main(String[] args) {
//3.1.实例化
MyRunnable myRun = new MyRunnable();
//3.2
/*Thread t1 = new Thread(myRun,"邓超");
Thread t2 = new Thread(myRun,"孙俪");
//启动线程
t1.start();
t2.start();*/

//简化写法
new Thread(myRun,"邓超").start();
new Thread(myRun,"孙俪").start();
}
}


public class MyRunnable implements Runnable{

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

}



Runnable接口实现卖票程序;

public class Demo {
public static void main(String[] args) {
TicketPool pool = new TicketPool();

SellTicketRunnable tRun1 = new SellTicketRunnable(pool);
SellTicketRunnable tRun2 = new SellTicketRunnable(pool);
SellTicketRunnable tRun3 = new SellTicketRunnable(pool);

new Thread(tRun1,"窗口1").start();
new Thread(tRun2,"窗口2").start();
new Thread(tRun3,"窗口3").start();
}
}

import java.util.ArrayList;

public class SellTicketRunnable implements Runnable {
private TicketPool pool = null;
//ArrayList不保证线程安全
private ArrayList<Integer> ticArray = new ArrayList<Integer>();

public SellTicketRunnable(TicketPool pool) {
this.pool = pool;

}

@Override
public void run() {
// 一直抢票,一直抢到返回值为0
while (true) {
int t = this.pool.getTicket();
if (t <= 0) {
System.out.println("没票了,我结束了!");
break;
}
// System.out.println(this.getName() + " 抢到票:" + t);
ticArray.add(t);// 将票装到集合中
}
System.out.println(Thread.currentThread().getName() + " 一共抢到:" + this.ticArray.size()
+ " 张票!");
}

}

public class TicketPool {
private int tickets = 100;

public int getTicket(){
if(tickets > 0){
return this.tickets--;
}else{
return 0;
}
}
}

进行同步操作后
加入同步代码块_synchronized;
/*
* 同步代码块,解决多线程环境下的并发问题:
*
* 问题:
* 1.是否是多线程环境
2.是否有共享数据
3.是否有多条语句操作共享数据

解决:将可能会被多个线程访问的代码进行”同步”使用关键字:synchronized
语法:synchronized(theObject){
//可能会被多个线程访问的代码
}
theObject:是一个对象,对其加锁的对象。表示,当我访问这个对象的某个同步代码块时,
其它的线程不能访问这个对象的同步方法或同步代码块
synchronized:
好处:代码在多线程环境下变得安全,数据变得可靠;
弊端:由于内部做了很多线程控制方面的工作,所以效率低。

public class Demo {
public static void main(String[] args) {
TicketPool pool = new TicketPool();

SellTicketThread t1 = new SellTicketThread(pool);
SellTicketThread t2 = new SellTicketThread(pool);
SellTicketThread t3 = new SellTicketThread(pool);

t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

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

import java.util.ArrayList;

public class SellTicketThread extends Thread {
private TicketPool tPool = null;
private ArrayList<Integer> ticArray = new ArrayList<Integer>();

public SellTicketThread(TicketPool pool){
this.tPool = pool;
}
@Override
public void run() {
//一直抢票,一直抢到返回值为0
while(true){
int t = this.tPool.getTicket();
if(t <= 0){
System.out.println("没票了,我结束了!");
break;
}
// System.out.println(this.getName() + " 抢到票:" + t);
ticArray.add(t);//将票装到集合中
}
System.out.println(this.getName() + " 一共抢到:" + this.ticArray.size() + " 张票!");
}
}


public class TicketPool {
private int tickets = 100;

public int getTicket(){//窗口1,窗口2
//比如这里有些复杂的操作,需要耗费5毫秒
synchronized(this){//下面这段代码具有多线程的安全性:窗口1 先进入
try {//这段代码块将会被锁定,不允许其它线程访问。如果有线程访问,将会列队等待,直到窗口1执行完毕;
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(tickets > 0){
return this.tickets--;
}else{
return 0;
}
}
//窗口1 执行完毕,解锁,其它线程才可以进入
}
}

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-