多线程(动力)
概述
堆和方法区共享,栈独立
一个线程一个栈
main方法结束只代表主线程结束了,其他线程可能还在执行
思考:
使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。
main方法结束只是主线程结束了,主栈空了,其他的栈可能还在压栈弹栈
多线程的并发理解
问题: 对于单核的CPU来说,真的可以做到真正的多线程并发吗?
t1线程执行t1的
t2线程执行t2的。t1不影响t2,t2也不影响t1。这叫真正的多线程并发。
单核CPU表示只有一个大脑:
不能做到真正的多线程并发,但是可以做到给人一种”多线程并发“的感觉
对于单核的CPU来说,在某一时间点上实际上只能处理一件事情。只是由于CPU的处理速度极快,在多个线程之间频繁切换执行。
对于多核的CPU电脑说,真正的多线程并发是有的
4核CPU表示同一时间点上,可以真正的有4个进程并发执行
一个栈中,自上而下的顺序依次逐行执行
在Java语言中,堆内存和方法区内存共享
但是栈内存是独立的,一个线程一个栈
多线程的创建
实现线程的第一种方式
继承Thread类(java.lang.Thread)
然后重写run方法
再主方法中创建对象
start()方法的作用:启动一个分支线程,在JVM中开辟一个新的栈空间,在段代码任务完成之后,瞬间就结束了。这段代码的任务只是为了开启一个新的栈空间。只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
启动成功的线程就会自动调用run方法,并且run方法在分支栈的栈底部(压栈)
run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main方法是平级的
注意:
我们直接调用run方法是不会开启新的线程,不会分配新的栈,单线程,不能并发
我们调用start(),就是开启一个新的线程,会分配一个新的栈
下面是用run方法调用:
使用start()方法调用多线程
第二种方式Runnable接口
实现java.lang.Runnable接口,实现run方法
使用匿名内部类创建线程对象
public class ThreadTest04{
//采用匿名内部类创建线程
Thread t = new Thread(new Runnable(){
@Override
public void run(){
//业务代码
}
});
//启动线程
t.start();
}
}
两种方式对比
建议我们用第二种方式,因为第一种方式有局限性且第二种方式是面向接口编程。
Java是单继承多实现。所以使用第二种方式更加灵活。
多线程的生命周期图解
新建状态
就绪状态
运行状态
阻塞状态
死亡状态
![image-20211117161753868](F:\记录\PicGo\Java Web\image-20211117161753868.png)
在官方文档里,是有6种线程状态的
1、new:尚未开启线程处于此状态
2、Runnable :在Java虚拟机中执行的线程处于此状态(注意runnable状态不是正在运行状态 ,只是表示可以运行了,具体运行还需要看线程调度器来控制)
3、Blocked:被阻塞等待监视器锁定的线程处于此状态
4、waiting:正在等待另一个线程执行特定动作的线程处于此状态
5、Time_waiting:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态(超时等待)
6、Terminated:已退出的线程处于此状态
多线程的常用函数方法
获取线程的名字
设置线程的名字:xx.setName("xxxx")
获取线 程的名字:x.getName()
public class ThreadTest{
public static void main(String[] args){
//创建线程对象
Thread t1 = new MyThread();
//设置名字
t1.setName("One");
System.out.println(t1.getName());
t1.start();
}
}
class MyThread extends Thread{
public void run () {
for(int i = 0 ; i <100; i++){
System.out.println("分支:"+i);
}
}
}
获取当前线程对象
java.lang.Thread
类中
有个static Thread
方法,其中方法名是currentThread()
那么怎么获取当前线程对象?
Thread t = Thread.currentThread()
currentThread就是当前线程对象
Thread.currentThread()方法写在哪个线程里就是获取当前线程的对象。 如在main方法中就是获取main方法对象,写在其他分线程中就是获取分线程的对象
线程的Sleep方法
static void sleep(long millis)
1、静态方法:Thread.sleep(1000)
2、参数是毫秒
3、作用:让当前线程进入休眠,进入“阻塞”状态,放弃占有cpu时间片,让给其他线程使用
public class SleepMethod {
/*
* 关于线程的sleep方法
* static void sleep(long millis)
public class SleepMethod {
/*
* 关于线程的sleep方法
* static void sleep(long millis)
* 1、静态方法:Thread.sleep(1000)
* 2、参数是毫秒
* 3、作用:让当前线程进入休眠,进入“阻塞”状态,放弃占有cpu时间片,让给其他线程使用
* 4、Thread.sleep():该方法可以做到:每隔特定的时间,去执行一段特定的代码。
* */
public static void main(String[] args) {
System.out.println("Hello World!");
try {
//休眠2秒
Thread.sleep(1000 * 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello World!");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "------>" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}java
* 4、Thread.sleep():该方法可以做到:每隔特定的时间,去执行一段特定的代码。
* */
public static void main(String[] args) {
System.out.println("Hello World!");
try {
//休眠2秒
Thread.sleep(1000 * 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello World!");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "------>" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
关于sleep的面试题
public class SleepInterview {
public static void main(String[] args) {
Thread t1 = new MyThread();
t1.setName("T");
t1.start();
try {
//问题:这个代码会让线程t1进入休眠状态吗?
t1.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello!");
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "----->" + i);
}
}
}
不会,因为t1.start()方法在执行的时候还是会转换成:Thread.sleep()
这行代码的作用是:让当前线程进入休眠状态 ,也就是让main线程进入休眠
终止线程的睡眠方法
终止线程的具体方法名:
x.interrupt()
public class EndSleepMethod {
/*
* 终止休眠的方法
* 1、sleep休眠太久了,如果希望半道上醒来,我们就应该唤醒一个正在睡眠的线程
* 这个不是终断线程的执行,是终断线程的睡眠。
* */
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
t.setName("ttt");
t.start();
//希望5秒后,t线程醒来
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终断t线程的睡眠(这种终断睡眠的方式是依靠了Java异常的处理机制)
t.interrupt();
}
}
class MyRunnable2 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---->begin");
try {
Thread.sleep(1000 * 60 * 60);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->end");
}
}
强行终止线程的方法(stop) --不推荐使用
stop()
方法的是存在缺点的,是因为容易丢失数据。
因为这种方式是直接将线程杀死了,线程如果还没有保存数据,那么就会丢失数据。所以不建议使用
public class EndSleepMethod1 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable3());
t.setName("分线程");
t.start();
//模拟5秒
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后强行终止t线程
t.stop(); //已过时(不建议使用)
}
}
class MyRunnable3 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
合理强行终止线程的方法()--推荐使用
使用布尔值打一个标记
public class EndSleepMethod2 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t = new Thread(myRunnable);
t.setName("T");
t.start();
//模拟5s
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程
//我们想要什么时候终止t的执行,那么把标记改为false就OK了
myRunnable.run = false;
}
}
class MyRunnable implements Runnable {
//打一个布尔标记
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (run) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//终止当前线程
return;
}
}
}
}
线程的调度(了解)
概述
常见的线程调度模型有什么?
抢占式调度模型:
哪个线程的优先级比较高,那么抢到的cpu时间片的概率就高一些
Java采用的就是抢占式调度模型。
均分布调度模式:
平均分配Cpu时间片,每个线程占有的cpu时间片时间长度一样。平均分配,一切平等
线程调度的方法
实例方法:
void setPriority (int newPriority)
设置线程的优先级
int getPriority()
获取线程的优先级
注意:
- 最低优先级是1
MIN_PRIORITY
- 默认优先级是5
NORM_PRIORITY
- 最高优先级是10
MAX_PRIORITY
优先级比较高点的获取的CPU时间片的时间可能会多一点
静发方法:
static void yield()
让位方法
表示暂停当前正在执行的线程对象,并执行其他线程
注意:
1、yield()方法不是阻塞方法,只是表示当前线程让位,先给其他线程使用。
2、yield()的执行会让当前线程从“运行状态”回到“就绪状态”。
3、如果回到就绪状态之后,也还可能再次抢到时间片
public class YieldMethod {
public static void main(String[] args) {
Thread t1 = new Thread(new MyT());
t1.setName("1111");
t1.start();
for (int i = 1; i < 10000; i++) {
System.out.println(Thread.currentThread().getName()+ "----->" + i);
}
}
}
class MyT implements Runnable{
@Override
public void run() {
for (int i = 1; i < 10000; i++) {
//每100个让位一次
if (i %100 == 0){
Thread.yield(); //当前线程暂停一下,让给主线程
}
System.out.println(Thread.currentThread().getName()+ "----->" + i);
}
}
}
实例方法
void join()
合并线程
class M1 extends Thread{
public void run(){
M2 t = new M2();
t.join(); //当前线程进入阻塞,t线程执行,直到t线程结束,当前线程才执行
}
}
class M2 extends Thread{
}
public class JoinMethod {
public static void main(String[] args) {
Thread t = new Thread(new T());
t.setName("t");
t.start();
try {
t.join(); //当前线程阻塞,t线程执行直到结束
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main Over");
}
}
class T implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
线程的安全问题(重点)
因为以后我们的项目都是在服务器中,服务器已经将线程的定义与线程对象的创建,线程的启动等都已经实现完了。
我们就需要关注这些数据在多线程并发的环境下是否安全。
什么时候数据在多线程并发的环境下存在安全问题
什么时候数据在多线程并发的环境下会存在安全问题?
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
满足上诉三个条件就可能存在安全问题。
解决线程安全问题
线程排队执行即可。
也称为线程同步机制
专业术语:线程同步 实际就行线程不能并发了,必须排队执行
线程同步和异步的理解
异步编程模型:
线程a和线程b,各自执行各自的,a不管b,b不管a,谁也不需要等谁
多线程并发(效率较高)
异步就是并发
同步编程模型:
线程a和线程b,在线程a执行的时候,必须等待b线程执行结束,或者说b线程执行的时候,必须等待a线程执行结束
两个线程之间发生了等待关系,这就是同步编程模型。
效率低。
同步就是排队
具体实现Synchronized() - 排他锁
Account
public class Account {
//账户
private String actno;
//余额
private double balance;
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款方法
public void withdraw(double money) {
//取款之前的余额
double balance = this.balance;
//取款之后
double after = balance - money;
//更新余额
this.setBalance(after);
}
}
AccountThread
public class AccountThread extends Thread {
//两个线程必须共享一个账户对象
private Account act;
//通过构造方法传递过来账户对象
public AccountThread(Account act) {
this.act = act;
}
@Override
public void run() {
synchronized (this) {
//run方法的执行表示取款操作
//取款5k
double money = 5000;
act.withdraw(money);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("成功:" + "余额还有" + act.getBalance());
}
}
}
public class Test {
public static void main(String[] args) {
Account act = new Account("001", 10000);
Thread t1= new AccountThread(act);
Thread t2= new AccountThread(act);
t1.setName("1");
t2.setName("2");
t1.start();
t2.start();
}
}
注意:
synchronized(){
//( )里面的对象是共享对象,通常是this,也可以是其他的 。只要是同一个对象就可以了
}
也可使用同步方法:
这种方式共享对象只能是this了,还有有个缺点就是会无故扩大同步的范围,导致程序的执行效率降低。
优点:代码写的少了。
如果我们共享的对象就是this,并且需要同步的代码块是整个方法体,那么就建议使用这个方法。
public class Account {
//账户
private String actno;
//余额
private double balance;
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款方法
public synchronized void withdraw(double money) {
//取款之前的余额
double balance = this.balance;
//取款之后
double after = balance - money;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新余额
this.setBalance(after);
}
}
Java的三大变量(重要知识点)
- 实例变量:在堆中
- 静态变量:在方法区
- 局部变量:在栈中
以上三大变量中,局部变量永远不会存在线程安全问题。
因为局部变量不共享---(因为一个线程一个栈)
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
具体实现续集
如果使用局部变量的话,建议使用StringBuilder
因为局部变量不存在线程安全问题
如果选择StringBuffer效率比较低
ArrayList是非线程安全的
Vector是线程安全的
HashMap HashSet是非线程安全的
HashTable是线程安全的
总结
Synchronize有三种方法:
- 同步代码块
synchronized(线程共享对象){
同步代码块
}
第二种方法:
- 在实例方法上使用synchronized
表示共享对象一定是this,并且同步代码块是整个方法体
三种:
- 在静态方法上使用synchronized (保证静态变量的安全)
表示类锁
类锁永远只有1个
对象锁:1个对象1把锁,100个对象100把锁
类锁:100个对象,也可能只是1把锁
面试题:
//面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
/*
* 不需要,因为下面的doOther方法没有synchronized方法
* */
public class Exam01 {
public static void main(String[] args) {
MyClass mc = new MyClass();
Thread t1 = new MyThread(mc);
Thread t2 = new MyThread(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000); //作用:保证t1线程先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread {
private MyClass mc;
public MyThread(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")) {
mc.doSome();
}
if (Thread.currentThread().getName().equals("t2")) {
mc.doOther();
}
}
}
class MyClass {
public synchronized void doSome() {
System.out.println("begin");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Over");
}
public void doOther() {
System.out.println("Other begin");
System.out.println("Other Over");
}
}
面试二:
//面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
//需要,因为doOther方法也有synchronized来同步,而doSome和doOther都是用同步方法,而锁对象这是本类的this
public class Exam02 {
public static void main(String[] args) {
MyClass mc = new MyClass();
Thread t1 = new MyThread(mc);
Thread t2 = new MyThread(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000); //作用:保证t1线程先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread {
private MyClass mc;
public MyThread(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")) {
mc.doSome();
}
if (Thread.currentThread().getName().equals("t2")) {
mc.doOther();
}
}
}
class MyClass {
public synchronized void doSome() {
System.out.println("begin");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Over");
}
public synchronized void doOther() {
System.out.println("Other begin");
System.out.println("Other Over");
}
}
面试三:
//面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
//需要,因为静态方法是类锁,不管创建了几个对象,类锁只有一把
public class Exam02 {
public static void main(String[] args) {
MyClass mc = new MyClass();
Thread t1 = new MyThread(mc);
Thread t2 = new MyThread(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000); //作用:保证t1线程先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread {
private MyClass mc;
public MyThread(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")) {
mc.doSome();
}
if (Thread.currentThread().getName().equals("t2")) {
mc.doOther();
}
}
}
class MyClass {
public synchronized static void doSome() {
System.out.println("begin");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Over");
}
public synchronized static void doOther() {
System.out.println("Other begin");
System.out.println("Other Over");
}
}
开发中应该怎么解决线程安全问题
一上来就选择线程同步吗?synchronized()?
不是,synchronized会让程序的执行效率降低,用户体验不好。并且要降低用户的吞吐量,用户体验极差。所以在不得已的情况下再选择线程同步机制。
解决方法:
第一种方法:尽量使用局部变量代替实例变量和静态变量。
第二种方法:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应一个对象,100个线程对应100个对象)
第三种方法:如果不能使用局部变量,对象也不能创建多个,这个时候也只能选择synchronized,线程同步机制
死锁的概述
比如线程a 和线程b
线程a要先锁住o1 再锁o2
线程b要先锁o2,再锁o1
就会出现死锁。
因为a锁o2的时候被b锁住了
b锁o1的时候被a锁住了
死锁:不出现异常,也不会出现错误,程序一直停在那里。
死锁的代码演示
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
//t1和t2两个线程共享o1,o2
Thread t1 = new MyThread(o1, o2);
Thread t2 = new MyThread2(o1, o2);
}
}
class MyThread extends Thread {
Object o1;
Object o2;
public MyThread(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
}
}
}
}
class MyThread2 extends Thread {
Object o1;
Object o2;
public MyThread2(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
}
}
}
}
守护线程
守护线程可以理解为垃圾回收器
Java语言中有两个线程:
- 用户线程:主线程等等
- 守护线程:也称后台线程。
其中守护线程具有代表性的就是:垃圾回收线程(守护线程)
守护线程的特点
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程也将自动结束。
注意:主线程main方法是一个用户线程
模拟一个守护线程
xx.setDaemon(true)
public class DaemonThread {
public static void main(String[] args) {
Thread b = new B();
b.setName("备份线程");
b.setDaemon(true);
b.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class B extends Thread {
@Override
public void run() {
int i = 0;
while (true) {
System.out.println(Thread.currentThread().getName() + "--->" + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
定时器
间隔特定的时间,执行特定的程序。
在实际的开发中,每隔一段特定的时间执行一段特定的程序,这种需求是很常见的,那么在Java中其实可以采用多种方式实现:
可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务 (原始方法)
在Java的类库中已经写好了一个定时器:Java.util.Timer,可以直接拿来用。但是这种方法在目前的开发中也很少用,因为现在很多高级框架都是支持定时任务的
实现线程的第三种方式
FutureTask方式,实现callable接口(JDK1.8新特性)
这种方式实现的线程可以获取线程的返回值
之前的两种方式不能获取线程的返回值
wait和notify方法
关于Object类中的wait和notify方法(生产者和消费者模式!)
第一:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类自带的
wait方法和notify方法不是通过线程对象调用
如:t.wait(),t.notify()。。是不对的
第二:wait()方法作用?
Object o = new Object();
o.wait();
表示让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
直到最终调用o.notify()
方法才能被唤醒
notify()方法可以让正在wait的线程醒来。
第三:notify()方法的作用?
Object o = new Object();
o.notify();
表示:
唤醒正在o对象上等待的线程。
还有一个notifyAll( )方法:
这个方法是唤醒o对象上处于等待的所有线程。
wait方法和notify方法建立在synchronized线程同步的基础之上
o.wait0方法会让正在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁
o.notify()方法只会通知,不会释放之前占有的o对象的锁。