Java基础语法练习43(线程)
目录
一、基本概念
二.线程的基本使用方法
1.继承Thread 类,重写 run 方法
2.实现 Runnable 接口,重写 run 方法
3.练习题(多窗口售卖火车票-有超卖的问题)
三、线程终止
四、线程常用方法
1.第一组线程常用方法:
2.用户线程和守护线程
五、Synchronized
1.线程同步机制:
2. Synchronized 的实现方法:
六、互斥锁
1.基本介绍:
七、线程的死锁
八、释放锁
九、测试题
题目1:
题目2:
一、基本概念
进程:
- 进程是指运行中的程序,比如我们使用 QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
- 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程。
线程:
- 线程由进程创建的,是进程的一个实体。
- 一个进程可以拥有多个线程。
总结:进程:使用QQ ; 线程:在QQ中可以同时聊天、下载图片、下载文件等
并发:同一个时刻,多个任务交替执行,造成一种 “貌似同时” 的错觉,简单的说,单核 cpu 实现的多任务就是并发。
并行:同一个时刻,多个任务同时执行。多核 cpu 可以实现并行。并发和并行,
二.线程的基本使用方法
在 java 中线程来使用有两种方法:
- 继承 Thread 类,重写 run 方法
- 实现 Runnable 接口,重写 run 方法
1.继承Thread 类,重写 run 方法
基本示例代码如下:
public class ThreadUse001 {
public static void main(String[] args) throws InterruptedException {
Cat cat = new Cat();
cat.start();
// 4.当main线程启动一个子线程Thread-0, 主线程不会阻塞, 会继续执行
// 5.主线程结束并不影响子线程结束,程序未必结束
for (int i = 0; i < 10; i++) {
System.out.println("主线程里的嘿嘿"+" 主线程的名字为:"+Thread.currentThread().getName());
Thread.sleep(10);
}
}
}
//通过继承Thread类创建线程
//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们会重写 run方法,写上自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的run方法
class Cat extends Thread{
int times = 0;
@Override
public void run(){ //重写 run 方法,写上自己的业务逻辑
while(true){
System.out.println("嘿嘿" +(++times)+" 子线程的名字为:"+Thread.currentThread().getName());
try {
sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (times == 100){
break;
}
}
}
}
2.实现 Runnable 接口,重写 run 方法
基本示例代码如下:
public class ThreadUse002 {
public static void main(String[] args) {
Dog dog = new Dog();
//dog.start();这里不能调用start
//需采用以下方法
Thread t1 = new Thread(dog);
t1.start();
}
}
class Dog implements Runnable {
int times = 0;
public void run() { //普通方法,并不会真正的开启一个多线程
while(true){
System.out.println("Dog run times: " + (++times)+"线程的名字为:"+Thread.currentThread().getName());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(times == 20){
break;
}
}
}
}
3.练习题(多窗口售卖火车票-有超卖的问题)
代码如下:
public class ThreadUse004 {
public static void main(String[] args) {
/*
//测试使用Thread 方式
SellTicket01 sellTicket01 = new SellTicket01();
SellTicket01 sellTicket02 = new SellTicket01();
SellTicket01 sellTicket03 = new SellTicket01();
//这里我们会出现超卖..
//比如还有2张票,但是同时通过了if的判断条件
sellTicket01.start();//启动售票线程
sellTicket02.start();//启动售票线程
sellTicket03.start();//启动售票线程
*/
//还是会出现超卖的情况
System.out.println("===使用实现接口方式来售票=====");
SellTicket02 sellTicket04 = new SellTicket02();
new Thread(sellTicket04).start();//第 1 个线程-窗口
new Thread(sellTicket04).start();//第 2 个线程-窗口
new Thread(sellTicket04).start();//第 3 个线程-窗口
}
}
//使用Thread 方式
class SellTicket01 extends Thread {
private static int ticketNum = 100;//让多个线程共享 ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束...");
break;
}
//休眠50毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数="+(--ticketNum));
}
}
}
//实现接口方式
class SellTicket02 implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束...");
break;
}
//休眠50毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数="+(--ticketNum));//1- 0--1--2
}
}
}
三、线程终止
1.基本说明:
1)当线程完成任务后,会自动退出。
2)还可以通过使用变量来控制 run 方法退出的方式停止线程,即通知方式
示例代码如下:
public class ThreadExit001 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
Thread.sleep(1000); //mian线程休眠1秒,然后通知退出线程
t.setLoop(false);
}
}
class T extends Thread{
private boolean loop=true;
public void setLoop(boolean loop) {
this.loop = loop;
}
public void run(){
while(loop){
System.out.println("T1线程在运行,名字为:"+Thread.currentThread().getName());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
四、线程常用方法
1.第一组线程常用方法:
- setName // 设置线程名称,使之与参数 name 相同
- getName // 返回该线程的名称
- start // 使该线程开始执行;Java 虚拟机底层调用该线程的 start0 方法
- run // 调用线程对象 run 方法;
- setPriority // 更改线程的优先级
- getPriority // 获取线程的优先级
- sleep // 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
- interrupt // 中断线程
- yield: 线程的礼让。让出 cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
- join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
示例代码如下:
public class ThreadUse005 {
public static void main(String[] args) throws InterruptedException {
T11 t11 = new T11();
t11.start();
for (int i = 0; i < 10; i++) {
System.out.println("第"+(i+1)+"次循环,线程名字为:"+Thread.currentThread().getName());
Thread.sleep(100);
if(i==4){
System.out.println("主线程循环5次后 让 子线程先运行");
//t11.join();//插队
Thread.yield();//礼让,如果CPU资源多,不一定礼让成功
System.out.println("子线程运行完了,现在主线程接着运行");
}
}
}
}
class T11 extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("第"+(i+1)+"次循环,线程名字为:"+Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
2.用户线程和守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
- 常见的守护线程:垃圾回收机制
示例代码如下:
public class ThreadUse005 {
public static void main(String[] args) throws InterruptedException {
T11 t11 = new T11();
t11.setDaemon(true);//守护线程 主线程结束,子线程也随之结束
t11.start();
for (int i = 0; i < 10; i++) {
System.out.println("第"+(i+1)+"次循环,线程名字为:"+Thread.currentThread().getName());
Thread.sleep(100);
}
}
}
class T11 extends Thread {
public void run() {
for (int i = 0; ; i++) {
System.out.println("第"+(i+1)+"次循环,线程名字为:"+Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
五、Synchronized
1.线程同步机制:
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
- 也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作.
2. Synchronized 的实现方法:
- 同步代码块
synchronized (对象) { // 得到对象的锁,才能操作同步代码
// 需要被同步代码;
} - synchronized 还可以放在方法声明中,表示整个方法 - 为同步方法
public synchronized void m (String name){
// 需要被同步的代码
}
示例代码如下:
public class Synchronized0001 {
public static void main(String[] args) {
//测试一把
SellTicket002 sellTicket002 = new SellTicket002();
new Thread(sellTicket002).start();//第 1 个线程-窗口
new Thread(sellTicket002).start();//第 2 个线程-窗口
new Thread(sellTicket002).start();//第 3 个线程-窗口
}
}
//实现接口方式, 使用synchronized实现线程同步
class SellTicket002 implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
private boolean flag = true;
public synchronized void sell() {
if (ticketNum <= 0) {
System.out.println("售票结束...");
flag = false;
return;
}
//休眠50毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数="+(--ticketNum));//1- 0--1--2
}
@Override
public void run() {
while (flag) {
sell();
}
}
}
六、互斥锁
1.基本介绍:
- Java 语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
- 每个对象都对应于一个可称为 “互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
- 关键字 synchronized 来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问。
- 同步的局限性:导致程序的执行效率要降低。
- 同步方法(非静态的)的锁可以是 this, 也可以是其他对象 (要求是同一个对象)。
- 同步方法(静态的)的锁为当前类本身。
注意事项和细节:
- 同步方法如果没有使用 static 修饰:默认锁对象为 this
- 如果方法使用 static 修饰,默认锁对象:当前类.class
- 实现的落地步骤:
- 需要先分析上锁的代码
- 选择同步代码块或同步方法
- 要求多个线程的锁对象为同一个即可!
七、线程的死锁
基本介绍:
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生。
例如:
妈妈:你先完成作业,才让你玩手机
小明:你先让我玩手机,我才完成作业.
示例代码如下:
public class DeadLock01 {
public static void main(String[] args) {
//模拟死锁现象
DeadLockDemo A=new DeadLockDemo(true);
A.setName("A线程");
DeadLockDemo B=new DeadLockDemo(false);
B.setName("B线程");
A.start();
B.start();
}
}
class DeadLockDemo extends Thread {
static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Override
public void run() {
//下面业务逻辑的分析
//2. 如果线程A 得不到 o2 对象锁,就会Blocked
//4. 如果线程B 得不到 o1 对象锁,就会Blocked
if (flag) {//1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
synchronized (o1) {//对象互斥锁, 下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入 1");
synchronized (o2) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 2");
}
}
} else {synchronized (o2) { //3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
System.out.println(Thread.currentThread().getName() + " 进入 3");
synchronized (o1) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 4");
}
}
}
}
}
八、释放锁
下面操作会释放锁:
- 当前线程的同步方法、同步代码块执行结束
案例:上厕所,完事出来 - 当前线程在同步代码块、同步方法中遇到 break、return。
案例:没有正常的完事,经理叫他修改 bug,不得已出来 - 当前线程在同步代码块、同步方法中出现了未处理的 Error 或 Exception,导致异常结束
案例:没有正常的完事,发现忘带纸,不得已出来 - 当前线程在同步代码块、同步方法中执行了线程对象的 wait () 方法,当前线程暂停,并释放锁。
案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去
下面操作不会释放锁:
- 线程执行同步代码块或同步方法时,程序调用 Thread.sleep ()、Thread.yield () 方法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在坑位上眯了一会 - 线程执行同步代码块时,其他线程调用了该线程的 suspend () 方法将该线程挂起,该线程不会释放锁。
提示:应尽量避免使用 suspend () 和 resume () 来控制线程,方法不再推荐使用
九、测试题
题目1:
(1) 在 main 方法中启动两个线程
(2) 第 1 个线程循环随机打印 100 以内的整数
(3) 直到第 2 个线程从键盘读取了 “Q” 命令。
import java.util.Scanner;
public class HomeWork01 {
public static void main(String[] args) {
A a = new A();
a.start();
B b = new B(a);
b.start();
}
}
class A extends Thread{
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
public void run(){
while(flag){
//p = (int) (Math.random()*3+0); //整数 0,1,2
System.out.println((int) (Math.random()*100+1));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class B extends Thread {
private A a;
Scanner sc = new Scanner(System.in);
public B(A a) {
this.a = a;
}
boolean flag = true;
public void run(){
System.out.println("请输入Q进行线程终止");
char c = sc.next().toUpperCase().charAt(0);
if(c == 'Q'){
a.setFlag(false);
}
}
}
题目2:
(1) 有 2 个用户分别从同一个卡上取钱(总额:10000)
(2) 每次都取 1000,当余额不足时,就不能取款了
(3) 不能出现超取现象 => 线程同步问题
package Thread0002;
public class HomeWork02 {
public static void main(String[] args) {
T t = new T();
Thread t1 = new Thread(t);
t1.setName("t1");
Thread t2 = new Thread(t);
t2.setName("t2");
t1.start();
t2.start();
}
}
//1.因为这里涉及到多个线程共享资源,所以使用实现Runable方式
class T implements Runnable {
private int money = 10000;
@Override
public void run() {
while (true) {
//1.使用synchronized实现线程同步
//2.当多个线程执行到这时。要抢this这个锁
synchronized (this) {
//判断余额
if (money < 1000) {
System.out.println("余额不足");
break;
}
money = money - 1000;
System.out.println(Thread.currentThread().getName() + "取了1000" + " 当前余额为" + money);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}