多线程的概述
-
什么是进程?什么时线程?
进程是一个应用程序(1个进程是一个软件)。
线程是一个进程中的执行场景/执行单元。
一个进程可以启动多个线程
-
进程和线程是什么关系?
阿里巴巴:进程
公司的每个员工就是阿里巴巴的每个线程
京东:进程
强东:京东的一个进程
妹妹:京东的一个线程
进程可以看作是现实生活当中的公司。
线程可以看作是公司当中的某个员工。
注意:
进程A和进程B的内存独立不共享
进程A和进程B在java语言中
线程A和线程B,堆内存和方法区共享内存
但是栈内存独立,一个线程一个栈。
j假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
火车站可以看作是一个进程。
火车站中的每一个售票窗口可以看作是一个线程。
我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你,所以多线程并发可以提高效率。
java中之所以有多线程机制,目的就是为了提高程序的处理效率
多线程并发
什么是真正的多线程并发?
t1线程执行t1的。
t2线程执行t2的。
t1不会影响t2,t2也不会影响t1,这就叫做多线程并发。
单核CPU可以做到真正的多线程并发吗?
对于多核的CPU电脑来说,真正的多线程并发是没问题的,
4核CPU表示同一个时间点,可以真正的有4个进程并发执行。
单核CPU表示只有一个大脑:
不能够做到真正的多线程并发, 但是可以做到给人一种“多线程并发”的感觉
对于单核的CPU来说,在某个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给人的感觉是:多个事情在同时做。
java实现多线程有两种方式
第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。
public class User extends Thread {
@Override
public void run() {
//编写程序,这段程序运行在分支线程中(分支栈)
for (int i = 0; i <100 ; i++) {
System.out.println("分支线程"+i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//这里是main方法,这里的代码属于主线程,在主栈中运行
//新建一个分支对象
User user=new User();
//启动线程
/*
* start()的作用:启动一个分支线程,在jvm中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了
* 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,
* 线程就启动成功了。启动成功的线程会自动调用run方法,并且在分支栈的栈底部(压栈)。
* run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
* */
user.start(); //并发执行
for (int i = 0; i < 100; i++) {
System.out.println("主线程+"+i);
}
}
}
注意:方法体中当中的代码永远都是自上而下的顺序依次逐行执行的。
run和start的区别
线程的run
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-11uoEsCO-1596290691138)(D:\pic\Typora-pic\image-20200731122253393.png)]
线程的start
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kOXUxqpM-1596290691139)(D:\pic\Typora-pic\image-20200731123128388.png)]
第二种方式:编写一个类,实现Runnable接口
public class RunTest {
public static void main(String[] args) {
//创建一个可运行的对象
Test01 test01=new Test01();
//将可运行的对象封装成一个线程对象
Thread thread=new Thread(test01);
//启动线程
thread.start();
for (int i = 0; i <100 ; i++) {
System.out.println("主"+i);
}
}
}
//这并不是一个线程类,是一个可执行的类。它还不是一个线程
class Test01 implements Runnable {
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println("Runnable"+i);
}
}
}
第二种方式比较常用:因为是面向接口编程,一个类实现了接口还可以继承。
线程的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F3j1zxFY-1596290691140)(D:\pic\Typora-pic\image-20200731145940702.png)]
关于线程对象的生命周期?
新建状态
就绪状态
运行状态
阻塞状态
死亡状态
线程的方法
获取当前线程对象
获取线程对象的名字
设置线程对象的名字
默认线程的名字
package com.aaa.thread.xiancheng;
/*
* 1. 怎么获取当前线程对象
* static Thread currentThread() 返回对当前正在执行的线程对象的引用。
* 2. 获取线程对象的名字 线程对象.getName();
* 3. 修改线程对象的名字 线程对象.setName("线程一");
* 4. 当线程没有设置名字时,默认的名字的规律?
* Thread-0
* Thread-1
* */
public class ThreadTest01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//设置线程对象的名字
myThread.setName("分支线程1");
//获取线程对象的名称
String name = myThread.getName();
//System.out.println(name);
//currentThread()当前线程
//这个代码出现在main方法中,所以当前线程就是主线程
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
//启动线程
myThread.start();
MyThread thread1=new MyThread();
thread1.setName("分支2");
thread1.start();
}
}
//线程对象
class MyThread extends Thread{
@Override
public void run() {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
线程的 sleep方法
静态方法
参数是毫秒
作用:让当前线程进入休眠,进入阻塞状态,放弃 占有CPU时间片,让给其他线程使用。
package com.aaa.thread.xiancheng;
/*
* 线程的阻塞状态
* */
public class ThreadTest02 {
public static void main(String[] args) {
MyThread2 t2=new MyThread2();
t2.setName("test2");
//获取线程的名字
System.out.println(t2.getName());
//启动线程
t2.start();
//使当前线程阻塞
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程对象
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
class MyThread2 extends Thread{
@Override
public synchronized void start() {
System.out.println("分支线程");
}
}
sleep的面试题
package com.aaa.thread.xiancheng;
/*
* sleep的面试题
* */
public class ThreadTest03 {
public static void main(String[] args) throws InterruptedException {
//创建线程对象
Thread t3=new MyThread3();
t3.setName("T");
t3.start();
//调用sleep
//问题:这行代码会让线程T进入休眠状态吗? 不会
t3.sleep(1000*5);
//5秒之后
System.out.println("Hello World");
}
}
class MyThread3 extends Thread{
@Override
public synchronized void start() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
终止线程的睡眠
package com.aaa.thread.xiancheng;
/*
* sleep睡眠太久了,如果希望提前醒来, 如何提前终止睡眠?
*
*
* */
public class ThreadTest04 {
public static void main(String[] args) {
Thread thread=new Thread(new MyThread4());
thread.setName("t");
thread.start();
//希望5秒之后,t线程醒来(5秒之后主线程的开始执行)
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断 t线程的睡眠 中断这个线程。(这种中断方式是依靠了java的异常处理机制
// (InterruptedException(中断异常)))
thread.interrupt();
}
}
class MyThread4 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+"begin");
//子类不能抛出比父类更宽泛的异常
try {
Thread.sleep(1000*60*60*24);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+"end");
}
}
怎么合理的终止一个线程的执行。这种方式很常用
package com.aaa.thread.xiancheng;
/*
* 强行终止进程
* */
public class ThreadTest05 {
public static void main(String[] args) {
MyThread5 thread5=new MyThread5();
Thread thread=new Thread(thread5);
thread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread5.run=false;
}
}
class MyThread5 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;
}
}
}
}
线程安全(最重要)
关于多线程并发环境下,数据的安全问题。
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了,这些代码不需要我们编写
最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的时这些数据在多线程并发下是否是安全的。
线程不安全的情况?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qNzN8mrA-1596290691142)(D:\pic\Typora-pic\image-20200731182229804.png)]
三个条件:
- 多线程并发。
- 有共享数据。
- 共享数据有修改的行为。
满足以上3个条件之后,就会窜在线程安全问题。
怎么解决线程安全问题?
线程排队执行。(不能并发)
用排队执行解决线程安全问题。
这种机制被称为:线程同步机制(线程排队)。
线程同步就是线程排队了,线程排队就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事。
同步和异步
同步编程模型:
线程a和线程b,在线程a执行的时候,必须等待b执行结束,或者说在b执行的时候,必须等待a执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行。
异步编程模型:
线程a和线程b,各自执行各自的,线程a不管b,b也不管a,谁也不需要等谁,这种编程模型叫做:异步编程模型,
其实就是:多线程并发(效率高)
代码实现例子(异步编程模型)(异步就是并发)
package com.aaa.thread.threadlist;
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 balance){
//取款前的余额
double before=this.getBalance();
//取款后的余额
double after=before-balance;
//更新
this.setBalance(after);
}
}
package com.aaa.thread.threadlist;
public class AccountThread implements Runnable {
private Account account;
public AccountThread(Account account) {
this.account = account;
}
@Override
public void run() {
account.withdraw(5000);
System.out.println("用户"+account.getActno()+"余额为:"+account.getBalance());
}
}
测试
package com.aaa.thread.threadlist;
public class Test {
public static void main(String[] args) {
Account account=new Account("acc-001",10000);
Runnable at=new AccountThread(account);
Runnable at1=new AccountThread(account);
Thread thread=new Thread(at);
Thread thread1=new Thread(at1);
thread.setName("t1");
thread1.setName("t2");
thread.start();
thread1.start();
}
}
同步线程机制
线程同步机制的语法是:
synchronized() {
//线程同步代码块
}
synchronized后面小括号中传的这个数据是非常关键的。
这个数据必须是多线程共享的数据。才能达到多线程排队。
()中写什么?
看你想让哪些线程同步。
假设你有a b c d 4个线程,
你只希望a b c 排队,d不需要排队。怎么办?
你一定要在()中写一个a b c 共享的对象。而这个对象对于d来说不是共享的。
package com.aaa.thread.threadlist;
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 balance){
synchronized (this){
//取款前的余额
double before=this.getBalance();
//取款后的余额
double after=before-balance;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新
this.setBalance(after);
}
}
}
package com.aaa.thread.threadlist;
public class AccountThread implements Runnable {
private Account account;
public AccountThread(Account account) {
this.account = account;
}
@Override
public void run() {
account.withdraw(5000);
System.out.println(Thread.currentThread().getName()+"用户"+account.getActno()+"余额为:"+account.getBalance());
}
}
package com.aaa.thread.threadlist;
public class Test {
public static void main(String[] args) {
Account account=new Account("acc-001",10000);
Runnable at=new AccountThread(account);
Runnable at1=new AccountThread(account);
Thread thread=new Thread(at);
Thread thread1=new Thread(at1);
thread.setName("t1");
thread1.setName("t2");
thread.start();
thread1.start();
}
}
java中的三大变量
实例变量:在堆中
静态变量:在方法区中
局部变量:在栈中
以上三大变量中:
局部变量永远都不会存在线程安全问题。
因为局部变量不共享(一个线程一个栈)。
局部变量在栈中。所以局部变量永远都不会共享。
实例变量在堆中,堆只有一个。
静态变量在方法区中,方法区中只有一个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
synchronized的三种写法:
第一种:同步代码块
灵活
synchronized(线程共享对象){
同步代码块
}
第二种:在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法
第三种:在静态方法上使用synchronized
表示找类锁
类锁永远只有一把。
就算创建了100个对象,那类锁也只有一把。
对象锁:1个对象1把锁,1000个对象100把锁。
类锁:100个对象,也可能只是一把类锁
守护线程
java语言中线程分为两大类:
一类是:用户线程
一类是:守护线程(后台线程)
其中具有代表性的就是垃圾回收线程(守护线程)。
# 守护线程的特点:
- 一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束
`注意:主线程main方法是一个用户线程`
## 守护线程用在什么地方呢?
- 每天00:00的时候系统数据自动备份。
- 这个需要使用到定时器,并且我们可以将定时器设值为守护线程。一直在那里看着,每到00:00的时候就备份一次。
- 如果用户线程结束了,守护线程自动退出,没有必要进行数据备份了
### 设置 setDaemon(true)将此线程标记为守护线程
setDaemon(boolean on)
将此线程标记为 守护线程
即当用户线程一结束那个守护进程无论是不是死循环都会结束。
定时器
定时器的作用:
间隔特定的时间,执行特定的程序。
## 在实际开发中,每隔多久执行一段特定的程序,这种需要很常见,在java中可以采用多种方式实现。
- 可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始定时器。
`在java的类库中已经写好了定时器:java.util.Timer可以直接拿来用。`
不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
在实际的开发中,目前使用较多的spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务
基本实现
package com.aaa;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class Ding {
public static void main(String[] args) {
//创建定时器
Timer timer = new Timer();
TimerTasks timerTasks = new TimerTasks();
timer.schedule(timerTasks,new Date(),10*1000);
}
}
class TimerTasks extends TimerTask{
@Override
public void run() {
System.out.println("运行");
}
}
实现线程的第三种方式
实现Callable接口。(jdk8新特性)
这种方式实现的线程可以获取线程的返回值(一开始的那两种不能获取返回值)
缺点:效率低
package com.aaa;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class FutureTaskTest {
public static void main(String[] args) {
//创建一个未来任务类
FutureTask futureTask=new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
int a =100;
int b=200;
Thread.sleep(1000);//睡觉了
return a+b;
}
});
//创建线程对象
Thread th=new Thread(futureTask);
th.start();
//获取线程的返回结果
try {
Object o = futureTask.get();
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//main方法这里的线程要想执行必须等待get()方法的执行结束。
System.out.println("ttt");
}
}
关于Object类中的wait和notify方法。(生产者和消费者模式)
第一:wait和notify方法不是线程对象的方法, 是java中任何一个java对象都有的方法,因为这两个方式是Object类自带的方法。
wait方法和notify方法不是通过线程对象调用的。
## wait()方法的作用?
```java
Object o=new Object();
o.wait();
表示:
让正在o对象上活动的线程进入等待状态,无期限等待,
直到被唤醒为止。
```
## notify()方法作用?
Object o=new Object();
o.notify();
表示:
唤醒正在o对象上等待的 线程
还有一个notifyAll()方法:
这个方法是唤醒o对象上处于等待的所有线程
package com.aaa.production;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/*
*
* 实现生产和消费者
* 一个仓库当作共享对象
* 生产者和消费者必须是线程安全的,生产出的东西需要和消费的东西的个数一致
* 有一个生产者和一个消费者
* 生产者和消费者共享一个对象
* 使用List集合当作仓库
* 仓库的数量为1
* 当仓库的数量为1时仓库就满了
* 当仓库的数量为0时仓库为空
* */
@SuppressWarnings("all")
public class Test01 {
public static void main(String[] args) {
//创建list集合
List list=new ArrayList();
//创建线程对象 两个线程共享一个list集合
Thread thread=new Thread(new Product(list));
Thread thread1=new Thread(new Consumption(list));
//为线程对象设置名称
thread.setName("生产者线程");
thread1.setName("消费者线程");
//启动线程
thread.start();
thread1.start();;
}
}
/*
* 生产者 一直生产
* */
@SuppressWarnings("all")
class Product implements Runnable{
List list;
public Product (List list){
this.list=list;
}
@Override
public void run() {
while (true) {
synchronized (list) {
//判断仓库的数量是否为空
// 如果仓库的数量不为空则代表仓库满了
// 我们就需要等待被消费
if (list.size() > 0) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果仓库没有满,那么就进行给仓库增加
Object object = new Object();
list.add(object);
System.out.println(Thread.currentThread().getName()+object);
//增加完之后就需要唤醒等待的线程
list.notifyAll();
}
}
}
}
/*
* 消费者 一直消费
* */
@SuppressWarnings("all")
class Consumption implements Runnable{
List list;
public Consumption (List list){
this.list=list;
}
@Override
public void run() {
while (true) {
synchronized (list) {
if (list.size() == 0) {
//仓库中已经空了
//消费者的线程等待,释放list集合的锁
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object remove = list.remove(0);
System.out.println(Thread.currentThread().getName()+remove);
list.notifyAll();
}
}
}
}