Java基础——多线程(待续)

本文介绍了Java中开启多线程的两种方式:继承Thread类与实现Runnable接口,并探讨了线程安全问题及其解决方案——加锁。此外,还详细讲解了线程间的通信机制,包括wait、notify及notifyAll等关键方法的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、开启多线程的两种方法

1、继承Thread类,在子类中重写run()方法,然后直接创建该类对象用start开启线程。

class ThreadChild extends Thread {
    public void run(){
        //要实现的代码块
    }
}
new ThreadChild().start();
new ThreadChild().start();

2、定义一个类,覆盖实现Runnable接口中的run()方法,并创建一个Thread类对象,传入Runnable接口的子类对象作为Thread构造函数的参数。最后start实现。

class ThreadDemo implements Runnable {
    public void run(){
            //要执行的代码
    }
}

ThreadDemo d = new ThreadDemo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();

二、线程安全问题

解决方案:加锁。就是将多条操作共享数据的线程代码封装起来, 当有线程在执行这些代码的时候, 其他线程不可以参与运算。 必须要当前线程把这些代码都执行完毕后, 其他线程才可以参与运算。

同步的好处:解决安全问题。
同步的弊端:由于每个线程都会进行判断锁的操作,故降低了程序运行效率。
2.1、同步代码块

synchronized(){
    //需要被同步的代码
}

2.2、同步函数
在函数上加上synchronized修饰符。

同步函数的锁是固定的this,同步代码块的锁是任意的对象。
建议使用同步代码块。

三、线程间通信

其实就是多个线程在操作同一个资源,但是操作的动作不同。

3.1 涉及方法
1. wait():让线程池处于冻结状态,被wait的线程会被存储到线程池中。
2. notify():唤醒线程池中任何一个线程。
3. notifyAll():唤醒线程池中所有线程。

P.S.
1. 这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
2. 必须明确到底操作的是哪个锁上的线程。
3. wait和sleep的区别?
答:1)wait可以指定时间也可以不指定,sleep必须指定时间。
2)在同步中对CPU的执行权不同,锁的处理不同。
wait:释放执行权,释放锁
sleep:释放执行权,不释放锁。

Java中,死锁(Deadlock)是指两个或多个线程相互等待对方持有的资源而无限期地阻塞的情况。每个线程都持有某些资源,并且正在尝试获取其他被另一个线程所占有的资源。 ### 如何造成死锁 为了创建一个简单的死锁例子,我们可以定义两个同步的方法(或者说是在同一时间只有一个线程能够访问它们)并让其中一个调用另外一个;然后启动两个线程分别执行这两个方法: ```java public class DeadLockExample { private static Object resource1 = new Object(); private static Object resource2 = new Object(); public void methodOne() { // Thread one will get lock on Resource 1 first. synchronized (resource1) { System.out.println("Thread 1: locked resource 1"); try { Thread.sleep(10); } catch (Exception e) {} // Then tries to get lock on the second resource. synchronized (resource2) { System.out.println("Thread 1: locked resource 2"); } } } public void methodTwo() { // Thread two gets lock on resource 2 first. synchronized (resource2){ System.out.println("Thread 2: locked resource 2"); try{ Thread.sleep(10);}catch(Exception e){} // Then attempts to acquire a lock on another object that is already held by thread one. synchronized(resource1){ System.out.println("Thread 2: Locked resource 1"); } } } public static void main(String[] args)throws InterruptedException{ final DeadLockExample demo = new DeadLockExample(); Thread t1 = new Thread(new Runnable(){ @Override public void run(){ demo.methodOne(); } }); Thread t2 = new Thread(new Runnable(){ @Override public void run(){ demo.methodTwo(); } }); t1.start(); t2.start(); t1.join(); t2.join(); } } ``` 在这个场景下: - 如果`methodOne()`首先开始运行,则它会先占有第一个资源(`resource1`),之后休眠片刻,在这段时间内如果`methodTwo()`也开始运行了,那么它可以成功锁定第二个资源 (`resource2`) - 接下来当`methodOne()`从睡眠状态恢复过来试图获得 `resource2` 的时候就会因为该资源已经被 `t2` 占有而陷入等待;与此同时,`thread2`也会由于无法取得由`thread1`占用着的 `resource1` 而进入等待——这也就形成了典型的“循环等待”型死锁现象。 ### 死锁产生的必要条件: 根据Dijkstra提出的理论,要发生死锁必须同时满足以下四个条件: - **互斥使用** - 至少有一个资源必须处于不能共享的状态; - **部分分配**(持而待续):进程已经保持至少一种类型的资源并且正请求额外的新类型或其他相同类型的更多单位的数量。 - **不可剥夺**: 已经分配给某个进程的资源不可以强行收回; - **环路等待**: 存在一个封闭的过程链,其中每个过程都在等待下一个过程中仅拥有的资源。(如上述示例中的情况) #### 解释 在这段代码里我们故意制造了一个导致死锁的机会。实际上大多数情况下开发者并不希望程序出现这种情况,所以在编写并发应用时应当小心避免这种状况的发生。预防措施包括但不限于始终按照相同的顺序去获取锁、限制任何时刻只允许一个任务拥有某种特定类型的全部可用实例等策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值