多线程
文章目录
1、线程和进程
进程和线程的概述
- 进程(Process):是计算机中程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
- 线程(thread):是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中实际运行单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多线程,每条线程并行执行不同的任务。
并发和并行
-
并发:指在同一时刻只能有一条指令执行,多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替执行。
-
并行:真正意义的同时执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zHdRvFAy-1656332147870)(C:\Users\21158\AppData\Roaming\Typora\typora-user-images\image-20220427111145254.png)]
2、线程的生命周期
- 线程的5种状态
- 起始:当创建线程对象时
- 就绪:当调用start方法启动时,指能运行但没有运行的过程,在等待CPU为其分配时间片
- 运行:执行run方法时
- 阻塞:当线程中断、主动出让、等待、睡眠时、会重新进入就绪状态
- 结束:线程执行完毕或异常终止或执行stop();
3、线程的创建
继承Thread类
- 通过继承Thread类重新run方法
public class TestThread extends Thread{
@Override
public void run() {
System.out.println(this.getName() + " runnig");
}
public static void main(String[] args) {
Thread thread = new TestThread();
thread.start();//start启动线程
}
}
实现Runnable接口
- 通过实现Runnable接口重写run()方法
public class TestRunnable implements Runnable{
@Override
public void run() {
//getName是在Thread类的currentThread()返回当前线程
System.out.println(Thread.currentThread().getName()+ " running ");//返回当前线程名字
}
public static void main(String[] args) {
//Thread类里面有可以放runnable参数的构造方法
Runnable runnable = new TestRunnable();
Thread thread = new Thread(runnable);
thread.start();
//匿名内部类实现
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 匿名内部类实现的线程");
}
}).start();
//lambda表达式创建线程
new Thread(() -> System.out.println(Thread.currentThread().getName() + " lambda表达式")).start();
}
}
- 好处是可以实现多个接口
继承Callable接口
- 思考:怎么让线程计算0-100的值?
- 实现Callable接口,可以定义返回指定的泛型,依赖FutuerTask类
public class TestCallable implements Callable<String> {
@Override
public String call() throws Exception {
return Thread.currentThread().getName() + " running";
}
public static void main(String[] args) throws Exception{
/**
* Callable<V>有返回值存储在FutureTask中的
* FutureTask继承RunnableFuture接口继承了Runnable所以线程创建是向上造型成了Runnable接口
*/
FutureTask<String> futureTask = new FutureTask<String>(new TestCallable());
//线程创建使用的构造方法:public Thread(Runnable target)
Thread thread = new Thread(futureTask);
thread.start();
//get()获取线程的返回值
System.out.println(futureTask.get());
}
}
- Callable线程创建过程
- Callable有返回值存储在FutureTask中的
FutureTask继承RunnableFuture接口继承了Runnable所以线程创建是向上造型成Runnable接口,然后使用Thread(Runnable)构造方法创建线程
- Callable有返回值存储在FutureTask中的
- Runnable和Callable的区别和联系
- 没有任何联系
- Runnable没有返回值,Callable有返回值
线程池的创建
-
工厂类Executors
public class TestExecutors { public static void main(String[] args) throws Exception { //创建一个线程池,中有五个线程,newFixedTreadPool创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(5); //循环启动这个五个线程 for (int i = 0; i < 100; i++) { executorService.execute(()-> System.out.println(Thread.currentThread().getName())); } //submit把线程交给Callable执行 Future<String> future = executorService.submit( ()-> {return Thread.currentThread().getName();}); System.out.println(future.get()); //shutdown 关闭线程池 executorService.shutdown(); } }
4、线程的常用方法
public class TestCommon implements Runnable{
@Override
public void run() {
//currentThread()获取当前线程对象
Thread thread = Thread.currentThread();
System.out.println("当前线程:" + thread);//Thread[Thread-0,5,main]
//getName()获取线程名字 setName()设置线程名字,也可以通过构造方法设置
System.out.println("当前线程名字:" + thread.getName());
//getId()获取线程id
System.out.println("当前线程ID:" + thread.getId());
/**
* 优先级Priority
* MIN_PRIORITY = 1 最小优先级
* NORM_PRIORITY = 5 默认优先级
* MAX_PRIORITY = 10 最大优先级
* 注:并不是说优先级高的就一定先执行完
*/
//setPriority()设置线程优先级
thread.setPriority(Thread.MIN_PRIORITY);//设置为最小优先级 1
//getPriority()获取线程优先级
System.out.println("当前线程优先级为:" + thread.getPriority());//1
//isAlive() 查看当前线程是否活动
System.out.println("当前线程是否活动:" + thread.isAlive());//true
try {
thread.sleep(1000);//线程休眠1000毫秒
//yielde()暂停当前正在执行的对象,目的就是为了能够轮换调度
thread.yield();
thread.join(1000);//等待thread线程1000毫秒后在执行
} catch (InterruptedException e) {
e.printStackTrace();
}
//interrupt() 中断线程
thread.interrupt();
//isInterrupted() 查看当前线程是否中断
System.out.println(thread.isInterrupted());//true 当前线程已中断
}
public static void main(String[] args) {
Thread thread = new Thread(new TestCommon());
System.out.println(thread.getName());
//main线程的优先级为5
System.out.println(Thread.currentThread().getPriority());//5
//start()启动线程
thread.start();
}
}
- Object提供的等待
守护线程
- 是在最后一个线程执行完自动停止
/*
守护线程Daemon()
在Thread0 创建一个死循环
main()中休眠1秒
发现main线程结束后Thread0线程自动结束了
*/
public class TestDaemon implements Runnable{
@Override
public void run() {
while (true){
System.out.println("Daemon running...");
}
}
public static void main(String[] args) throws Exception{
Thread thread = new Thread(new TestDaemon());
thread.setDaemon(true);
thread.start();
Thread.sleep(1000);//主线程休眠1秒
}
}
5、线程同步问题
-
案例-五个人同时抢购问题
/** * 5个人同时抢购,不同线程抢占同一资源,数据混乱 */ public class RushToBuy implements Runnable{ private Integer stock = 100;//库存 private Boolean flag = true;//标志库存还有 public static void main(String[] args) { RushToBuy rushToBuy = new RushToBuy(); //创建5个线程ABCDE分别代表五个用户 new Thread(rushToBuy,"A").start(); new Thread(rushToBuy,"B").start(); new Thread(rushToBuy,"C").start(); new Thread(rushToBuy,"D").start(); new Thread(rushToBuy,"E").start(); } @Override public void run() { while (flag){ buy(); } } private void buy(){ if(stock <= 0 ){ flag = false; return; } System.out.println(Thread.currentThread().getName() + "剩余库存" + (--stock)); } }
- 发现这个结果出现乱的情况和逻辑想的不一样
-
线程同步:同一个资源,多个人想使用,解决问题上锁,一个资源在使用时上锁,别人只能等待,等资源使用完时解锁。
同步方法
-
synchronized修饰的方法
-
对run方法进行加锁
@Override public synchronized void run() { while (flag){ buy(); } }
加锁了可以保证同一资源被多个对象使用,这样保证了数据的一致性
同步代码块
synchronized (obj){}
-
Obj:称为监听器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监听器
- 同步方法中无序指定同步监听器,因为同步方法中国监听器就是this
-
修改run方法
@Override public void run() { while (flag){ synchronized (this){ buy();//买 } } }
同步代码块是指锁住所控制的那一块代码,方法中其他的内容不受影响。比起同步方法来说更节省了资源调度时间。
Lock锁
-
通过显示定义同步锁对象(Lock)来完成同步
-
ReentrantLock类实现了Lock接口,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁和释放锁。
-
修改run方法
private final Lock lock = new ReentrantLock(); public void run() { while (flag){ try { //进入加锁 lock.lock(); buy();//买 } finally { //解锁 lock.unlock(); } } }
- Lock是显示锁可以看到加锁和解锁,synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较小的时间来调整线程,性能好。并且具有更好的扩展性(提供了更多的子类)
死锁
-
死锁就是两个或多个线程在使用时相互都需要对方的资源,相互都不会释放的状态,形成一个永久等待状态。
如:A线程程需要1资源,运行途中它又需要2资源,然后这时2资源已经被B使用(则A就需要等待B使用完)B线程程运行到途中它又需要1资源(1资源已经被A使用了B需要等待),这样就形成了两个线程都在永久等待的状态。
-
死锁的四个必要条件:
- 互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
- 请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
- 非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
- 循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。
-
解决死锁:如果打破上述3任何一个条件,便可让死锁消失
//死锁案例 public class TesetDeadlock { public static String data1 = "data1";//资源1 public static String data2 = "data2";//资源2 public static void main(String[] args) { TreadDl treadDl = new TreadDl(); treadDl.threadA.start();//启动线程A treadDl.threadB.start();//启动线程B } } class TreadDl { //线程A public Thread threadA = new Thread(() -> { synchronized (TesetDeadlock.data1) { //得到资源1 System.out.println("A 获取" + TesetDeadlock.data1); try { Thread.sleep(1000);//为了测试休眠1秒让B线程能运行 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("A 等待获取资源2"); //需要获取资源2,资源2已经被B使用处于等待状态 synchronized (TesetDeadlock.data2) { System.out.print("A"); } } }); //线程B public Thread threadB = new Thread(() -> { synchronized (TesetDeadlock.data2) {//锁住资源2 //得到资源2 System.out.println("B 获取" + TesetDeadlock.data2); try { Thread.sleep(1000);//为了测试休眠1秒让A线程能运行 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("B 等待获取资源1"); //需要获取资源1,资源1被A使用处于等待状态 synchronized (TesetDeadlock.data1) {//锁住资源1 System.out.println("B 获取" + TesetDeadlock.data1); } } }); }
6、线程通信
-
wait()等待,notify()唤醒属于Object类中
-
注:需要在同步代码块或同步方法中执行,必须是同一个同步监视器
-
生产者消费者
- 生产者线程生产后进行等待消费者消费,消费者消费了生产者生产
**
* 线程通信
* wait()等待,notify()唤醒属于Object类中
* 注:需要在同步代码块或同步方法中执行,
* 必须是同一个同步监视器
* 生成者消费者问题
* 线程1生产一个之后进行等待线程2消费
* 线程2消费了唤醒线程1生产
* 用户输入需要生产的数量
*/
public class TestTreadSignal {
public static void main(String[] args) {
Object obj = new Object();//创建一个监听对象
Scanner scanner = new Scanner(System.in);
System.out.println("请输入生产数量:");
int quantity = scanner.nextInt();//生产数量
//生产者线程
Thread thread1 = new Thread(() -> {
//同步代码块监视器obj
synchronized (obj) {
//生产quantity
for (int i = 1; i <= quantity; i++) {
System.out.println("生产:" + i);
try {
if (i > 1) {
obj.notify();//生产完成唤醒消费者线程消费
}
obj.wait();//生产完等待消费
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread2 = new Thread(() -> {
//同步代码块监视器obj
synchronized (obj) {
for (int i = 1; i <= quantity; i++) {
System.out.println("消费 " + i);
obj.notify();//消费完成唤醒生产线程生产
try {
obj.wait();//等待生产者线程生产
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread1.start();//启动生产者线程
thread2.start();//启动消费者线程
}
}
for (int i = 1; i <= quantity; i++) {
System.out.println("消费 " + i);
obj.notify();//消费完成唤醒生产线程生产
try {
obj.wait();//等待生产者线程生产
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread1.start();//启动生产者线程
thread2.start();//启动消费者线程
}
}