复习Java多线程&Java面试题数据库的并发场景下事务存在哪些数据问题&生活【记录一个咸鱼大学生三个月的奋进生活】020

复习Java多线程

学习多线程之前我们先来了解一下什么进程和线程的区别?

进程:
  首先进程是执行中的程序(比如现在你们正在使用的浏览器) 他是一个活动的实体,而且进程是“自包容”的运行程序在运行之后就会自己操作自己,有自己的地址空间,进程相较于线程来说是重量级的,因为进程耦合性方面要比线程的大一些,但是线程提供的服务要比线程多。

线程:
  首先线程相对进程来说是轻量级进程,是CPU使用的基本单元,线程属于某一个进程,一个进程中拥有一个或多个线程,多个线程共享同一个进程资源。

特点:
各个进程需要分配它们自己独立的地址空间
进程间调用涉及的开销比线程间通信多
多个线程可共享相同的地址空间并且共同分享同一个进程
线程间的切换成本比进程间切换成本低
进程是重量级的、线程是轻量级的

多任务处理:
多任务处理分两类
① 基于进程: 计算机同时运行多个进程
② 基于线程: 一个进程包含多个线程

使用多线程的理由:
① 与用户交互更佳
② 模拟同步动作
③ 利用多处理器
④ 等待缓慢I/O操作时完成其他任务
⑤ 简化对象模型
⑥ 多线程可以用来解决阻塞情况,阻塞:比如I/O读写时没有反馈

电脑实现多线程的原理:
CPU是用很快的速度循环处理一个又一个的线程,快到让你感觉就是在同时处理多个线程!

JVM(Java虚拟机)中的线程:
  JVM进程内至少包含了两个线程:主函数线程(main方法)垃圾回收线程,它们称为主线程,有时它们还会启动其他线程,它们必须最后完成执行,执行各种关闭、释放动作,然后咱们自己定义使用的其他线程就会在主函数线程以及垃圾回收线程之间完成运行。

实现Java中的多线程

第一种:声明一个类继承 Thread 类,并重写其中的 run() 方法实现多线程

例子(创建第一个线程 A1):

package com.javawork26;

// 线程的实现A1,这是通过继承Thread线程包实现的,缺点就是这个类永远就是线程类了,适用于小型项目

public class Work26_ThreadxianchengdeshixianA1 extends Thread {         // 类继承Thread,重写里面的run方法

	@Override
	public void run() {        // 重写run方法
		System.out.println("t1运行了");
		try {
			for(int i = 0; i < 10; i++) {
				System.out.println("aaaaa");
				Thread.currentThread().sleep(1000);           // .currentThread()实例化一个线程Thread,不传参就是获得当前类的线程然后执行sleep方法,sleep方法内传入毫秒数,就是等多少毫秒之后执行之后代码
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("t1结束了");
	}
}

例子(创建第二个线程 A2):

package com.javawork26;

// 线程的实现A2,这是通过继承Thread线程包实现的,缺点就是这个类永远就是线程类了,适用于小型项目

public class Work26_ThreadxianchengdeshixianA2 extends Thread {         // 类继承Thread,重写里面的run方法

	@Override
	public void run() {        // 重写run方法
		System.out.println("t2运行了");
		try {
			for(int i = 0; i < 10; i++) {
				System.out.println("bbbbb");
				Thread.currentThread().sleep(2000);           // .currentThread()实例化一个线程Thread,不传参就是获得当前类的线程然后执行sleep方法,sleep方法内传入毫秒数,就是等多少毫秒之后执行之后代码
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("t2结束了");
	}
}

例子(调用多线程的运行类【调用:线程实体类.start()方法 —— 运行该线程】):

package com.javawork26;

// 这是实现多线程原理的运行类

public class Run {

	public static void main(String[] args) {
		System.out.println("main运行了");
		
		// 这是调用继承Thread类的多线程类使用,并调用了一些线程的常用方法
		System.out.println(Thread.activeCount());                     // .activeCount()方法返回激活的线程数
		
		Work26_ThreadxianchengdeshixianA1 t1 = new Work26_ThreadxianchengdeshixianA1();
		Work26_ThreadxianchengdeshixianA2 t2 = new Work26_ThreadxianchengdeshixianA2();
		
		System.out.println(t1.getName());                             // .getName()方法获得线程的名字,.setName()方法可以设置线程的名字
		System.out.println(t2.getName());
		
		System.out.println(t1.isAlive());                             // .isAlive()方法看线程是否正在运行(返回true或false)
		System.out.println(t2.isAlive());
		
		t1.start();                  // .start()方法就是并发执行之前线程类run方法里的代码就成多线程操作,不能用run方法因为用run方法出来的还是单线程的结果
		t2.start();
		
		System.out.println(t1.isAlive());
		System.out.println(t2.isAlive());

		System.out.println(Thread.activeCount());
		
		try {
			t1.join();                                                // .join()方法是当该进程结束后再执行之后的代码
			System.out.println(t1.isAlive());
			t2.join();
			System.out.println(t2.isAlive());
			
			System.out.println("main结束了");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

运行结果:

第二种:声明一个类实现 Runnable 接口,并覆盖 run() 方法实现多线程

例子(创建第一个线程 B1):

package com.javawork26;

// 通过实现Runnable接口里的run方法来实现线程,适用于大型项目

public class Work26_ThreadxianchengdeshixianB1 implements Runnable {

	@Override
	public void run() {
		System.out.println("t1运行了");
		try {
			for(int i = 0; i < 10; i++) {
				System.out.println("aaaaa");
				Thread.currentThread().sleep(1000);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("t1结束了");
	}
}

例子(创建第一个线程 B2):

package com.javawork26;

// 通过实现Runnable接口里的run方法来实现线程,适用于大型项目

public class Work26_ThreadxianchengdeshixianB2 implements Runnable {

	@Override
	public void run() {
		System.out.println("t2运行了");
		try {
			for(int i = 0; i < 10; i++) {
				System.out.println("bbbbb");
				Thread.currentThread().sleep(2000);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("t2结束了");
	}
}

例子(先实例化Thread类再将刚才实现Runnable接口的类当参数传入然后调用start方法实现多线程)

package com.javawork26;

// 这是实现多线程原理的运行类

public class Run {

	public static void main(String[] args) {
		System.out.println("main运行了");
		
		// 这是调用继承Thread类的多线程类使用,并调用了一些线程的常用方法
		System.out.println(Thread.activeCount());                     // .activeCount()方法返回激活的线程数
		
		// 这是调用实现了Runnable的多线程类的使用
		Thread t1 = new Thread(new Work26_ThreadxianchengdeshixianB1());          // 因为该类只是实现了接口而并没有继承于Thread类,所以他并不是一个线程类,所以要先实例化Thread时传入该类
		Thread t2 = new Thread(new Work26_ThreadxianchengdeshixianB2());
	
		System.out.println(t1.getName());                             // .getName()方法获得线程的名字,.setName()方法可以设置线程的名字
		System.out.println(t2.getName());
		
		System.out.println(t1.isAlive());                             // .isAlive()方法看线程是否正在运行(返回true或false)
		System.out.println(t2.isAlive());
		
		t1.start();             
		t2.start();
		
		System.out.println(t1.isAlive());
		System.out.println(t2.isAlive());

		System.out.println(Thread.activeCount());
		
		try {
			t1.join();                                                // .join()方法是当该进程结束后再执行之后的代码
			System.out.println(t1.isAlive());
			t2.join();
			System.out.println(t2.isAlive());
			
			System.out.println("main结束了");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

例子(改进第一个线程 B1,因为用这个方法实现多线程时还要在使用该类时实例化 Thread 再传参,比较麻烦,所以可以在该类的构造函数直接实例化Thred并将自己传入然后再调用 Start 方法,以至于在使用类时可以直接实例化该类立马实现线程):

package com.javawork26;

// 通过实现Runnable接口里的run方法来实现线程,这个类是一个改进版:加上了在实例化时的构造方法直接就可以实例化本类的Thread类并传入,并且调用其中的Start方法
// 适用于大型项目

public class Work26_ThreadxianchengdeshixianB3 implements Runnable {

	private Thread t = null;
	
	// 重写一下构造方法直接将本类传入Thread实例化时的参数,并调用Start方法
	public Work26_ThreadxianchengdeshixianB3() {
		t = new Thread(this);
		t.start();
	}

	@Override
	public void run() {
		System.out.println("t1运行了");
		try {
			for(int i = 0; i < 10; i++) {
				System.out.println("aaaaa");
				Thread.currentThread().sleep(1000);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("t1结束了");
	}
}

然后在调用类就可以这样写直接就能实现多线程:

package com.javawork26;

// 这是实现多线程原理的运行类

public class Run {

	public static void main(String[] args) {
		
		// 这是调用实现了Runnable的多线程类的使用:
		Thread t1 = new Thread(new Work26_ThreadxianchengdeshixianB1());          // 因为该类只是实现了接口而并没有继承于Thread类,所以他并不是一个线程类,所以要再实例化Thread时传入该类
		Thread t2 = new Thread(new Work26_ThreadxianchengdeshixianB2());
		t1.start();
		t2.start();
		
		// 这是调用改进的B3线程的使用,反观是不是方便很多:
		new Work26_ThreadxianchengdeshixianB3();
		
	}
}

了解这两种方法的区别

第一种方法:线程类继承 Thread 类,这样的话这个类就永远都是线程类了,不在运行类中 start 他,就不会执行,这个方法更适合小型项目,需求不是那么多的。

第二种方法:线程类实现 Runnable 接口,这样的话实例化这个类还是可以使用,当作为 Thred 类的参数实例化时那就会作为线程运行,这个方法更适合大型项目,当一个类想实现多个事时,又或是可能是多线程可能是普通使用时。

线程的状态

新建态(NEW): 至今尚未启动的线程处于这种状态
新创建的但未调用 .start() 方法的线程处于该状态

可运行态 (RUNNABLE): 可运行线程的线程状态
处于可运行状态的某一线程正在 Java 虚拟机中运行,但它可能正在等待操作系统中的其他资源

锁定态 (BLOCKED): 受阻塞线程的线程状态
处于受阻塞状态的线程正在等待监视器锁,以便进入一个同步的块/方法,或者在调用 Object.wait() 之后再次进入同步 的块/方法

等待态(WAITING ): 某一等待线程的线程状态
某一线程因为调用不带超时值的 Object.wait()、Thread.join()方法之一 而处于等待状态

定时等待态 (TIMED_WAITING): 具有指定等待时间的某一等待线程的线程状态
某一线程因为调用带有指定正等待时间的 Thread.sleep()、Object.wait()、Thread.join()方法之一 的等待状态

中止态(TERMINATED): 已终止线程的线程状态
线程已经结束执行时是这个状态

Thread 类中常用的方法

.getName() —— 返回线程的名称
.isAlive() —— 如果线程是激活的,则返回 true
.setName(String name) —— 将线程的名称设置为由name指定的名称
.join() —— 等待该线程结束再执行
.isDaemon() —— 检查线程是否为守护线程
.setDaemon(boolean on) —— 标记线程是否为守护线程
.sleep(n) —— 用于将线程挂起一段时间,也就是每隔n毫秒执行一次该线程
.start() —— 调用run()方法启动线程,开始执行线程
.activeCount() —— 返回激活的线程数
.yield() —— 使正在执行的线程临时暂停,并允许其他线程执行

线程优先级

Java 中线程的优先级是在 Thread 类中定义的常量
MAX_PRIORITY : 值为 10
NORM_PRIORITY : 值为 5
MIN_PRIORITY : 值为 1
默认优先级为 NORM_PRIORITY

有关优先级的方法有两个:
.setPriority(优先级级别数) —— 设置线程的当前优先级
.getPriority() —— 返回线程的优先级

对于线程优先级,我们需要注意:

  • Thread.setPriority()可能根本不做任何事情,这跟你的操作系统和虚拟机版本有关
  • 线程优先级对于不同的线程调度器可能有不同的含义,可能并不是你直观的推测。特别地,优先级并不一定是指CPU的分享,在UNIX系统,优先级或多或少可以认为是CPU的分配,但Windows不是这样
  • 线程的优先级通常是全局的和局部的优先级设定的组合。Java的setPriority()方法只应用于局部的优先级。换句话说,你不能在整个可能的范围 内设定优先级。(这通常是一种保护的方式,你大概不希望鼠标指针的线程或者处理音频数据的线程被其它随机的用户线程所抢占)
  • 不同的系统有不同的线程优先级的取值范围,但是Java定义了10个级别(1-10)。这样就有可能出现几个线程在一个操作系统里有不同的优先级,在另外一个操作系统里却有相同的优先级(并因此可能有意想不到的行为)
  • 操作系统可能(并通常这么做)根据线程的优先级给线程添加一些专有的行为(例如”only give a quantum boost if the priority is below X“)。这里再重复一次,优先级的定义有部分在不同系统间有差别。
  • 大多数操作系统的线程调度器实际上执行的是在战略的角度上对线程的优先级做临时操作(例如当一个线程接收到它所等待的一个事件或者I/O),通常操作系统知道最多,试图手工控制优先级可能只会干扰这个系统。
  • 你的应用程序通常不知道有哪些其它进程运行的线程,所以对于整个系统来说,变更一个线程的优先级所带来的影响是难于预测的。例如你可能发现,你有一个预期 为偶尔在后台运行的低优先级的线程几乎没有运行,原因是一个病毒监控程序在一个稍微高一点的优先级(但仍然低于普通的优先级)上运行,并且无法预计你程序 的性能,它会根据你的客户使用的防病毒程序不同而不同

线程同步

线程同步的概念

有时两个或多个线程可能会试图同时访问一个资源
例如:一个线程可能尝试从一个文件中读取数据,而另一个线程则尝试在同一文件中修改数据
在此情况下,数据就可能会变得不一致

为了确保在任何时间点一个共享的资源只被一个线程使用,就必须使用“同步”
同步是指同时只能有一个线程访问同一个资源

Java实现线程同步

① 将类中的方法通过synchronized修饰符声明成一个同步方法:
public synchronized void 同步方法() { }
// 但是只有这个方法永远都是需要同步的情况下使用才合适

② 同步块:
synchronized() {
  写需要同步执行的代码
}
// 大多数情况用这个

不用线程同步时的例子(当一个打印店需要一个一个打印文件时,这是突然来了一群学生想打印,如果没有线程同步而是放任线程执行时就会出现打印顺序混乱的情况):

1、打印方法的类:

package com.test;

// 这是实现线程同步方法的打印具体案例

public class Work27_xianchengtongbuDayin {

	// pt打印 {学生名字} 方法,info获取的是Student类里调用时传入的学生姓名
	public void pt(String info) {
		System.out.print("{");
		try {
			Thread.sleep(1000);
			System.out.println(info + "}");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}	
}

2、学生打印时的类:

package com.test;

// 这是实现线程同步方法的学生打印时调用pt打印方法的具体案例

public class Work27_xianchengtongbuStudent extends Thread {
	private String name = null;
	private Work27_xianchengtongbuDayin printM = null;
	
	// 重写构造方法获取传入的学生姓名
	public Work27_xianchengtongbuStudent() {
		// TODO Auto-generated constructor stub
	}
	public Work27_xianchengtongbuStudent(String name, Work27_xianchengtongbuDayin printM) {
		this.name = name;
		this.printM = printM;
	}
	
	// 重写线程类中的run方法
	@Override
	public void run() {
		
		this.printM.pt(this.name);
			
	}
}

3、三个学生打印时的运行类:

package com.test;

// 当多线程需要在同一时间访问同一资源时的情况

public class Work27_xianchengtongbuRun {

	public static void main(String[] args) {
		Work27_xianchengtongbuDayin printM = new Work27_xianchengtongbuDayin();                    // 实例化打印方法以供传入学生类构造方法实例化
		
		Work27_xianchengtongbuStudent s1 = new Work27_xianchengtongbuStudent("jeck", printM);      // 实例化三个打印的学生
		Work27_xianchengtongbuStudent s2 = new Work27_xianchengtongbuStudent("lucy", printM);
		Work27_xianchengtongbuStudent s3 = new Work27_xianchengtongbuStudent("peter", printM);
		
		s1.start();
		s2.start();
		s3.start();
	}
}

4、没用线程同步时的运行结果:
在这里插入图片描述


使用线程同步时的例子(当一个打印店需要一个一个打印文件时,突然来了一群学生想打印,有了线程同步这些学生就会排好队一个一个打印):

1、打印方法的类:

package com.test;

// 这是实现线程同步方法的打印具体案例

public class Work27_xianchengtongbuDayin {

	// pt打印 {学生名字} 方法,info获取的是Student类里调用时传入的学生姓名
	public void pt(String info) {
		System.out.print("{");
		try {
			Thread.sleep(1000);
			System.out.println(info + "}");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}	
}

2、学生打印时的类(并将run方法中使用了线程同步块):

package com.test;

// 这是实现线程同步方法的学生打印时调用pt打印方法的具体案例

public class Work27_xianchengtongbuStudent extends Thread {
	private String name = null;
	private Work27_xianchengtongbuDayin printM = null;
	
	// 重写构造方法获取传入的学生姓名
	public Work27_xianchengtongbuStudent() {
		// TODO Auto-generated constructor stub
	}
	public Work27_xianchengtongbuStudent(String name, Work27_xianchengtongbuDayin printM) {
		this.name = name;
		this.printM = printM;
	}
	
	// 重写线程类中的run方法
	@Override
	public void run() public void run() {
		// 这是synchronized同步块实现方式
		synchronized(this.printM) {
			this.printM.pt(this.name);        // 调用printM实例化中的pt打印方法将打印学生的姓名传入
	}
}

3、三个学生打印时的运行类:

package com.test;

// 实现线程同步方法的调用,调用出来的结果并不会出现顺序错乱的情况,这就是线程同步
// 当多线程需要在同一时间访问同一资源时就用线程同步,当有一个线程进入执行时其他线程就不会插队

public class Work27_xianchengtongbuRun {

	public static void main(String[] args) {
		Work27_xianchengtongbuDayin printM = new Work27_xianchengtongbuDayin();                    // 实例化打印方法以供传入学生类构造方法实例化
		
		Work27_xianchengtongbuStudent s1 = new Work27_xianchengtongbuStudent("jeck", printM);      // 实例化三个打印的学生
		Work27_xianchengtongbuStudent s2 = new Work27_xianchengtongbuStudent("lucy", printM);
		Work27_xianchengtongbuStudent s3 = new Work27_xianchengtongbuStudent("peter", printM);
		
		s1.start();
		s2.start();
		s3.start();
	}
}

4、用了线程同步时的运行结果:
在这里插入图片描述

wait-notify机制

Java提供了一个精心设计的线程间通信机制,使用wait()、notify()和notifyAll()方法
这三个方法仅在 synchronized 方法中才能被调用

.wait()方法 —— 告知被调用的线程退出监视器并进入等待状态,直到其他线程进入相同的监视器并调用 notify( ) 方法,相当于阻塞线程
.notify() —— 唤醒离调用该方法的线程最近的线程
.notifyAll() —— 唤醒wait的所有线程,具有最高优先级的线程将先运行,其他线程继续wait

学习Java面试题(数据库的并发场景下事务存在哪些数据问题)

指路陈哈哈大佬的Java数据库相关面试题原帖

照片分享

作者:茶马古道  作品名:上海朝霞日出  出自500px社区






2021.07.21  by wyh

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Aspiriln

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值