2020.01.26 星期二
多线程
1. 多线程
1. 1 基本概念
程序: 为了实现某个特定的任务,使用某种编程语言编写的一组指令集合(静态)
进程: 正在运行的一个程序,是一个动态过程,具有生命周期,是资源分配的基本单位
线程:进程细分为线程,线程是一个程序内部的一条执行路径
- 线程是调度和执行的基本单位
- 同一个进程下的线程共享资源(相同的内存单元/内存地址空间)
- 每个线程拥有独立的运行栈和程序计数器(PC)
并行: 多个人(CPU)同一时间干多种事(任务)
并发:一个人(CPU)同一时间(时间片)干多种事(任务)
为什么要使用多线程?
- 提高应用程序的响应速度(图形化界面),增强用户体验
- 提高计算机CPU利用率
- 改善程序的结构,将长且复杂的进程划分为多个线程,独立运行,利于理解和修改
1.2 线程的创建和使用
- 继承
Thread
类,重写run
方法
public class ThreadExtendsThread extends Thread{
@Override
public void run(){
//重写run()方法
}
}
使用:
public static void main (String[] args){
ThreadExtendsThread thread = new ThreadExtendsThread();
thread.start();
//start(),创建一个线程并执行run()方法
//如果直接执行thread.run(),则只是简单的执行了thread对象的run()方法,没有开启一个线程
}
//一个Thread子类对象,一个线程
//Thread类的常用方法
//1.void start()
//2.void setName(String name)
//3.String getName()
//4.Thread currentThread()
//5.void yield()
//6.static void sleep(long millis)
//7.join()
//8.stop()
//9.boolean isAlive()
- 实现
Runnable
接口,重写run
方法
public class ImplementRunnable implements Runnable{
@Override
public void run(){
//重写run()方法
}
}
使用:
public void main(String[] args){
ImplementRunnable iRunnalbe = new ImplementRunnable();
Thread thread = new Thread(iRunnable);
thread.start();
}
//一个Thread类对象,一个线程
1.3 线程的生命周期
- 新建
- 就绪
- 运行
- 阻塞
- 死亡
1.4 线程的同步
- 什么是线程安全?
当某个数据同时被多个线程访问,很有可能出现线程不安全的问题,例如多个线程同时对一个数据进行增加,删除,修改等操作
- 实现线程安全的思路?
对访问共享数据的线程进行限制,如当某个线程在访问共享数据时,其他线程无法其进行任何操作(读,写…),保证了数据的安全性,称为线程的同步
-
实现线程安全的方法?
- 同步代码块
public void run(){ //如果实现多线程的方式是继承Thread类方式, //则同步监视器(锁)应该是类.class(保持唯一) synchronized(本类.class){ //多线程代码; } //如果实现多线程的方式是实现Runnable接口, //则同步监视器(锁)应该是this,(保持唯一) synchronized(this){ //多线程代码; } }
b. 同步方法
public synchronized void run(){ //如果实现多线程的方式是继承Thread类 //则同步方法应该是静态的,只有一份,同步监视器类.class //如果实现多线程的方式是实现Runnable接口 //则同步监视器应该是this }
-
释放锁的操作
- 程序正常执行
- wait() ==sleep不会释放锁
- break/return
- 未处理的Exception/Error
-
不会释放锁的操作
- sleep()
- 死锁
- yield
- suspend()
-
什么是死锁?
双方都占用互相需要的资源不放手,都在等待对方放手
public void main(String[] args){
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Ovrride
public void run(){
synchronized(s1){
s1.append("a");
s2.append("1");
Thread.sleep(10);//此时锁住s1,s2暂时自由
//睡醒了,锁住s1,等待s2
synchronized(s2){
s1.append("b");
s2.append("2");
}
}
}
}.start();
new Thread(new Runnable(){
@Override
public void run(){
synchronized(s2){
s1.append("c");
s2.append("3");
Thread.sleep(10);
//当s1被锁住,s2暂时自由,
//这个线程很容易执行到这,
//锁住s2,等待s1
synchronized(s1){
s1.append("d");
s2.append("4");
}
}
}
}).start();
}
解决方法:
-
专门的算法/原则
-
减少同步资源的定义
-
减少嵌套同步
-
JDK5.0新增的线程同步机制*—通过显式定义同步锁对象(Lock对象)来实现同步
java.util.concurrent.locks.Lock
接口:- 控制多个线程对共享资源进行访问的工具
- 提供了堆共享资源的独占访问
- 每次只有一个线程对Lock对象加锁
-
Lock锁的使用
public void main(String[] args){
//1.创建锁对象
Lock lcok = new ReentrantLock();
//2.访问共享数据前,手动上锁
lock.lock();
try{
//访问共享数据
}
//3.为了确保解锁操作一定被执行,所以放在finally模块中
finally{
lock.unlock();
}
}
Synchronized
和Lock
的对比Synchronized
是隐式锁,进出作用域相当于上锁解锁Lock
是全手动,上锁lock.lock()
解锁lock.unlock()
全手动Lock
只有代码锁,Synchronized
有代码锁和方法锁Lock
锁性能更好,因为JVM
花费较少时间来调度线程,且其具有较高可扩展性(提供更多子类)- 使用建议:
Lock
—>同步代码块—>同步方法
1.5 线程的通信
1. wait() 释放锁(故调用此方法前提是拥有锁),当前线程挂起并放弃CPU,同步资源并等待,让别的线程可访问并修改共享资源
2. notify() 唤醒正在排队等待同步资源的线程(优先级最高者先结束等待,若无最高,随机唤醒)
3. notifyAll()唤醒所有正在排队等待同步资源的线程(全部)
4. yield() 让步给更高优先级/相同优先级的线程
5. join() 调用线程将被阻塞,直到join()方法加入的join线程执行完为止
wait()
,notify()
和notifyAll()
都只能在同步方法/同步代码块中使用否则会出现java.lang.IllegalMonitorStateException
异常
###1.6 JDK5.0新增的线程创建方式