线程池和多线程编程细节

线程池

创建线程需要花费昂贵的资源和时间,如果每次都是任务到来时才创建线程对象,那么响应时间会边 长,而且一个进程能够创建的线程数有限。为了避免这些问题,在程序启动时就创建若干个线程对象用 于响应处理,这个就称为线程池,里面的每个线程就叫工作线程

从JDK1.5开始Java提供了Executor框架可以创建不同的线程池。例如单线程池,每次只能处理一个任 务;数目固定的线程池或者缓存线程池等

使用线程池的优势

  • 重用存在的线程,减少对象的创建和消亡的开销,性能较好
  • 可以有效地控制最大并发线程数,提供系统资源的利用率,同时避免过多的资源竞争
  • 提供了定时执行、定期执行、单线程、并发数控制等功能

工作原理

1、线程池首先判断核心线程池中的线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行 任务;如果核心线程池中的线程都在执行任务,则执行第二步

2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在工作队列中进行等 待。如果工作队列已满则执行第三步

3、线程池判断线程池的线程是否都处于工作状态【核心线程+非核心线程】,如果没有,则创建一个新 得工作线程来执行任务;如果线程池已经达到上限,则交给饱和策略进行处理这个任务

请添加图片描述

线程池的使用

在ali的文档中不建议直接使用java封装好的线程池,建议直接使用ThreadPoolExecutor自己定制线程池

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long
keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue)

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long
keepAliveTime, TimeUnit unit,BlockingQueue<Runnable>
workQueue,RejectedExecutionHandler handler)
  • corePoolSize设置线程池的核心大小
  • maximumPoolSize线程池中允许的最大线程数
  • workQueue阻塞工作队列

请添加图片描述

固定大小的线程池源码

public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(nThreads, nThreads,0L,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}

选用

  • 一般创建线程对象时不建议使用extends Thread方法:单根继承体系
  • 如果没有返回结果建议使用Runnable接口
  • 如果有返回值建议使用Callable接口
  • 如果使用线程比较频繁建议使用线程池,ThreadPoolExecutor

守护线程

正常情况下,非守护线程的执行时间和主线程无关,即使主线程已经结束,也不会影响子线程【为了说 明方便,在Java中所有的线程都是平级的】的运行

public class Test1 {
	public static void main(String[] args) {
		System.out.println("开始程序......");
		new Thread(()->{
			for(int i=0;i<1000;i++)
				System.out.println(Thread.currentThread()+":"+i);
		}).start();
		System.out.println("结束程序......");
	}
}

守护线程就是为其它线程提供服务的线程。守护线程的特点是当程序中的主线程结束时,守护线程会自动终止。

public class Test2 {
	public static void main(String[] args) {
		System.out.println("开始程序......");
		Thread t = new Thread(() -> {
			int i=0;
			while (true) {
				System.out.println(Thread.currentThread() + ":" + i++);
			}
		});
		t.setDaemon(true);
		t.start();
		System.out.println("结束程序......");
	}
}

如何设置一个线程为守护线程

  • 在一个线程调用start启动之前,调用方法thread.setDaemon(true)就可以将 thread线程设置为守 护线程

守护线程一般应该是一个独立的线程,run方法一般是无限寻循环

线程组

Thread类中toString方法的定义

public String toString() {
	ThreadGroup group = getThreadGroup(); //获取当前线程对象所属的线程组
	if (group != null) {
		return "Thread[" + getName() + "," + getPriority() + "," +
		group.getName() + "]"; //输出格式为【Thread[线程名称,优先级,线程组的名称]】
	} else {
		return "Thread[" + getName() + "," + getPriority() + "," + "" + "]";
	}
}

请添加图片描述

所有线程一般都会隶属于某个线程组,也可以是默认线程组【main】,也可以是创建线程时明确指定的 一个组

ThreadGroup group =new ThreadGroup("myGroup"); //创建一个线程组
Thread t=new Thread(group,"myThread");//创建线程对象时可以指定对应的线程组
ThreadGroup tg=t.getThreadGroup(); //获取线程对象所属的线程组

说明:

  • 在创建之初,线程被限制到一个组里,而且不能改变到不同的组
  • new Thread(group,“myThread”);
  • 如果创建多个线程而不指定线程组,它们就会与创建这个线程的线程在同一个组中

使用线程组创建线程对象的方法

  • public Thread(ThreadGroup group, Runnable target)
  • public Thread(ThreadGroup group, String name)
  • public Thread(ThreadGroup group, Runnable target, String name)

线程组的使用

public class Test5 {
	public static void main(String[] args) {
//这个线程组yan1是在main线程中创建的,所以系统识别yan1为main线程组的后代线程组
		ThreadGroup group = new ThreadGroup("yan1");
		Thread t1=new Thread(
				group,
				()->{
			int res=0;
			for(int i=0;i<10000000;i++) {
				//System.out.println(Thread.currentThread());
				//System.out.println("");
				res+=i;
			}
		});
		t1.start();
		System.out.println(Thread.currentThread()); //Thread[main,5,main]
//线程组的用途
		ThreadGroup tg=Thread.currentThread().getThreadGroup(); //获取当前线
程所属的线程组 main
		int acc=tg.activeCount();//获取main线程组下的所有活跃线程的线程数
		Thread[] arr=new Thread[acc];
//获取main线程组下中所有的线程对象,包括后代线程组中,并将其存放到arr数组中
//当前main线程组中应该有2个线程:1、main主线程,2、这里启动的线程t1
		tg.enumerate(arr);
//可以通过获取到的线程对象应用针对线程组中的所有活跃线程进行操作
		for(Thread tmp:arr) {
			System.out.println("\t"+tmp);
		}
/* 输出
* Thread[main,5,main]
* Thread[Thread-0,5,yan1]
*/
//以树状结构显示所有线程组信息
		tg.list();
/* 输出结果为:
* java.lang.ThreadGroup[name=main,maxpri=10]
* Thread[main,5,main]
* java.lang.ThreadGroup[name=yan1,maxpri=10]
* Thread[Thread-0,5,yan1]
*/
	}
}

线程组的主要用途:

可以通过线程组,对线程组中的一组线程进行统一管理,而不是一个一个的进行管理

ThreadGroup tg=Thread.currentThread().getThreadGroup();
int acc=tg.activeCount();//获取main线程组下的所有活跃线程的线程数
Thread[] arr=new Thread[acc];
tg.enumerate(arr);

注意:

显示线程时,如果线程显示为null,表示这个线程已经执行完毕,线程已经消亡。 在具体开发中很少使用

多线程编程细节

线程池应用编程实现

  • 使用固定大小的线程池
public class Test1 {
	public static void main(String[] args) throws Exception{
		ExecutorService es = Executors.newFixedThreadPool(3);
// 计算1+2+3+...+1000,启动10个工作任务
		Future[] fs = new Future[10];
		for(int i=0;i<10;i++) {
			int begin=i*100+1;
			int end=(i+1)*100;
			Callable<Integer> caller=new MyCallable(begin, end);
			fs[i]=es.submit(caller);
		}
		int res=0;
		for(int i=0;i<fs.length;i++) {
			Object kk=fs[i].get();
			if(kk!=null && kk instanceof Integer) {
				res+=(Integer)kk;
			}
		}
		es.shutdown();
		System.out.println("1+2+3+...+1000="+res);
	}
		public static class MyCallable implements Callable<Integer>{
			private int begin,end;
			public MyCallable(int begin,int end) {
				this.begin=begin;
				this.end=end;
			}
			@Override
			public Integer call() throws Exception {
				System.out.println(Thread.currentThread());
				int res=0;
				for(int i=begin;i<=end;i++)
					res+=i;
				return res;
		}
	}
}
  • 使用缓存线程池
public class Test2 {
	public static void main(String[] args) throws Exception{
		ExecutorService es = Executors.newCachedThreadPool();
// 计算1+2+3+...+1000,启动10个工作任务
		Future[] fs = new Future[10];
		for(int i=0;i<10;i++) {
		int begin=i*100+1;
		int end=(i+1)*100;
		Callable<Integer> caller=new MyCallable(begin, end);
		fs[i]=es.submit(caller);
	}
		int res=0;
		for(int i=0;i<fs.length;i++) {
			Object kk=fs[i].get();
			if(kk!=null && kk instanceof Integer) {
				res+=(Integer)kk;
			}
		}
		es.shutdown();
		System.out.println("1+2+3+...+1000="+res);
	}
	public static class MyCallable implements Callable<Integer>{
	private int begin,end;
	public MyCallable(int begin,int end) {
		this.begin=begin;
		this.end=end;
	}
		@Override
		public Integer call() throws Exception {
			System.out.println(Thread.currentThread());
			int res=0;
			for(int i=begin;i<=end;i++)
				res+=i;
			return res;
		}
	}
}

ThreadPoolExecutor

在具体开发中一般不建议直接使用系统预定义的线程池【ali】,而是通过ThreadPoolExecutor进行自定义线程池

ThreadPoolExecutor是线程池框架的一个核心类,线程池提供了线程对象的复用机制,并对线程进行统 一管理

  • 降低系统资源消耗
  • 提高响应速度
  • 提高线程的可管理性

线程池的运行状态有5种

  • Running高3位位111,可以接收新任务并处理阻塞队列中的任务
  • shutdown高3位为000,不接收新任务但是会处理阻塞队列中的任务
  • stop高3位为001,不会接收新任务,也不会处理阻塞队列中的任务,并且中断正在运行的任务
  • tidying高3为010,所有任务都终止,工作线程数为0
  • terminated高3为011,中断任务执行阶段结束

构造器参数

public static ExecutorService newCachedThreadPool() {
	return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
					60L, TimeUnit.SECONDS,
					new SynchronousQueue<Runnable>());
}

构造器的定义

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long
keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory
threadFactory,RejectedExecutionHandler handler)
  • corePoolSize用于配置线程池中核心线程数。

  • maximumPoolSize用于设置线程池中允许的最大线程数。当阻塞队列满了之后,继续提交任务则 创建新的线程执行任务

  • keepAliveTime用于配置空闲线程的存活时间。默认情况下只有当线程池的线程数大于

  • corePoolSize这个参数才会生效。当一个工作线程的空闲时间到达这个值时,这个线程会自动终 止,直到线程数不超过corePoolSize为止。一般核心线程不会自动终止,除非调用方法

  • allowCoreThreadTimeout(boolean)配置

  • unit就是keepAliveTime参数的时间单位,是一个枚举类型值

  • workQueue任务缓存队列,用于存放等待执行的任务。如果当前线程数为corePoolSize时,继续 提交的任务就会被缓存到任务队列中,等待被调度执行。

    • SynchronousQueue是一个不存储元素的阻塞队列,每个插入操作必须等待另外线程调用移 除操作,否则插入操作一直处于阻塞等待状态
    • LinkedBlockingQueue是一种基于链表结构的阻塞队列,是一种无界队列
    • ArrayBlockingQueue是一个基于数组结构的有界阻塞队列,会按照FIFO的规则排序任务
  • ThreadFactory线程工作,用于创建新线程时使用的工厂类

  • handler设置任务拒绝策略,当阻塞队列满,而且线程池中线程数已经到达了maximumPoolSize, 如果继续提交任务,就会采用任务拒绝策略处理新任务

    • AbortPolicy丢弃任务,并且抛出异常,是默认拒绝策略
    • CallerRunsPolicy由调用execute方法提交任务的线程自己执行任务
    • DiscardPolicy丢弃任务,但是不抛出异常
    • DiscardOldestPolicy 丢弃任务队列中最前面的任务,然后重新尝试执行任务

同时也允许根据应用场景实现RejectedExecutionHandler接口自定义饱和策略,例如记录日志或者 持久化存储不能处理的任务

Executors

Executors是一个用户创建线程池的工具类

  • newFixedThreadPool用于创建一个固定大小的定长线程池,可以控制线程的最大并发数,超出的 线程会在队列中等待
    • 因为采用的是无界队列,而且实际线程数永远不会变化,适用于可以预测线程数量的业务中, 或者服务器负载较重的场景下,对当前的线程数量进行限制
    • 由于是无界队列可以会导致大量的任务积压
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L,
TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
//核心线程数=最大线程数,所以keepAliveTime设置无效,任务队列采用LinkedBlockingQueue无界队列
}
  • newCachedThreadPool用于创建一个可缓存的线程池,如果线程池长度超过处理需求,可以灵活 的回收空闲线程资源,如果没有可回收重用线程时会自动新建线程
    • 可以用来创建一个可以无限扩大的线程池(最大线程数Integer.MAX_VALUE),适用于服务 器负载较轻,执行很多短期异步任务的场景下
public static ExecutorService newCachedThreadPool() {
	return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
//核心线程数0<最大线程数Integer.MAX_VALUE,所以keepAliveTime设置有效,一个线程最大空闲时间为60s
}
  • newSingleThreadExecutor用于创建只有一个线程的定长线程值,只用唯一的工作线程来执行任 务,保证所有的任务按照FIFO或者LIFO顺序执行
    • 适用于需要保证顺序执行各个任务的场景下,而且需要保证在任意的时间点上不会出现多个任 务同时执行
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1,
1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
核心线程数1=<最大线程数1,所以所以keepAliveTime设置无效,任务队列采用LinkedBlockingQueue无界队列
}
  • newScheduledThreadPool创建一个变长的线程池,并且支持定时及周期性任务的执行
    • 可以延时启动、定时执行的线程池,适用于需要多个后台线程执行周期性任务的场景下
public static ScheduledExecutorService newScheduledThreadPool(int
corePoolSize) {
	return new ScheduledThreadPoolExecutor(corePoolSize);
}

public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor
implements ScheduledExecutorService

public ScheduledThreadPoolExecutor(int corePoolSize) {
	super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new
DelayedWorkQueue());
}
  • newWorkStealingPool创建一个拥有多个任务队列的线程池
    • 创建当前可用cpu数量的线程来并行执行,适用于比较耗时的操作同时并行执行的场景下

提交任务的方式

线程池提供了2种任务提交方法submit和execute,一般通过submit提交任务是用于可以有返回值的时 候,通过execute提交的任务不能获取任务的执行结果

execute的方法

public class Test1 {
	public static void main(String[] args) {
		ExecutorService es=Executors.newFixedThreadPool(3);
		es.execute(()->{
			for(int i=0;i<10;i++) {
				System.out.println("Hello "+i);
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		System.out.println("main........");
	}
}

submit的方法

ExecutorService es=Executors.newFixedThreadPool(3);
		es.submit(()->{
			for(int i=0;i<10;i++) {
				System.out.println("Hello "+i);
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}				
			}
		});
		System.out.println("main........");

ExecutorService es = Executors.newFixedThreadPool(3);
		Future f = es.submit(() -> {
			for (int i = 0; i < 10; i++) {
				System.out.println("Hello " + i);
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	});
f.get(); //可以通过get接收子线程的执行结果,也可以到达阻塞主线程,等待子线程执行完成的效
果
System.out.println("main........");

sleep 和 wait 方法

Thread.sleep(long)/(long ms, long ns) 休眠阻塞 long ms,不会释放锁 obj.wait()/(long) 阻塞当前线程,等待队列,会释放锁

waitsleep
同步要求同步上下文不需要同步上下文
作用对象作用域对象本身object中的方法Thread 类中的静态方法,作用于当前线程
释放锁释放不释放
唤醒notify/notifyAll超时或者interrupt
方法属性实例方法Thread类中的静态方法

多线程调度机制

基于时间片轮转法的抢占式模型

线程优先级

1-10,默认 5

优先级映射的问题

Thread 类中的 3 个常量

GC 垃圾回收

线程同步

多线程的运行过程不可重现

synchronized 关键字

用途:保证一个时刻点上只有一个线程在执行,不会出现并发的情况,达到排队执行的目的

synchronized 的用法

3 种不同的用法:对象锁、类锁和同步块。注意:synchronized 不能修改构造器、变量等, synchronized 支持重入

同步实例方法

修饰实例方法,作用域当前实例加锁,进入同步方法需要获取当前实例的锁。

方法上添加 synchronized 关键字,例如 public synchronized void show(

注意:在一个类对象种所有的同步方法都是互斥。

1、只要有一个线程进入了当前类对象的同步方法,则不允许其它线程再次进入当前对象的 任何同步方法,但是允许进入非同步方法

2、同样当前线程可以进入当前类对象的其它同步方法(重入),也允许进入非同步方法。 当线程进入同步方法,则获取同步锁,离开同步方法则自动释放锁

3、这个锁就是当前类对象

请添加图片描述

操作账户类对象时只要账户对象是一个,则多线程执行 withdraw 和 disposit 操作时会串 行执行。同步锁就是当前账户对象,不可能出现同时操作的问题

这种方法不是最佳选择,因为同步处理的颗粒度太大了,所有当前对象种的同步方法都是互 斥的,会影响并发性

线程安全的类:是通过使用同步方法的类,同步监视器是 this

1、该类对象可以被多线程安全的访问

2、每个线程调用该对象的任意方法后都将获取正确的结果

3、每个线程调用该对象的任何方法后,该对象状态依然保持合理状态

String、StringBuilder 和 StringBuffer

  • StringBuilder 是线程不安全的类,数据不安全,但是并发执行效率高,一般用于定义临时 变量
  • StringBuffer 是线程安全的类,数据安全,但是并发执行效率第,一般用于定义属性
同步静态方法

修改静态方法,作用于当前类对象加锁,进入同步静态方法需要获取当前类对象的锁

同步监视器是当前类

public class NumOper {
private static int num=0;
public static synchronized void add(){
int cc=num;//缓存数据
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
cc++;
num=cc;
System.out.println("数据:"+cc);
}
public static void main(String[] args) {
Thread[] ts=new Thread[10];
for(int i=0;i<ts.length;i++){
ts[i]=new Thread(()->{
//构建 10 个 NumOper 对象,如果使用实例同步方法,则不能达到锁定的目的
NumOper no=new NumOper();
no.add();
});
ts[i].start();
}
for(Thread t:ts)
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(NumOper.num);
}
}

注意:如果在方法上去掉 static,则不能实现锁的效果,因为没有 static 则是采用对象锁, 但是在线程种创建了 10 个对象,各个对象之间没有任何关系,所以不能达到锁定的效果, 但是如果添加 static,则采用类锁,类只有一个,所以可以达到互斥锁定的效果

普通成员方法同步采用的是当前对象充当锁,静态方法采用是类作为锁 如果需要增加灵活性,可以引入同步代码块,将锁对象作为参数进行传递

同步代码块

使用 synchronized 修饰代码块时同时指定加锁对象,对给定对象加锁,进入同步代码块的 前提时获取指定对象的锁。建议在可能被并发访问的 goon 共享临界资源使用,通过这种方 法可以保证并发线程在任何一个时刻只有线程可以进入修改共享的临界资源的代码块(临界 区) 例如 synchronized(account){},其中 account 对象充当锁或者监视器,同步监视器可以阻 止多个线程同时修改同一个 account 对象。任何时刻只能有一个线程可以获取同步监视器 的锁定,当同步代码块执行完成后,该线程就睡释放该同步监视器的锁定。

代码验证和理解

两个线程分别调用同一个对象种的不同方法 aaa 和 bbb,一个同步一个不同步,会不会有 等待问题

结论:同步方法之间可以达到互斥等待的目的,同步和非同步方法之间没有任何影响

public class Test1 {
public static void main(String[] args) {
App1 app1=new App1();
new Thread(){
public void run(){
app1.aaa();
}
}.start();
new Thread(){
public void run(){
app1.bbb();
}
}.start();
}
public static class App1 {
public void aaa() {
long k1 = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "..." + k1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void bbb() {
long k1 = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "..." + k1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

两个方法都有同步

结论:会出现一个等待另外一个的情况

一个静态一个非静态

结论:没有出现等待,这是因为静态使用的是类锁,非静态使用的是对象锁。两者并没有出 现互斥

两个方法都是静态

结论:出现等待,因为都使用使用的类锁。即使是创建多个对象,仍旧会出现互斥等待的效 果。这是因为不管创建多少个对象,但是类只有一个

synchronized 底层原

sychronized 作用:

1、原子性:synchronized 保证语句块内操作是原子的

2、可见性:synchronized 保证可见性(通过在执行 unlock 之前,必须先把此变量同步回 主内存实现)

3、有序性:synchronized 保证有序性(通过一个变量在同一时刻只允许一条线程对其进 行 lock 操作)

底层原理的话术:

同步代码块在源代码编译成字节码时,同步代码部分是利用 monitorenter 和 monitorexit 这两个字节码指令。它们分别位于同步代码块的开始和结束位置。当 jvm 执行到 monitorenter 指令时,当前线程试图获取 monitor 对象的所有权,如果未加锁或者已经被 当前线程所持有,就把锁的计数器+1;当执行 monitorexit 指令时,锁计数器-1;当锁计 数器为 0 时,该锁就被释放了。如果获取 monitor 对象失败,该线程则会进入阻塞状态, 直到其他线程释放锁。

1、synchronized 是可重入的,所以不会自己把,自己锁死

2、synchronized 锁一旦被一个线程持有,其他试图获取该锁的线程将被阻塞。 同步方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作 之 中 。 JVM 可 以 从 方 法 常 量 池 中 的 方 法 表 结 构 method_info Structure 中 的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将 会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有 monitor 管程, 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时 释放 monitor。

monitor 管程

任何对象都管理一个管程,管程就是控制对象并发访问的一种机制,是一个同步原语。管程 用于提供一种排他的访问机制

对象的内存布局

请添加图片描述

虚拟机对象头分配问题

JVM 创建对象是先申请内存,然后对内存仅从数据填充。数据填充包括设置对象头,例如 将 OOP 对应的 Klass 属性作为初始值填充,如果使用了锁机制,还需要填充对应的锁标志值

请添加图片描述

锁的状态: 无锁,对象头开辟 54bit 空间存放对象的 hashcode 值,4bit 用于存放分代年龄,1bit 于存放是否使用偏向。无锁表示没有对资源进行锁定,所有的线程都可以对同一个资源进行 访问,但是只能一个线程进行修改成功,例如 CAS 模型实现 偏向锁,适合只有一个线程访问的同步场景。如果存在多线程竞争则会带来额外的锁撤销消 耗;但是加锁和解锁消耗低 轻量级锁:适用于追求响应时间的应用场景下,可能会出现始终无法获取资源,自旋操作会 消耗 CPU,但是可以提供响应速度。 重量级锁:适用于追求高吞吐量的场景下,得不到锁的线程会阻塞,性能比较差,但是阻塞 是不需要消耗 CP

何时需要同步处理

多个线程访问互斥可交换的数据时,应该采用同步以保护数据的安全性,避免出现两个线程 同时修改数据的情况

1、对于非静态字段种可更改的数据,通常使用非静态同步方法访问

2、对应静态字段种可以更改的数据,通常使用静态方法访问

何时释放锁

同步代码执行结束

同步方法种遇到 break

小结

1、线程同步的目的在于保护多个线程同时访问一个资源数据时,对数据的破坏【修改】

2、线程同步是通过锁(监视者)来实现的,每个对象都有且仅有一个锁,这个锁和一个特 定的对象关联,线程一旦获取到了对象锁,其它访问对象的线程则无法访问该对象的同步方 法(可以访问静态同步方法)

3、对于静态方法,锁是针对这个类的,锁对象是该类的 Class 对象,静态方法和非静态方 法的锁互补干预。一个线程获取到了对象锁,还可以继续访问静态方法获取类锁。

4、对于同步处理,要清晰在哪个对象上同步

于存放分代年龄,1bit 于存放是否使用偏向。无锁表示没有对资源进行锁定,所有的线程都可以对同一个资源进行 访问,但是只能一个线程进行修改成功,例如 CAS 模型实现 偏向锁,适合只有一个线程访问的同步场景。如果存在多线程竞争则会带来额外的锁撤销消 耗;但是加锁和解锁消耗低 轻量级锁:适用于追求响应时间的应用场景下,可能会出现始终无法获取资源,自旋操作会 消耗 CPU,但是可以提供响应速度。 重量级锁:适用于追求高吞吐量的场景下,得不到锁的线程会阻塞,性能比较差,但是阻塞 是不需要消耗 CP

何时需要同步处理

多个线程访问互斥可交换的数据时,应该采用同步以保护数据的安全性,避免出现两个线程 同时修改数据的情况

1、对于非静态字段种可更改的数据,通常使用非静态同步方法访问

2、对应静态字段种可以更改的数据,通常使用静态方法访问

何时释放锁

同步代码执行结束

同步方法种遇到 break

小结

1、线程同步的目的在于保护多个线程同时访问一个资源数据时,对数据的破坏【修改】

2、线程同步是通过锁(监视者)来实现的,每个对象都有且仅有一个锁,这个锁和一个特 定的对象关联,线程一旦获取到了对象锁,其它访问对象的线程则无法访问该对象的同步方 法(可以访问静态同步方法)

3、对于静态方法,锁是针对这个类的,锁对象是该类的 Class 对象,静态方法和非静态方 法的锁互补干预。一个线程获取到了对象锁,还可以继续访问静态方法获取类锁。

4、对于同步处理,要清晰在哪个对象上同步

5、编写线程安全的类,需要注意对多个线程竞争访问资源的逻辑和安全进行判断,对需要 具有原子性操作的步骤做出正确的分析,并加锁 锁会引入一个新问题:死锁问题。死锁是线程之间相互等待锁释放所引发的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值