一、介绍
进程:在内存中执行的应用程序
线程:是进程中最小的执行的单元
线程作用:负责当前进程中程序的运行,一个进程中至少有一个线程,一个进程中还可以有多个线程,这样的应用程序就称之为多线程程序
简单理解:一个功能就需要一条线程去执行
使用场景:软件中的耗时操作->拷贝文件,加载大量的资源
所有的聊天软件
所有的后台服务器
一个线程可以做一件事,我们就可以同时做多件事,提高了CPU的利用率
二、并发与并行
并行:在同一时刻,有多个执行在多个CPU上同时执行(好比多个人做不同的事情)
比如:多个厨师在炒多个菜
并发:在同一时刻,有多个指令在单个CPU上交替执行
比如:一个厨师在炒多个菜
细节:
- 之前CPU是单核,但是在执行多个程序的时候好像实在同时进行,原因是CPU在多个线程之间做高速切换
- 现在的CPU都是多核多线程的比如2核4线程那么CPU可以同时运行4个线程,此时不同切换,但是如果多了,CPU就要切换了,所以现在CPU在执行程序的时候并发和并行都存在
CPU调度
- 分时调度:指的是让所有的线程轮流获取CPU使用权,并且平均分配每个线程占用CPU的时间片
- 抢占式调度:多个线程轮流抢占CPU使用权,哪个线程先抢到了哪个线程先执行,一般都是优先级高的先抢到CPU使用权的几率大我们Java程序就是抢占调度
主线程介绍
主线程:CPU和内存之间开辟的专门为main方法服务的线程
三、创建线程的方式
1.第一种方式_extends Thread
(1)定义一个类,继承Thread
(2)重写run方法,在run方法中设置线程任务(所谓的线程任务指的是此线程要干的具体的事,具体执行的代码)
(3)创建自定义线程类的对象
(4)调用Thread中的start方法开启线程,jvm自动调用run方法(不能直接调用)
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("MyThread....执行了"+i);
}
}
}
public class Test01 {
public static void main(String[] args) {
//创建线程对象
MyThread t1 = new MyThread();
//调用Thread中的start方法开启线程,jvm自动调用run方法
t1.start();
//t1.run(); 如果调用run就一定先走自己的哪个
for (int i = 0; i < 10; i++) {
System.out.println("main线程........执行了"+i);
}
}
}
注意:同一个线程对象不能连续调用多次start方法,如果想要再次调用start,那么咱们就new一个新的线程对象
(1)Thread类中的方法
Thread类中的方法
- Void start()->开启线程,jvm自动调用run方法
- Void run()->设置线程任务,这个run方法就是Thread重写的接口Runable中的run方法
- String getName()->获取线程名字
- Void setName(String name)->给线程设置名字
- Static Thread currentThread()->获取正在执行的线程对象(此方法在那个线程中使用,获取的就是哪个线程对象)
- Static void sleep(long millis)->线程睡眠,超时后会自动醒来继续执行传递的是毫秒值
问题:为什么再重写的run方法中只能try不能throws
原因:继承的Thread中的run方法没有抛异常,所以在子类中重写run方法后就不能抛只能try
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//线程睡眠
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName()+"....执行了"+i);
}
}
}
public class Test01 {
public static void main(String[] args) throws InterruptedException {
//创建线程对象
MyThread t1 = new MyThread();
//给线程设置名字
t1.setName("名");
//调用Thread中的start方法开启线程,jvm自动调用run方法
t1.start();
//t1.run(); 如果调用run就一定先走自己的哪个
MyThread t2 = new MyThread();
t2.start();
for (int i = 0; i < 10; i++) {
Thread.sleep(1000L);
System.out.println(Thread.currentThread().getName()+"线程........执行了"+i);
}
}
}
A.线程优先级
void setPriority(int newPriority)->设置线程优先级,优先级越高的线程,抢到的CPU使用权几率越大,但是不是每次都先抢到
int getPriority()->获取线程优先级
public class MyThread01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了....."+i);
}
}
}
public class Test02 {
public static void main(String[] args) {
MyThread01 t1 = new MyThread01();
t1.setName("名");
MyThread01 t2 = new MyThread01();
t2.setName("怡");
/*
*
* 获取两个线程的优先级
* MIN_PRIORITY=1 最小优先级 1
* NORM_PRIORITY=5 默认优先级 5
* MAX_PRIORITY=10 最大优先级 10
*
* */
// System.out.println(t1.getPriority());
// System.out.println(t2.getPriority());
//设置优先级
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
B.守护线程
void setDaemon(boolean on)->设置守护线程,当非守护线程执行完毕,守护线程就要结束了,但是守护线程也不是立马结束,当非守护线程结束之后,系统会告诉守护线程结束了,你也结束把再告知的过程中守护线程会执行,只不过执行到一半就结束了
public class MyThread01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了....."+i);
}
}
}
public class MyThread02 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"执行了.."+i);
}
}
}
public class Test02 {
public static void main(String[] args) {
MyThread01 t1 = new MyThread01();
t1.setName("名");
MyThread02 t2 = new MyThread02();
t2.setName("怡");
//将t2设置成守护线程
t2.setDaemon(true);
t1.start();
t2.start();
}
}
C.礼让线程
static void yield()->礼让线程,让当前线程让出CPU使用权
void join()—>插入线程或者叫插队线程
场景说明:如果有两个线程,可能执行一会线程A,在执行一会线程B,或者可能线程A执行完毕了线程B在执行那么我们能不能让两个线程尽可能的平衡一点->尽量让两个线程交替执行
注意:只是尽可能的平衡不是绝对的你来我往,有可能是A线程执行了,但是回头A又抢到CPU的使用权了
public class MyThread01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了....."+i);
Thread.yield();
}
}
}
public class Test02 {
public static void main(String[] args) {
MyThread01 t1 = new MyThread01();
t1.setName("名");
MyThread01 t2 = new MyThread01();
t2.setName("怡");
//礼让线程
t1.start();
t2.start();
}
}
2.创建多线程的第二种方式_实现Runnable接口
(1)创建类,实现Runnable接口
(2)重写run设置线程任务
(3)利用Thread类的构造方法:Thread(Runnable target),创建Thread(线程对象)将自定义的类当参数传递到Thread构造中->这一步是让我们自己定义的类成为一个真正的线程类对象
(4)调用Thread中的start方法,开启线程,jvm自动调用run方法
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"..执行了"+i);
}
}
}
public class Test01 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
/*
*
* */
Thread t1 = new Thread(myRunnable);
t1.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"..执行了"+i);
}
}
}
两种实现多线程的方式区别
- 继承Thread:继承只支持单继承,有继承的局限性
- 实现Runnable:没有局限性,Mythread extends Fu implements Runnable
(1)匿名内部类的创建多线程
严格意义来说,匿名内部类方式不属于创建多线程方式其中之一,因为匿名内部类形式建立在实现Runnable接口的基础上完成的
public class Test02 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"..执行了"+i);
}
}
},"名").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"..执行了"+i);
}
}
}).start();
}
}
(2)线程安全
什么时候发生:当多个线程访问一个资源时,导致数据有问题
线程安全问题->线程不安全的代码
public class MyTicket implements Runnable {
//定义票
int ticket=100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
}
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket, "名");
Thread t2 = new Thread(myTicket, "怡");
Thread t3 = new Thread(myTicket, "杨");
t1.start();
t2.start();
t3.start();
}
}
原因:CPU在多个线程之间做高速切换
A.解决线程安全问题的第一种方式(使用同步代码块)
格式
Synchronized(任意对象){
线程可能出现不安全的代码
}
任意对象:就是我们的锁对象
执行:一个线程拿到锁以后,会进入到同步代码块中执行,在此期间,其他线程拿不到锁,就进不去同步代码块,需要在同步代码块外面等待排队,需要等着执行的线程执行完毕,出了同步代码,相当于释放锁了,等待的线程才能抢到锁才能进入到同步代码块中执行
public class MyTicket implements Runnable {
//定义票
int ticket=100;
//任意对象
Object obj=new Object();
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (obj) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
}
}
}
}
B.解决线程安全问题第二种方式:同步方法
普通同步方法_非静态
格式:
修饰符 synchronized 返回值类型 方法名(参数){
方法体
Return 结果
}
默认锁:this
静态同步方法
格式:
修饰符 static synchronized 返回值类型 方法名(参数){
方法体
Return 结果
}
默认锁:class对象
public class MyTicket implements Runnable {
//定义票
int ticket=100;
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// method01();
method02();
}
}
private void method02() {
synchronized (this){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
}
}
public synchronized void method01(){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
}
}
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
System.out.println(myTicket);
Thread t1 = new Thread(myTicket, "名");
Thread t2 = new Thread(myTicket, "怡");
Thread t3 = new Thread(myTicket, "杨");
t1.start();
t2.start();
t3.start();
}
}
(3)死锁(锁嵌套就有可能产生死锁)
概念:指的是两个或者两个以上的线程在执行的过程中由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况称之为死锁
public class DieLock implements Runnable {
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (LockA.lockA) {
System.out.println("if....lockA");
synchronized (LockB.lockB) {
System.out.println("if.....lockB");
}
}
} else {
synchronized (LockB.lockB) {
System.out.println("else...lockB");
synchronized (LockA.lockA) {
System.out.println("else....lockA");
}
}
}
}
}
public class LockA {
public static LockA lockA=new LockA();
}
public class LockB {
public static LockB lockB=new LockB();
}
public class Test01 {
public static void main(String[] args) {
DieLock dieLock = new DieLock(true);
DieLock dieLock1 = new DieLock(false);
new Thread(dieLock).start();
new Thread(dieLock1).start();
}
}
3.实现多线程的第三种方式_实现Callable接口
概述:是一个接口
方法:
V call()->设置线程任务的,类似于run方法
Cll方法和run方法的区别:
相同点:都是设置线程任务
不同点:
Call方法有返回值,而且有异常可以throws
Run方法没有返回值,而且有异常不可以throws
<v>:
<v>叫做泛型
泛型:用于指定我们操作什么类型的数据,<>中只能写引用数据类型,如果泛型不写,默认是Object类型数据
实现Callable接口时,指定泛型是什么类型的,重写的call方法返回值就是什么类型的
获取call方法返回值:FutureTask<V>
FutureTask<V>是实现了一个接口:Future<V>
FutureTask<V>中有一个方法:
V get()->获取call方法的返回值
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "名和怡999";
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<String> FutureTask = new FutureTask<>(myCallable);
//创建Thread对象
Thread t1 = new Thread(FutureTask);
t1.start();
//调用get方法获取call方法返回值
System.out.println(FutureTask.get());
}
}
4.线程池_实现多线程方式四
问题:来一个线程任务,就需要创建一个线程对象去执行,用完还要销毁线程对象,如果任务多了,就需要频繁创建线程对象和销毁线程对象,这样会消耗内存资源,所以我们就想线程对象能不能循环利用,用的时候直接拿线程对象,用完还回去
(1)如何创建线程池对象:工具类->Executors
(2)获取线程池对象:Executors中的静态方法
static ExecutorService newFixedThreadPool(int nthreads)
参数:指定线程池中最多创建的线程对象条数
返回值ExecutorService是线程池,用来管理线程对象
(3)执行线程任务:ExecutorService中的方法
(4)submit方法的返回值:Future接口
(5)ExecutorService中的方法
void shutdown()启动有序关闭,关闭先前提交的任务将被执行,但不会接受任何新任务
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 1;
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+".....执行了");
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test01 {
public static void main(String[] args) {
//创建线程对象
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(new MyRunnable());
es.submit(new MyRunnable());
es.submit(new MyRunnable());
es.shutdown();//关闭线程池对象
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Integer> future = es.submit(new MyCallable());
System.out.println(future.get());
}
}
需求:创建两个线程任务,一个线程任务完成1-100的和,一个线程任务返回一个字符串
import java.util.concurrent.Callable;
public class MyString implements Callable<String> {
@Override
public String call() throws Exception {
return "我吃饭了";
}
}
import java.util.concurrent.Callable;
public class MySum implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i <=100; i++) {
sum+=i;
}
return sum;
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(2);
Future<String> f1 = es.submit(new MyString());
Future<Integer> f2 = es.submit(new MySum());
System.out.println(f1.get());
System.out.println(f2.get());
}
}
四、定时器_Timer
轮播图,登录网站打广告都有用到定时器
概述:定时器
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class Demo01Timer {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("今天吃什么饭");
}
},new Date(),2000L);
}
}
五、Lock锁
Lock对象的介绍和基本使用
概述:Lock是一个接口
实现类:ReentrantLock
方法:
Lock()获取锁
Unlock()释放锁
Synchronized:不管是同步代码块还是同步方法都需要在结束一对{}之后,释放锁对象
Lock:是通过两个方法控制需要被同步的代码更灵活
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyTicket implements Runnable {
//定义票
int ticket=100;
//创建lock
Lock lock=new ReentrantLock();
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
//获取锁
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
//释放锁
lock.unlock();
}
}
}
}
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket, "名");
Thread t2 = new Thread(myTicket, "怡");
Thread t3 = new Thread(myTicket, "杨");
t1.start();
t2.start();
t3.start();
}
}
六、多线程_等待唤醒
等待唤醒
案例分析(线程之间的通信)
要求:一个线程生产,一个线程消费,不能连续生产,不能连续消费->等待唤醒机制(生产者,消费者)(线程之间的通信)
方法 | 说明 |
---|---|
void wait() | 线程等待,等待的过程中线程会释放锁,需要被其他线程调用notify()方法将其唤醒,重新抢锁执行 |
void notify() | 线程唤醒,一次唤醒一个等待线程,如果有多条线程等待,则随机唤醒一条等待线程 |
void notifyAll() | 唤醒所有等待线程 |
/*
* count和flag可以定义成包装类
*但是要记得给count和flag手动赋值
*不然对于本案例来说,容易出现空指针异常
*
* */
public class BaoZiPu {
//代表包子count
private int count;
//代表包子是否有包子的flag
private boolean flag;
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
/*
* 改造成消费包子方法
* 直接输出count
* */
/* public void getCount() {
System.out.println("消费了..............第"+count+"包子");
}
*/
public synchronized void getCount() {
//判断flag是否为false,如果时false证明没有包子,消费线程等待
while (this.flag==false){
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//如果flag为true证明有包子开始消费
System.out.println("消费了..............第"+count+"包子");
//改变flag状态为false证明生产完了没有包子了
this.flag=false;
//唤醒所有等待线程
this.notifyAll();
}
/*
* 改造成生产包子count++
* */
/*public void setCount() {
count++;
System.out.println("生产了...第"+count+"个包子");
}*/
public synchronized void setCount() {
//判断flag是否为true,如果时true证明有包子,生产线程等待
while (this.flag==true){
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//如果flag为flag证明没有包子开始生产
count++;
System.out.println("生产了......第"+count+"包子");
//改变flag状态为true证明生产完了有包子了
this.flag=true;
//唤醒所有等待线程
this.notifyAll();
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class Consumer implements Runnable{
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
// BaoZiPu baoZiPu=new BaoZiPu();
@Override
public void run() {
while (true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
baoZiPu.getCount();
}
}
}
public class Product implements Runnable{
private BaoZiPu baoZiPu;
public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
// BaoZiPu baoZiPu=new BaoZiPu();
@Override
public void run() {
while (true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
baoZiPu.setCount();
}
}
}
public class Test0 {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();
Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);
Thread t1 = new Thread(product);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
多等待多唤醒案例
解决多生产多消费问题(if改为while,将notify改为notifyAll)