介绍
- 百度百科:同步多线程(SMT)是一种在一个CPU 的时钟周期内能够执行来自多个线程的指令的硬件多线程技术。本质上,同步多线程是一种将线程级并行处理(多CPU)转化为指令级并行处理(同一CPU)的方法。 同步多线程是单个物理处理器从多个硬件线程上下文同时分派指令的能力。同步多线程用于在商用环境中及为周期/指令(CPI)计数较高的工作负载创造性能优势。 处理器采用超标量结构,最适于以并行方式读取及运行指令。同步多线程使您可在同一处理器上同时调度两个应用程序,从而利用处理器的超标量结构性质。
多线程访问临界资源
多线程访问临界资源是的数据安全问题
- 临界资源 :多个线程同时访问的资源。
- 产生原因:有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走了,临界资源问题就产生了
解决临界资源问题
- 解决方案:一个线程在访问临界资源的时候,如果给这个资源
“上一把锁”
,这个时候如果其他线程也要访问这个资源,就得在“锁”
外面等待。
锁
- 锁:任意的对象都可以被当做锁来使用
同步代码块
- 同步:Synchronized:有等待
- 异步:Asynchronized:没有等待,各执行各的
语法: synchronized(锁) { //需要访问临界资源的代码段 }
说明: a.程序走到代码段中,就用锁来锁住了临界资源,这个时候,其他线程不能执行代码段中的代码,只能在锁外边等待 b.执行完代码段中的这段代码,会自动解锁。然后剩下的其他线程开始争抢cpu时间片 c.一定要保证不同的线程看到的是同一把锁,否则同步代码块没有意义
同步代码块使用
public class Ticket implements Runnable{ // 需求:100张 // 临界资源 private int ticket = 100; @Override public void run() { while (true) { //上锁 synchronized(this){ if (ticket < 1) { break; } System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票"); ticket--; } } } }
同步方法
- 同步非静态方法
- 锁是
this
- 同步静态方法
- 锁是
类.class
ReentrantLock类(可重入锁)jdk1.5
- 从jdk1.5之后加入新的接口 Lock,ReentrantLock是Lock接口的实现类。
- 通过显式定义同步锁对象来实现同步,同步锁提供了比synchronized代码块更广泛的锁定操
- 注意:最好将
unlock
的操作放到finally
块中- 通过使用ReentrantLock这个类来进行锁的操作,它实现了Lock接口,使用ReentrantLock可以显式地加锁、释放锁
死锁
- 每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源。
- 当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁。
- 死锁的条件:
- 两个以上的线程
- 至少
两个锁
以上- 同步中
嵌套
同步
多线程在单例中的应用
- 单例的实现方式:懒汉式和饿汉式
- 其中,懒汉式是线程不安全的,当有多条线程同时访问单例对象时,则会出现多线程临界资源问题
- 单例实现步骤:
- 私有化构造方法
- 在类中创建对象
- 通过公开的方法返回这个对象
多线程访问单例-饿汉式
```java package com.qf.day20_7; /**
单例模式 * */ public class SingleTon { //1私有化构造方法 private SingleTon() {
} //2创建对象 private static SingleTon instance=new SingleTon(); //3公开的方法返回这个对象 public static SingleTon getInstance() { return instance; }
}
package com.qf.day20_7;
public class SingleTonThread extends Thread{ @Override public void run() { SingleTon singleTon=SingleTon.getInstance(); System.out.println(singleTon.hashCode()); } }
package com.qf.day20_7;
public class Test { public static void main(String[] args) { // SingleTonThread s1=new SingleTonThread(); // SingleTonThread s2=new SingleTonThread(); // SingleTonThread s3=new SingleTonThread(); // s1.start(); // s2.start(); // s3.start();
Runnable r=new Runnable() { @Override public void run() { SingleTon singleTon=SingleTon.getInstance(); System.out.println(singleTon.hashCode()); } }; new Thread(r).start(); new Thread(r).start(); new Thread(r).start(); } }
多线程访问单例-懒汉式
public class SingleTon { private SingleTon(){ //禁止反射破解 synchronized (SingleTon.class) { if (instance != null) { throw new RuntimeException("不能使用反射创建对象"); } } } private static volatile SingleTon instance; //volatile:不稳定的,易挥发的 public static SingleTon getInstance() { if(singleTon==null) {//为了提高效率 synchronized (SingleTon.class) {//判断锁的过程比较耗性能,为了提高效率 if (singleTon == null) { singleTon = new SingleTon(); } } } return singleTon; } } public class SingleTonThread extends Thread{ @Override public void run() { SingleTon singleTon=SingleTon.getInstance(); System.out.println(singleTon.hashCode()); } } package com.qf.day20_6; public class Test { public static void main(String[] args) { //线程对象 SingleTonThread s1=new SingleTonThread(); SingleTonThread s2=new SingleTonThread(); SingleTonThread s3=new SingleTonThread(); //启动线程 s1.start(); s2.start(); s3.start(); } }
补充单例其他的写法
静态内部类写法
/* * (1)节省空间 * (2)不会线程安全问题 */ public class SingleTon2 { private SingleTon2(){ } static class Holder{ private static final SingleTon2 INSTACNE=new SingleTon2(); } public static SingleTon2 getInstance(){ return Holder.INSTACNE; } }
使用枚举
/* * 枚举写法 * (1)没有线程安全问题 * (2)反射破解问题 * */ public enum SingleTon3 { INSTANCE; public static SingleTon3 getInstance(){ return INSTANCE; } }
线程的通信【生产者与消费者设计模式】
线程通信
- 实现功能:使用线程通信。
- 在jdk1.5之前有三个方法实现线程通信:
wait(): 等待,线程执行这个方法进入等待队列(和锁有关,一个锁对应一个等待队列), 需要被唤醒 notify(): 通知唤醒,从等待队列中随机唤醒一个线程 notifyAll():全部唤醒,把等待队列中所有的线程都唤醒
生产者与消费者设计模式原理
- 它描述的是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者可以从仓库中取走产品,解决生产者/消费者问题,我们需要采用某种机制保护生产者和消费者之间的同步
- 同步问题核心在于:如何保证同一资源被多个线程并发访问时的完整性,常用的方法就是加锁,保证资源在任意时刻只被一个线程访问
实现
- wait():当缓冲区已满或空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行
是Object的方法 调用方式:对象.wait(); 表示释放 对象 这个锁标记,然后在锁外边等待(对比sleep(),sleep是抱着锁休眠的) 等待,必须放到同步代码段中执行
- notify():当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态
是Object的方法 调用方式:对象.notify(); 表示唤醒 对象 所标记外边在等待的一个线程
- notifyAll():全部唤醒
是Object的方法 调用方式:对象.notifyAll() 表示唤醒 对象 所标记外边等待的所有线程
扩展:使用Jdk1.5 Lock优化生产者和消费者
扩展知识1:读写锁
ReadWriteLock
接口:可以实现多个读线程同时读取数据,写线程需要互斥执行。
读 写 、写 写 需要互斥 读 读 不需要互斥
扩展知识2:线程池
- 为什么需要线程池:
例如有非常的多的任务需要多线程来完成,且每个线程执行时间不会太长,这样会频繁的创建和销毁线程。频繁创建和销毁线程会比较耗性能。如果有了线程池就不要创建更多的线程来完成任务,因为线程可以重用。
线程池用维护者一个队列,队列中保存着处于等待(空闲)状态的线程。不用每次都创建新的线程。
和线程池相关的接口和类存在java.util.concurrent并发包中。
接口:
实现类:
扩展知识3:定时器Timer
- 只有一个线程
Timer timer = new Timer(); TimerTask tt = new TimerTask() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"=="+i); } timer.cancel(); } }; timer.schedule(tt,2000);