synchronized:
synchronized 三种方式来加锁
1.修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
2.静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
3.修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
synchronize基本用法
synchronized原理分析
Java对象头和monitor是实现synchronized的基础。
线程在获取锁的时候,实际上就是获得一个监视器对象(monitor) ,monitor 可以认为是一个同步对象,所有的Java 对象是天生携带 monitor。而monitor是添加Synchronized关键字之后独有的。synchronized同步块使用了monitorenter和monitorexit指令实现同步,这两个指令,本质上都是对一个对象的监视器(monitor)进行获取,这个过程是排他的,也就是说同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。
线程执行到monitorenter指令时,会尝试获取对象所对应的monitor所有权,也就是尝试获取对象的锁,而执行monitorexit,就是释放monitor的所有权
参考帖子
sync 和 Lock的区别
synchronized是关键字,是JVM层面的底层啥都帮我们做了,而Lock是一个接口,是JDK层面的有丰富的API。
synchronized会自动释放锁,而Lock必须手动释放锁。
synchronized是不可中断的,Lock可以中断也可以不中断。
通过Lock可以知道线程有没有拿到锁,而synchronized不能。
synchronized能锁住方法和代码块,而Lock只能锁住代码块。
Lock可以使用读锁提高多线程读效率。
synchronized是非公平锁,ReentrantLock可以控制是否是公平锁。
ThreadLocal详解:ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的”,每个线程都只能看到自己线程的值,这也就是ThreadLocal的核心作用:实现线程范围的局部变量
ThreadLocal应用场景内存泄漏,数据库链接
公平锁与非公平锁
锁Lock分为“公平锁”和“非公平锁”。
公平锁:表示线程获取锁的顺序是按照线程加锁的顺序来的进行分配的,即先来先得FIFO先进先出顺序。
非公平锁:一种获取锁的抢占机制,是随机拿到锁的,和公平锁不一样的是先来的不一定先拿到锁,这个方式可能造成某些线程一直拿不到锁,结果就是不公平的·。
ReentrantReadWriteLock类
类ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock()方法后的任务。这样虽然保证了实例变量的线程安全性,但是效率低下。所以在Java中提供有读写锁ReentrantReadWriteLock类,使其效率可以加快。在某些不需要操作实例变量的方法中,完全可以使用ReentrantReadWriteLock来提升该方法代码运行速度。
读写锁表示两个锁:
读操作相关的锁,也成为共享锁。
写操作相关的锁,也叫排他锁。
多个读锁之间不互斥,读锁与写锁互斥,多个写锁互斥。
在没有线程Thread进行写入操作时,进行读操作的多个Thread可以获取读锁,但是进行写入操作时的Thread只有获取写锁后才能进行写入操作。
volatile的用法
volatile通常被比喻成"轻量级的synchronized",也是Java并发编程中比较重要的一个关键字。和synchronized不同,volatile是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。
volatile的用法比较简单,只需要在声明一个可能被多线程同时访问的变量时,使用volatile修饰就可以了。
Executors提供四种线程池
1、newCachedThreadPool创建⼀个可缓存线程池,如果线程池⻓度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2、newFixedThreadPool 创建⼀个定⻓线程池,可控制线程最⼤并发数,超出的线程会在队列中等待。
3、newScheduledThreadPool 创建⼀个定⻓线程池,⽀持定时及周期性任务执⾏。
4、newSingleThreadExecutor 创建⼀个单线程化的线程池,它只会⽤唯⼀的⼯作线程来执⾏任务,保证所有任务
自定义线程池
可以用ThreadPool Executor类创建,它有多个构造方法来创建线程池,用该类很容易实现自定义的线程池
//创建等待队列
BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(10);
// 无界队列。使用无界队列
LinkedBlockingQueue<Runnable> linkedBlockingQueue = new LinkedBlockingQueue<Runnable>();
//创建线程池,池中保存的线程数为4,允许的最大线程数为5
ThreadPoolExecutor pool = new ThreadPoolExecutor(4,5,50,TimeUnit.MILLISECONDS,bqueue);
参数说明:
corePoolSize:线程池中所保存的核心线程数,包括空闲线程。
maximumPoolSize:池中允许的最大线程数。
keepAliveTime:线程池中的空闲线程所能持续的最长时间。
unit:持续时间的单位。
workQueue:任务执行前保存任务的队列,仅保存由execute方法提交的Runnable任务。
比较Executor和new Thread()
new Thread的弊端如下:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
线程创建的四种方式
第一种:继承Thread类重写run方法......
class Demo3 extends Thread{
@Override
public void run() {
System.out.println("线程demo3");
}
}
第二种:实现Runnable接口....
static class Demo2 implements Runnable {
@Override
public void run() {
System.out.println("demo2");
}
}
第三种:实现CallAble<T>接口,重写call方法.....
class MyCallable implements Callable<String>(){
//接口的泛型就是返回值类型 可以抛异常,有返回值
public String call()throws Exception{.....}
return "...."
}
FutureTask<String> task=new FutureTask<>(new MyCallable());
new Thread(task,"MyCallable").start();
第四种:通过线程池进行创建,用线程池执行Runnable和Callable的实例。
线程的生命周期
第一种状态:初始态,就是new出来还没调用start方法;
第二种状态:运行态,这里需要注意了就绪也是运行状态
包括正在运行中和就绪,这两种都获得了运行所需要的资源等待CPU的调度即可
第三种状态:阻塞态,指没有获取到执行所需要的锁;
第四种状态:等待态,指正在运行中的线程调用了某些方法,比如时wait方法
此时释放了锁同时也没有了CPU的执行权,必须等待其他线程的唤醒,
比如notify或者notifyAll方法,醒来之后如果获取到了锁就会进入
运行态,如果获取锁失败则进入阻塞状态;
第五种状态:超时等待态,指正在执行中线程调用了某些方法,比如sleep方法
此时该线程没有了CPU执行权,但是却没有释放锁,
休眠时间到了会自动进入运行状态;
第六种:终止态,线程运行完了。
网友评论