一、基础概念
1.线程和进程
-
进程:系统中正在运行的程序,是系统分配资源的最小单位
-
线程:是进程中的最小执行单元,是CPU分配时间的最小单位,一个进程最少拥有一个线程【简单理解,进程的一个功能就是一个线程】
2.并发和并行
-
并行:在同一时刻,多个任务在CPU上同时执行【例如:多个厨师在同时炒多个菜】
-
并发:在同一时刻,有多个任务在CPU上交替执行【例如:一个厨师在炒多个菜】
3.CPU调度方式
- 分时调度:让所有线程轮流获取CPU的使用权限,使用时间一样长
- 抢占式调度:多个线程轮流抢占CPU的使用权限,哪个线程抢到了,哪个线程先执行。【Java就是抢占式调度】
二、多线程基础
1.创建线程的方式
1.1 继承Thread
1.1.1 基本使用
class Cat extends Thread{//1.继承Thread类
public void run(){//2.重写run方法【里面是这个线程要干的事】
System.out.println("喵喵,要睡觉啦");
}
}
public class process {
public static void main(String[] args) {
Cat cat = new Cat();
cat.start();//3.调用start方法启动线程【会自动调用run方法】
}
}
1.1.2 匿名内部类的用法
public class process {
public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("小狗要吃奶。。。"+(++i));
}
}).start();
}
}
1.1.3 Thread类中的方法
void start();//启动线程
void run();//设置线程任务【这个方法是Thread类重写Runnable接口中的run方法】
String getName();//获取线程名字
void setName();//给线程设置name
static Thread currentThread();//获取正在执行的线程对象【在哪个线程中使用,获取的就是哪个线程对象】
static void sleep(long millis);//计时睡眠【时间到了自动唤醒】
1.1.4 Thread类中的其他方法
void setProiority(int new Proioriry);//设置线程优先级【1最小,5默认,10最大】
int getProiority();//获取线程优先级
void setDaemo(boolean on);//设置为守护线程【非守护线程结束之后守护线程就要结束(不是立马结束,有一定延迟)】
static void yield();//礼让线程(让出cpu使用权)【只是尽可能地平衡,不是绝对平衡】
void join();//插入线程
1.2 实现Runnable接口
1.2.1 基本使用
//1.创建类实现Runnable接口
class Dog implements Runnable{
//2.重写run方法,设置线程任务
@Override
public void run() {
System.out.println("小狗要吃奶。。。"+(++i));
}
}
public class process {
public static void main(String[] args) {
//3.创建实现类对象,将实现类对象放到Thread构造方法中创建Thread对象
Dog dog = new Dog();
Thread thread = new Thread(dog);
//4.调用Thread的start方法
thread.start();
}
}
1.3 实现Callable接口
1.3.1 介绍
1.Callable<V>是一个接口
2.通过call()方法设置线程任务【类似于run方法】
3.call方法和run方法的区别
a.相同点:都是设置线程任务的
b.不同点:run方法不能抛异常,call方法可以抛异常,并且call方法有返回值
4.<V>:实现Callable接口时指定的是什么类型,返回的也只能是对应的类型
5.获取call方法的返回值:FutureTask<V>
a.FutureTask<V>实现了一个接口Future<V>
b.FutureTask也是Runable的实现类
c.FutureTask里面有个方法V get()->用于获取call的返回值
1.3.2 基本使用
//创建接口实现类
public class MyStore implements Callable<String> {
@Override
public String call() throws Exception {
return "你和金莲的故事。。。。。";
}
}
//启动线程
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建接口实现类
MyStore myStore = new MyStore();
//创建FutureTask对象,参数是Callable接口实现类
FutureTask<String> futureTask = new FutureTask<>(myStore);
//启动线程
new Thread(futureTask).start();
//调用get方法获取返回值
System.out.println(futureTask.get());
}
}
1.4 线程池
1.4.1 介绍
频繁创建和销毁线程对象会占用太多资源。使用线程池可以解决反复创建线程对象的问题
1.如何获取程池对象?//使用工具类Executors的静态方法
static ExecutorService newFixedThreadPool(int nThreads)
a.参数:线程池中包含多少个线程
b.返回值:ExecutorService是线程池,用来管理线程对象
2.如何执行线程任务?//调用ExecutorService中的方法
Future<?> submit(Runnable task) 提交一个Runnable的任务
Future<?> submit(Callable<T> task) 提交一个Callable的任务
a.返回值Future可以用来接收call方法的返回值【Future的get方法】
3.ExecutorService中的其他方法:
void shutdown():关闭线程池。会有序关闭线程,已经提交的任务会被执行,不会再接收新任务
//【如果不关闭线程池,线程池会一直等着其他线程来,不会自动结束程序】
1.4.2 使用方法
public class CallableTest implements Callable<String> {
@Override
public String call() throws Exception {
//任务:完成1-100的和
int sum = 0;
for (int i=0;i<=100;i++){
sum +=i;
}
return "1-100的和为:"+sum;
}
}
public class RunnableTest implements Runnable{
@Override
public void run() {
System.out.println("RunnableTest启动类");
}
}
public class TestThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建一个容量为2的线程池
ExecutorService executorService = Executors.newFixedThreadPool(2);
//启动线程任务
Future<?> future = executorService.submit(new RunnableTest());
Future<String> future1 = executorService.submit(new CallableTest());
System.out.println(future1.get());//获取Callable的返回值
//关闭线程池
executorService.shutdown();
}
}
2.线程安全
2.1 什么时候发生线程安全问题
多个线程同时访问一个数据
2.2 解决方法
2.2.1 同步
同步代码块解决
格式:
synchronized(任意对象){
可能出现线程不安全的代码
}
说明:任意对象就是锁对象。拿不到锁就不能进到同步代码块里面
注意:要想线程安全,锁对象必须是同一个
//案例:买票
public class MyTicket implements Runnable{
int ticket=100;
//任意new一个对象代表锁
Object lock=new Object();
@Override
public void run(){
while(true){
synchronized(lock){//有线程安全的地方加锁
if(ticket>0){
System.out.println(ticket+"被卖出");
ticket--;
}
}
}
}
}
public class Test{
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();
}
}
同步方法解决
-
普通同步方法【方法上加synchronized】
默认锁是this
格式: 修饰符 synchronized 返回值类型 方法名 (参数){ 方法体 return 结果; }
-
静态同步方法
默认锁是class对象
格式: 修饰符 static synchronized 返回值类型 方法名 (参数){ 方法体 return 结果; }
2.2.2 Lock锁
Lock对象的基本介绍和使用:
//1.Lock是一个接口,一般使用他的实现类ReentrantLock
//2.使用方法
lock();//获取锁
unlock();//释放锁
public class Ticket implements Runnable{
private int ticket=100;//默认100张票
private Lock lock=new ReentrantLock();
@Override
public void run() {
while (ticket>0){
//买票
try {
Thread.sleep(100);
lock.lock();//先上锁
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票!");
ticket--;
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
public class Main {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket,"张三").start();
new Thread(ticket,"李四").start();
new Thread(ticket,"王五").start();
}
}
2.2.3 synchronized和Lock的区别
1.synchronized不管是同步代码块还是同步方法,都在{}之后才释放锁锁对象
2.Lock通过lock和unlock方法控制锁
3.死锁
死锁指的是两个或者两个以上的线程在执行过程中由于竞争同步锁而产生的一种阻塞现象。如果没有外力,他们将无法继续执行下去
如下图所示:线程1正在持有锁1,但是线程1必须再拿到锁2,才能继续执行。线程2正在持有锁2,但是线程2需要再拿到锁1,才能继续执行。此时两个线程处于互相等待的状态,就是死锁,在程序中的死锁将出现在同步代码块的嵌套中
//代码实现
public class LockA{
public static LockA lockA=new LockA();
}
public class LockB{
public static LockB lockB=new LockB();
}
public class DieLock{
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("if...lockA");
}
}
}
}
}
public class Test{
public static void main(String [] args){
DieLock dielock1=new DieLock(true);
DieLock dielock2=new DieLock(false);
new Thread(dielock1).start();
new Thread(dielock2).start();
}
}
4.线程状态
状态 | 状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动【没有调用start方法】 |
Runnable(可运行) | 线程可以被运行【可能正在运行、可能没有运行】 |
Blocked(锁阻塞) | 对象需要获取的锁正在被其他对象持有,当对象获取到锁时,转换为Runnable状态 |
Waiting(无线等待) | 一个线程正在等待另一个线程唤醒。【这个状态不能自动唤醒,需要另一个线程使用notify唤醒】 |
Timed Waiting(计时等待) | 时间到了可以自动唤醒也可以被其他程序唤醒【使用Thread.sleep,Object.wait进入】 |
Terminated(被终止) | 因为run方法正常退出而死亡\或者没有捕获到异常\或者调用了stop方法 |
sleep(time)和wait(time)的区别:
- sleep线程睡眠,在睡眠的过程中不会释放锁,设置的时间到了会自动醒来,继续执行
- wait线程等待,在等待的过程中会释放锁。如果在等待的过程中被唤醒,或者到达时间,会重新抢锁。抢到了继续执行,抢不到锁阻塞
wait()和notify():
- 空参wait:线程进入到无线等待状态,会释放锁。需要其他线程使用notify()或者notifyAll唤醒。被唤醒之后会和其他的线程抢锁,抢到了继续执行,抢不到进入锁阻塞状态
- notify:唤醒正在等待的线程。一次只能唤醒一条,如果有多个正在等待的线程,则随机唤醒一个。
- notifyAll:唤醒所有等待的线程
- wait和notify这两个方法都需要锁对象调用,所以两个方法需要用到同步代码块、同步方法中。两个方法的调用必须使用同一个锁对象
5.线程通信
要求:一个线程生产,一个线程消费,不能连续生产,不能连续消费->等待唤醒机制(线程之间的通信)
方法 | 作用 |
---|---|
void wait( ) | 线程等待,等待过程中会释放锁,需要其他线程使用notify()唤醒,唤醒之后需要重启抢锁。 |
void notify( ) | 线程唤醒,一次只能唤醒一个等待线程。如果有多个等待线程,则随机唤醒一个线程。 |
void notifyAll( ) | 唤醒所有等待线程 |
wait和notify方法需要锁对象调用,所以需要到同步代码块中使用,而且必须是同一个锁对象
5.1 案例
案例:一个线程生产包子,一个线程消费包子。要求不能连续生产包子,不能连续消费包子
5.1.1 同步代码块思路
public class BaoziPu {
private static int baozi = 0;//包子的个数
private static boolean flag = false;//当前是否有包子
public static boolean isFlag() {
return flag;
}
public static void setFlag(boolean flag) {
BaoziPu.flag = flag;
}
public static int getBaozi() {
return baozi;
}
public static void setBaozi(int baozi) {
BaoziPu.baozi = baozi;
}
}
//生产者
public class Prodect implements Runnable {
private Object lock;
public Prodect(Object lock) {
this.lock = lock;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
if (!BaoziPu.isFlag()) {//如果是false,就说明没有包子,生产包子
BaoziPu.setBaozi(BaoziPu.getBaozi() + 1);
BaoziPu.setFlag(true);
System.out.println("生产了第" + BaoziPu.getBaozi() + "个包子");
lock.notify();//唤醒之前睡眠的线程【肯定不是本线程】
} else {//如果是true,就说明有包子,不需要生产
try {
lock.wait();//当前线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
//消费者
public class Constum implements Runnable {
private Object lock;
public Constum(Object lock) {
this.lock = lock;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
if (BaoziPu.isFlag()) {//如果是true,就说明有包子,消费包子
BaoziPu.setFlag(false);
System.out.println("我吃了第" + BaoziPu.getBaozi() + "个包子");
lock.notify();//唤醒之前睡眠的线程【肯定不是本线程】
} else {//如果是false,就说明没有包子,不需要生产
try {
lock.wait();//当前线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class Main{
public static void main(String[] args) {
Object lock = new Object();
new Thread(new Constum(lock)).start();
new Thread(new Prodect(lock)).start();
}
}
5.1.2 同步方法思路
public class BaoziPu {
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public synchronized void getBaozi() {
if (this.flag) {//如果是true,就说明有包子,消费包子
flag=false;
System.out.println("我吃了第" + baozi + "个包子");
this.notify();
} else {//如果是false,就说明没有包子,不需要生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void setBaozi() {
if (!this.flag) {//如果是false,就说明没有包子,生产包子
baozi++;
flag = true;
System.out.println("生产了第" + baozi + "个包子");
this.notify();
} else {//如果是true,就说明有包子,不需要生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private int baozi = 0;//包子的个数
private boolean flag = false;//当前是否有包子
}
//消费者
public class Constum implements Runnable {
private BaoziPu baoziPu;
public Constum(BaoziPu baoziPu) {
this.baoziPu = baoziPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
baoziPu.getBaozi();
}
}
}
//生产者
public class Prodect implements Runnable {
private BaoziPu baoziPu;
public Prodect(BaoziPu baoziPu) {
this.baoziPu = baoziPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
baoziPu.setBaozi();
}
}
}