并行与并发
并行:多个CPU执行多个任务
并发:一个CPU“同时”(采取时间片的方式)执行一个任务
多线程:
为何要用多线程:
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
- 需要一些后台运行的程序时。
多线程程序的优点:
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
线程的四种创建方式:
- 方式一:继承于Thread类
- 定义子类继承Thread类。
- 子类中重写Thread类中的run方法。
- 创建Thread子类对象,即创建了线程对象。
- 调用线程对象start方法:start方法会启动当前线程,调用当前线程run方法。
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
// The following code would then create a thread and start it running:
PrimeThread p = new PrimeThread(143);
p.start();
Thread类的常用有关方法:
- void start(): 启动线程,并执行对象的run()方法
- run(): 线程在被调度时执行的操作
- String getName(): 返回线程的名称
- void setName(String name):设置该线程名称 (也可以通过构造器重命名)
- static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类 .j将 currentThread与 setName或getName同用来修改或获得当前线程名字
- 方式二: 实现Runnable接口
- 创建一个实现了Runnable接口的类
- 实现类去实现Runnable中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start()
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
//The following code would then create a thread and start it running:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
-
比较上面的创建线程的两种方式。
开发中:优先选择方法二:实现Runnable接口的方式
原因:
1. 实现Runnable接口的方式没有类的单继承性的局限性
2. 实现Runnable接口的方式更适合来处理多个线程有共享数据的情况。
联系: public class Thread implements Runnable
相同点: 两种方式都需要重写run(),(意思是这两种方法最终都是实现了Runnable中的run()方法)将线程要执行的逻辑声明在run()中。 -
方法三:实现Callable接口—(JDK 5.0 新增)
- 与使用Runnable相比, Callable功能更强大些
1). 相比run()方法,可以有返回值
2). 方法可以抛出异常
3). 支持泛型的返回值
4). 需要借助FutureTask类,比如获取返回结果
- 与使用Runnable相比, Callable功能更强大些
-
方法四:使用线程池 —(JDK 5.0 新增)
- 思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具
- 好处:
1). 提高响应速度(减少了创建新线程的时间)
2). 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3). 便于线程管理- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程优先级:
- 线程的优先级等级
- MAX_PRIORITY: 10
- MIN _PRIORITY: 1
- NORM_PRIORITY: 5
- 涉及的方法
- getPriority() : 返回线程优先值
- setPriority(int newPriority) : 改变线程的优先级
- 说明线程创建时继承父线程的优先级低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
线程的生命周期
线程的同步
- 使用多线程时会出现安全问题:
- 问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有
执行完,另一个线程参与进来执行。导致共享数据的错误。 - 解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以
参与执行。
-
.在Java中,我们通过同步机制,来解决线程的安全问题。
方式一:同步代码块
synchronized(同步监视器){
// 需要被同步的代码
}
说明:1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑
使用this(即本对象)充当同步监视器。此外,同步监视器可以使用
类本身,这样比较安全,格式为 synchronized ( 当前类名.class)
方式二:同步方法。
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
```java
//非静态方法,相当于使用this
public synchronized void show (String name){
….
}
//静态方法,相当于使用当前类本身,这样肯定就是唯一的了
private static synchronized void show(){
```
}
-
关于同步方法的总结:
- 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
- 非静态的同步方法,同步监视器是:this静态的同步方法,同步监视器是:当前类本身
-
同步的方式,解决了线程的安全问题。—好处
-
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 —局限性。
方法三:Lock(锁)
- 从JDK 5.0开始, Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。 同步锁使用Lock对象充当。
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
- ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义, 在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
lass A{
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
lock.lock();
try{
//保证线程安全的代码;
}
finally{
lock.unlock();
}
}
}
//注意:如果同步代码有异常,要将unlock()写入finally语句块
synchronized 与 Lock 的对比:
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁), synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁, synchronized有代码块锁和方法锁
- 使用Lock锁, JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序:
Lock —> 同步代码块(已经进入了方法体,分配了相应资源)—> 同步方法(在方法体之外)
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
线程的通信
- wait() 与 notify() 和 notifyAll()
- wait():令当前线程挂起并放弃CPU、 同步资源并等待, 使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有
权后才能继续执行。 - notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
- notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
注意:
1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。 否则,会出现IllegalMonitorStateException异常
3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
sleep() 和 wait()的异同?
1). 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
2).不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
3).调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
4).关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。