共计 8022 个字符,预计需要花费 21 分钟才能阅读完成。
Java 内存模型
涨姿态了!原来这才是多线程正确实现形式
涨姿态了!原来这才是多线程正确实现形式
线程同步
线程同步机制是一套实用于协调线程之间的数据拜访机制,该机制能够保障线程平安
java 平台提供的线程同步机制包含:锁、volatile 关键字、final 关键字,static 关键字、以及相干 API 如 object.wait/object.notify
锁概述
线程平安问题的产生前提是多个线程并发访问共享数据,将多个数据对共享数据的并发拜访,转化为串行拜访,即共享数据只能被一个线程拜访,锁就是这种思路。
线程拜访数据时必须先取得锁,取得锁的线程称为锁的持有线程,一个锁一次只能被一个线程持有,持有线程在取得锁之后和开释锁之前锁执行的代码称之为临界区。
锁具备排它性(Exclisive),即一个锁只能被一个线程持有,这种锁称为排它锁或者互斥锁。
涨姿态了!原来这才是多线程正确实现形式
JVM 局部把锁分为外部锁和显示锁,外部锁通过 Synchronized 关键字实现,显示锁通过
java.concurrent.locks.Lock 接口实现类实现的。
锁的作用
锁可能实现对共享数据的平安,保障线程的原子性,可见性与有序性。
锁是通过互斥保障原子性,一个锁只能被一个线程持有,这就保障了临界区的代码一次只能被一个线程执行,使得临界区代码所执行的的操作自然而然的具备不可分割的个性,既具备了原子性。
好比一条路段所有车辆都在跑,并发执行,在通过某一个路段的时候,多车道变为一车道,一次只能通过一辆车,由并发执行改为串行执行。
可见性是通过写线程冲刷处理器的缓存和读线程刷新处理器缓存这两个动作,锁的取得隐含着刷新处理器缓存的动作,锁的开释隐含着冲刷处理器缓存的动作。
锁可能保障有序性,写线程在临界区所执行的临界区看来像是齐全依照源码程序执行的。
锁的相干概念
可重入性: 一个线程持有该锁的时候可能再次 / 屡次申请该锁
如果一个线程持有一个锁的时候,还没有开释,但还可能持续胜利申请该锁,称该锁可重入,反之。
锁的争用与调度
java 中外部锁属于非偏心锁,显示锁反对非偏心锁和偏心锁
锁的粒度
一个所能够爱护的共享数据的数量大小称为锁的粒度。
锁爱护共享数据量大,称为锁粒度粗,否则称为粒度细。
锁的粒度过粗会导致线程在申请锁时会进行不必要的期待,锁粒度过细会减少锁调度的开销。
比方银行有一个柜台一个员工能够办理开卡、销户、取现、贷款那么所有人都只能去这个柜台办理业务,会须要很长的等待时间。然而如果把业务细分,一个业务一个柜台,这时候减少了银行的开销,须要三个员工。
外部锁:Synchronized
Java 中每一个对象都有一个与之关联的外部锁,这种锁也叫监视器,是一种排它锁,能够保障原子性、可见性、排它性。
Synchronized(对象锁)
{
同步代码块,能够在同步代码块中访问共享数据
}
润饰实例办法称为同步实例办法,润饰静态方法称为同步静态方法。
Synchronized 同步代码块
public class SynchronizedLock {
public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();
for (int i = 0; i <2 ; i++) {new Thread(new RunnableThread())
{
@Override
public void run() {synchronizedLock.mm();
}
}.start();}
}
public void mm()
{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
两个线程的代码都在并发执行
涨姿态了!原来这才是多线程正确实现形式
当初要打印的时候进行同步,同步的原理线程在执行的时候要先要取得锁
public class SynchronizedLock {
public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();
for (int i = 0; i <2 ; i++) {new Thread(new RunnableThread())
{
@Override
public void run() {synchronizedLock.mm();// 应用锁的对象是 synchronizedLock 对象
}
}.start();}
}
public void mm()
{synchronized (this)//this 作为以后对象
{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
涨姿态了!原来这才是多线程正确实现形式
因为 Synchronized 外部锁是排它锁,一次只能被一个线程持有,当初是 Thread- 0 先获得锁对象,Thread- 1 在期待区期待 Thread- 0 执行结束开释锁,Thread- 1 取得锁再执行。
锁对象不同不能实现同步
public class SynchronizedLock {
public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();
SynchronizedLock synchronizedLock2=new SynchronizedLock();
new Thread(new RunnableThread())
{
@Override
public void run() {synchronizedLock.mm();// 应用锁的对象是 synchronizedLock 对象
}
}.start();
new Thread(new RunnableThread())
{
@Override
public void run() {synchronizedLock2.mm();// 应用锁的对象是 synchronizedLock 对象
}
}.start();}
public void mm()
{synchronized (this)//this 作为以后对象
{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
涨姿态了!原来这才是多线程正确实现形式
因而想要同步必须应用同一个锁对象
应用常量作为锁对象
public class SynchronizedLock {
public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();
SynchronizedLock synchronizedLock2=new SynchronizedLock();
new Thread(new RunnableThread())
{
@Override
public void run() {synchronizedLock.mm();// 应用锁的对象是 synchronizedLock 对象
}
}.start();
new Thread(new RunnableThread())
{
@Override
public void run() {synchronizedLock.mm();// 应用锁的对象是 synchronizedLock 对象
}
}.start();}
public static final Object obj=new Object();
public void mm()
{synchronized (obj)// 常量作为以后对象
{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
同步实例办法
应用 synchronized 润饰实例办法,同步实例办法,默认应用 this 作为锁对象
public class SynchronizedLock {
public static void main(String[] args) {
SynchronizedLock synchronizedLock=new SynchronizedLock();
new Thread(new RunnableThread())
{
@Override
public void run() {synchronizedLock.mm();
}
}.start();
new Thread(new RunnableThread())
{
@Override
public void run() {synchronizedLock.mm2();
}
}.start();}
// 同步实例办法
public synchronized void mm()
{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
public void mm2()
{synchronized (this)// 常量作为以后对象
{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
同步静态方法
应用 synchronized 润饰静态方法,同步静态方法,默认运行时应用 SynchronizedLock class 作为锁对象
public class SynchronizedLock {
public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();
new Thread(new RunnableThread())
{
@Override
public void run() {synchronizedLock.mm2();
}
}.start();
new Thread(new RunnableThread())
{
@Override
public void run() {SynchronizedLock.mm();// 应用锁的对象是 SynchronizedLock.class
}
}.start();}
// 同步静态方法
public synchronized static void mm()
{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
public void mm2()
{synchronized (SynchronizedLock.class)// 常量作为以后对象
{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
同步代码块和同步办法如何抉择
public class SynchronizedLock {
public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();
new Thread(new RunnableThread())
{
@Override
public void run() {
try {synchronizedLock.mm2();
} catch (InterruptedException e) {e.printStackTrace();
}
}
}.start();
new Thread(new RunnableThread())
{
@Override
public void run() {
try {synchronizedLock.mm2();// 应用锁的对象是 SynchronizedLock.class
} catch (InterruptedException e) {e.printStackTrace();
}
}
}.start();}
// 同步实例办法 锁的粒度粗 执行效率低
public synchronized void mm() throws InterruptedException {long starttime= System.currentTimeMillis();
System.out.println("start");
Thread.sleep(3000);
for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);
}
System.out.println("end");
long Endtime= System.currentTimeMillis();
System.out.println(Endtime-starttime);
}
// 同步代码块 锁的粒度细 并发效率高
public void mm2() throws InterruptedException {System.out.println("start");
Thread.sleep(3000);
synchronized (this)// 常量作为以后对象
{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
System.out.println("end");
}
}
在执行同步办法的时候,两次线程调用每次都须要休眠三秒,而同步代码块同时启动线程都先筹备三秒,效率比拟高
脏读
public class Test06 {
public static void main(String[] args) throws InterruptedException {User user=new User();
SubThread subThread=new SubThread(user);
subThread.start();
user.GetName();}
static class SubThread extends Thread
{
public User user;
public SubThread(User user)
{this.user=user;}
@Override
public void run() {user.SetValue("ww","456");
}
}
static class User
{
private String name="ylc";
private String pwd="123";
public void GetName()
{System.out.println(Thread.currentThread().getName()+"==>"+name+"明码"+pwd);
}
public void SetValue(String name,String pwd)
{System.out.println("原来为为 name="+this.name+",pwd="+this.pwd);
this.name=name;
this.pwd=pwd;
System.out.println("更新为 name="+name+",pwd="+pwd);
}
}
}
涨姿态了!原来这才是多线程正确实现形式
在批改数据还没有实现的时候,就读取到了原来的数据,而不是批改之后的
呈现脏读的起因是对共享数据的批改和读取不同步引起的
解决办法是对批改和读取的办法进行同步办法上加上 synchronized 关键字
涨姿态了!原来这才是多线程正确实现形式
线程出现异常开释锁
如果在同步办法中,一个线程呈现了异样,会不会没有开释锁,其余在期待的线程就在始终期待,论证:
public class SynchronizedLock {
public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();
new Thread(new RunnableThread())
{
@Override
public void run() {synchronizedLock.mm();
}
}.start();
new Thread(new RunnableThread())
{
@Override
public void run() {synchronizedLock.mm2();
}
}.start();}
// 同步实例办法
public synchronized void mm()
{for (int i = 0; i <100 ; i++) {if(i==50)
{Integer.parseInt("abc");// 异样设置
}
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
public void mm2()
{synchronized (this)
{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
涨姿态了!原来这才是多线程正确实现形式
同步过程中线程出现异常,会主动开释锁对象,以供下一个线程继续执行
死锁
多线程中可能须要应用多个锁,如果获取锁的程序不统一,可能导致死锁。
public class Text06_5 {
public static void main(String[] args) {SubThread subThread=new SubThread();
SubThread subThread2=new SubThread();
subThread.setName("a"); subThread2.setName("b");
subThread.start();subThread2.start();
}
static class SubThread extends Thread
{private static final Object lock1=new Object();
private static final Object lock2=new Object();
@Override
public void run() {if("a".equals(Thread.currentThread().getName()))
{synchronized (lock1)
{System.out.println("a 线程 lock1 取得了锁, 再须要取得 lock2");
synchronized (lock2)
{System.out.println("a 线程 lock2 取得了锁");
}
}
}
if("b".equals(Thread.currentThread().getName()))
{synchronized (lock2)
{System.out.println("b 线程 lock2 取得了锁, 再须要取得 lock1");
synchronized (lock1)
{System.out.println("b 线程 lock1 取得了锁");
}
}
}
}
}
}
涨姿态了!原来这才是多线程正确实现形式
程序还在运行,却进入了卡死状态,a 线程失去了 lock1,要想把该线程开释的执行上面的代码获取 lock2,而 lock2 被 b 线程获取无奈开释,呈现了鹬蚌相争的状况。
防止死锁:当须要取得锁时,所有线程取得锁的程序统一,a 线程先锁 lock1,再锁 lock2,b 线程同理,就不会呈现死锁了。