文章目录
- 进程
- 线程
- 进程和线程的区别
- 线程的组成
- 线程执行的特点
- 创建线程
- 获取线程ID和名称
- 线程在JVM中的表现形式
- 线程的状态(基本)
- 常见方法
- 线程的优先级
- 线程打断
- 守护线程
- 线程的状态(等待)
- 线程安全问题
- 线程同步一
- 线程的状态(阻塞)
- 线程状态
- 线程同步二
- 同步规则
- 死锁
- 线程通信
- 线程池概念。
- 线程池原理
- 获取线程池
- Callable接口
- Future接口
- Lock接口
- 重入锁
- 读写锁
- Condition
- 面试题
- synchronized和Lock的区别
- 线程安全的集合
- Collections中的工具方法
- CopyOnWriteArrayList
- CopyOnWriteArraySet
- ConcurrentHashMap
- CAS算法
- 乐观锁和悲观锁
- Queue接口(队列)
- ConcurrentLinkedQueue
- BlockingQueue接口(阻塞队列)
- 阻塞队列
- 多线程三个特性
- 原子操作
进程
- 程序是静止的,只有真正运行时的程序,才被称为进程。
- 单核CPU在任何时间点上,只能运行一个进程;
- 宏观并行,微观串行。
线程
-
主线程可以创建子线程,mai方法就是主线程,
-
概念:线程,又称轻量级进程(Light Weight Process).程序中的一个顺序控制流程,同时也是CPU的基本调度单位。进程有多个线程组成,彼此间完成不同的工作,交替执行称为多线程。
进程和线程的区别
- 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。
- 一个程序运行后至少有一个进程。
- 一个进程可以包含多个线程,但至少需要有一个线程。
- 进程间不能共享数据段地址,但同进程的线程之间可以。
线程的组成
- 任何一个线程都具有基本的组成部分:
- CPU时间片:操作系统(OS)会为每个线程分配执行空间
- 运行数据:
- 堆空间:存储线程需要使用的对象,多个线程可以共享堆中的对象。
- 栈空间:存储线程需要使用的局部变量,每个线程都拥有独立的栈。
- 线程的逻辑代码。
线程执行的特点
- 线程抢占式执行
- 效率高
- 可防止单一线程长时间独占CPU
- 在单核CPU中,宏观上同时执行,微观上顺序执行。
创建线程
Thread Field
Thread Consturctor
-
Java虚拟机执行程序会自动创建一个主线程(main),可以使用主线程创建子线程。
-
方式一:继承Thread,从写run()方法.
public class TestCreateThread{
public static void main(String[]args){
MyThread thread=new MyThread();//创建线程对象。
thread.start(); //启动线程
}
}
class MyThread extends Thread{
public MyThread(){
super("MyThread"); //重写名称。
}
public void run(){
for(int i=1;i<=50;i++){
System.out.println(Thread.currentThead().getName()+":"+1);
}
}
}
-
1.继承Thread类
-
2.覆盖run()方法
-
3.创建子类对象
-
4.调用start()方法
-
方式二:实现Runnable接口
public class TestCreateThread{
public static void main(String[]args){
MyRunnable myRunnable=new MyRunnable();//任务.
Thread thread=new Thread(myRunnable);
thread.run();
thread.start();
}
}
class MyRunnable implements Runnable{ //接口实现类,
public void run(){
for(int i=1;i<=50;i++){
System.out.println("MyRunnable:"+i);
}
}
}
- 1.实现Runnable接口
- 2.覆盖run()方法
- 3.创建实现类对象
- 4.创建线程对象
- 5.调用start()方法
获取线程ID和名称
//Thread.currentThread().getName()//获取当前线程名称
//Thread.currentThread().setName()//设置当前线程名字
//Thread.currentThread().getId()//获取线程id
//threadName.setName();
//MyThread.java
public class MyThread extends Thread {
public MyThread(String name){
super(name);
}
@Override
public void run() {
// Thread.currentThread().setName("MyThread.");
for (int i = 0; i <50; i++) {
System.out.println("线程ID:"+Thread.currentThread().getId()+" 线程名称"+Thread.currentThread().getName()+":"+i);
System.out.println("线程ID:"+this.getId()+" 线程名称"+this.getName()+":"+i);
}
}
}
//main.java
public static void main(String[] args) {
//1.修改名称
MyThread myThread1=new MyThread("A");
MyThread myThread2=new MyThread("B");
//2.修改名称
myThread1.setName("A");
myThread2.setName("B");
myThread1.start();
myThread2.start();
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getId()+":"+i);
}
}
线程在JVM中的表现形式
-
1.继承Threa后线程在JVM中的内存图示。
-
2.实现Runnable接口,在JVM中的内存示意图.
-
线程的状态(基本)
常见方法
- 休眠:
- public static void sleep(long millis)//放在哪个线程中哪个线程就sleep().
- 当前想成主动休眠millis毫秒。
- 休眠后不会征用CPU。休眠时间到了后,进入就绪状态。
public static void main(String[] args) {
Runnable runnable=new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" ... "+i);
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
};
new Thread(runnable,"TigarLive").start();
}
- 放弃:
- public static void yield()
- 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。还会参加竞争时间片.吧CPU的使用权让给别的线程(优先级相等或更高的。)
thread.setPriority(1)//默认5,1-10
thread.getPriority();
Thread.currentThread().setPriority()
Thread.currentThread().getPriority()
Thread.currentThread().getName();
Thread.currentThread().getId();
public static void main(String[] args) {
Runnable runnable=new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" ... "+i);
Thread.yield();
}
}
};
Thread thread1=new Thread(runnable,"TigarLives");
Thread thread2=new Thread(runnable,"PickFishLive");
//thread.start();//放到这里也可以
thread1.start();
thread2.start();
}
- 结合:
- public final void join()
- **允许其他线程加入到当前线程中。**当前线程阻塞,直到加入线程执行完毕,才会继续执行。
public static void main(String[] args) {
Runnable runnable=new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" == "+i);
}
}
};
Thread thread=new Thread(runnable,"TigerLive");
thread.start();
Thread.currentThread().setName("PickFishTV");
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
线程的优先级
- thread.setPriority();默认为5,范围1-10,优先级越高拿到CPU时间片的机会越大(只会给比自己相同优先级或更高的,同时自己也会参加竞争。)
线程打断
- 线程对象.interrupt();
- 打断线程,被打断线程抛出InterruptedException异常,配合sleep.
public static void main(String[] args) {
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println("子线程开始休眠");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
System.out.println("我被叫醒了!");
}
System.out.println("干完活了。");
}
};
Thread thread=new Thread(runnable,"TigerLive");
thread.start();
System.out.println("20秒内输入任何字符借书子线程");
Scanner scanner=new Scanner(System.in);
scanner.next();
//打断子线程
thread.interrupt();
System.out.println("main thread ending.");
}
守护线程
- thread.setDeamon(true);//设置为后台线程,没有前台线程主线程就会结束,main方法为前台线程
public static void main(String[] args) {
Runnable runnable=new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//前台线程
Thread thread=new Thread(runnable,"TigerLive");
thread.setDaemon(true);//前台线程
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println("Main thread");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程的状态(等待)
线程安全问题
- 需求:A线程将“Hello"存入数组的第一个空位,B线程将”World“存入数组的第一个空位。
- 线程不安全;
- 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致
- 临界资源:共享资源(同一个对象),一次仅允许一个线程使用,才可保证其正确性。
- 原子操作:不可分割的多步操作,被视为一个整体,其顺序和步骤不可打乱或缺省。
线程同步一
同步
所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列
异步
所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了
-
同步代码块:
synchronized(临界资源对象){/对临界资源对象加锁 //代码(原子操作) }
-
临界资源必须是引用类型。
-
锁:一般选择共享资源作为锁,多个线程必须使用同一个锁。
-
注意:
- 每个对象都有一个互斥锁标记,用来分配给线程的。
- 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
- 线程退出同步代码块时,会释放相应的互斥锁标记。
// Ticket.java
public class Ticket implements Runnable {
private static int ticket=50;
private Object object=new Object();
@Override
public void run() {
while (true){
synchronized(object){
if(ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName()+":卖了第"+ticket+"张票");
this.ticket--;
}
}
}
}
Ticket ticket=new Ticket();
Thread thread1=new Thread(ticket,"Windows1");
Thread thread2=new Thread(ticket,"Windows2");
Thread thread3=new Thread(ticket,"Windows3");
Thread thread4=new Thread(ticket,"Windows4");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
线程的状态(阻塞)
线程状态
就绪和运行统称Runnable。
public enum State{
NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED
线程同步二
-
同步方法:
synchronized 返回值类型 方法名称(形参列表){//对当前对象(this)加锁 //代码(原子操作) }
-
注:
- 只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
- 线程退出同步方法时,会释放相应的互斥锁标记。
- 非静态方法,锁是this.
- 静态方法,锁是,类名.Class
public class Ticket implements Runnable {
private static int ticket = 50;
private Object object = new Object();
@Override
public void run() {
while (true) {
if(!sale()){
break;
}
}
}
//非静态方法,锁是this.
//静态方法,锁是,类名.Class
public synchronized boolean sale() {//this
if (ticket <= 0) {
return false;
}
System.out.println(Thread.currentThread().getName() + ":卖了第" + ticket + "张票");
this.ticket--;
return true;
}
}
同步规则
- 注意:
- 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
- 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
- 一直JDK中线程安全的类:
- StringBuffer
- Vector
- HashTable
- 以上类中的公开方法,均为synchronized修饰的同步方法。
死锁
- 思索:
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经又有的锁标记,由此可能造成死锁。
- 生产者,消费者:
- 若干个生产者在生产产品,这些产品将提供若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区取产品,也不允许生产者向一个满的缓冲区放入产品。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可以造成死锁。
//MyLock.java
public class MyLock {
public static Object locka=new Object();
public static Object lockb=new Object();
}
//Boy.java
public class Boy extends Thread {
@Override
public void run() {
synchronized (MyLock.locka){
System.out.println(Thread.currentThread().getName()+"take a");
synchronized (MyLock.lockb){
System.out.println(Thread.currentThread().getName()+"take b");
System.out.println("可以干活了");
}
}
}
}
//Girl.java
public class Girl extends Thread {
@Override
public void run() {
synchronized (MyLock.lockb) {
System.out.println(Thread.currentThread().getName() + "take b");
synchronized (MyLock.locka) {
System.out.println(Thread.currentThread().getName() + "take a");
System.out.println("可以干活了");
}
}
}
}
//Main.java
public class Main {
public static void main(String[] args) {
Boy boy=new Boy();
Girl girl=new Girl();
boy.start();
girl.start();
}
}
线程通信
Thread.sleep()和Thread.wait()的区别
1.sleep()是线程进入休眠状态,wait线程进入等待队列等待
2.sleep释放cpu了(CPU时间片),如果拿着锁,没有释放锁,wait释放了CPU,锁释放了。
- 等待;
- public final void wait()
- public final void wait(long timeout)
- 必须对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁,进入等待队列。
- 通知:
- public final void notify()
- public final void notifyAll()
- 必须在对obj加锁的同步代码块中。从obj的waiting 中释放一个或全部线程。对自身没有任何影响。
Example.------>存钱取钱
//BankCard.java
public class BankCard {
private double money;
private boolean flag;
public synchronized void save(double money){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.money+=money;
System.out.println(Thread.currentThread().getName()+"存钱了,");
this.flag=true;
this.notify();
}
public synchronized void take(double money){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.money-=money;
System.out.println(Thread.currentThread().getName()+"取钱了");
this.flag=false;
this.notify();
}
}
//AddMoney.java
public class AddMoney implements Runnable {
private BankCard bankCard;
public AddMoney(BankCard bankCard){
this.bankCard= bankCard;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
this.bankCard.save(1000);
}
}
}
//TakeMoney.java
public class TakeMoney implements Runnable {
private BankCard bankCard;
public TakeMoney(BankCard bankCard){
this.bankCard=bankCard;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
this.bankCard.take(1000);
}
}
}
//Main.java
public class Main {
public static void main(String[] args) {
BankCard bankCard=new BankCard();
AddMoney addMoney=new AddMoney(bankCard);
TakeMoney takeMoney=new TakeMoney(bankCard);
Thread thread1=new Thread(addMoney,"BoyFriend");
Thread thread2=new Thread(takeMoney,"GirlFriend");
thread1.start();
thread2.start();
}
}
如果四个人的话会造成钱有问题,修改问题if改为while,notify()改为notifyAll()。
线程池概念。
- 现有问题:
- 线程是宝贵的内存资源,单个线程约占1MB空间,过多分配易造成内存溢出。
- 频繁的创建及销毁线程会增加虚拟机回收频率,资源开销,做成程序性能下降。
- 线程池;
- 线程容量,可设定线程分配的数量上限。
- 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
- 避免频繁的创建和销毁。
线程池原理
- 将任务提交给线程池,有线程池分配线程,运行任务,并在当前任务结束后复用线程。
获取线程池
- 常用的线程池接口和类(所在包java.util.concurrent);
- Executor:线程池的顶级接口。
- ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码。
- Executors工厂类:通过此类可以获得一个线程池。
- 通过newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量。
- 通过newCachedThreadPool()获取动态数量的线程池,如不够则创建新的,无上限。
public static void main(String[] args) {
//Java四种线程池
//1.newFixedThreadPool() 固定大小线程池
//2.newCahceThreadPool() 创建动态线程池
//3.newSingleThreadPool() 创建单线程迟
//4.newScheduledThreadPool() 创建调度线程池。
//1.newFixedThreadPool()固定大小线程池
Runnable runnable1 = new Runnable() {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket <= 0) {
break;
}
System.out.println(Thread.currentThread().getName() + " "+ticket);
ticket--;
}
}
};
//2.创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(4);
// ExecutorService executorService = Executors.newCachedThreadPool();
// ExecutorService executorService = Executors.newSingleThreadExecutor();
//3.提交任务给线程池
for (int i = 0; i < 4; i++) {
executorService.submit(runnable1);
}
//4.关闭线程池
//shutdown()关闭等待所有的任务执行完毕后,才关闭
//shutdownNow();尝试关闭正在执行的线程,关闭没有执行的任务
executorService.shutdown();
}
- newScheduledExecutroService
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
Runnable runnable=new Runnable() {
private int count=0;
@Override
public void run() {
if(count==10){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(count==20){
scheduledExecutorService.shutdown();
}
count++;
Date date=new Date();
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yy-MM-dd HH:mm:ss");
System.out.println(simpleDateFormat.format(date));
}
};
// scheduledExecutorService.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);//如果延期了下次会补齐
scheduledExecutorService.scheduleWithFixedDelay(runnable, 0, 1, TimeUnit.SECONDS);//如果延期不会补齐
}
创建时尽量不要使用Executors。尽量通过ThreadPoolExecutor.
- 使用ThreadPoolExecutor类创建线程池.参数。
- 1.核心线程数
- 2.最大线程数
- 3.线程存货时间(除了核心线程数)
- 4.时间单位
- 5.请求队列
- 6.线程创建工厂
- 7.拒绝策略
- 7.1AbortPolicy 抛弃任务 默认的拒绝策略
- 这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态
- 如果是比较关键的业务,推荐使用此拒绝此略,这样在自行同不能承载更大的并发量的时候能够及时的通过已换成那个发现。
- 7.2DiscardPolicy 抛弃任务
- 使用此策略, 可能会使我们无法发现系统的异常状态。建议是一 些无关紧要的业务采用此策略。
- 7.3 DiscardOldestPolicy 放弃老的任务,添加拒绝的任务
- 此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。
- 7.4CallerRunsPolicy 线程池的创建线程执行,主线程执行。
- 如果任务被拒绝了, 则由调用线程(提交任务的线程)直接执行此任务
public class Test04 {
static int num;
public static void main(String[] args) {
//创建线程池
// ThreadPoolExecutor pool=new ThreadPoolExecutor(2, 3, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
ThreadPoolExecutor pool=new ThreadPoolExecutor(2, 3, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());
//2.添加任务
for (int i = 0; i < 100; i++) {
int num=i;
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 开始执行了"+num);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 执行完毕"+num);
}
});
}
pool.shutdown();
}
}
Callable接口
public interface Callable<V>{
public V call() throws Exception;
}
- JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
- Callable具有泛型返回值,可以声明异常。
Future接口
- 概念:异步接受ExecutorService.submit()所返回的状态结果,当中包含了call()的返回值
- 方法:V get()以阻塞形式等待Future中的异步处理结果(call()的返回值)
创建Callalbe对象,转化为任务,交给线程,最后获取结果。
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建Callable对象
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "开始计算");
int sum = 0;
for (int i = 0; i < 20; i++) {
sum += i;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "计算结束");
return sum;
}
};
//2.把可调用对象转成任务
//FutrueTask
FutureTask<Integer> task = new FutureTask<>(callable);
//3创建线程对象,把任务交给线程。
Thread thread = new Thread(task);
//4.启动线程
thread.start();
//5.获取线程执行的结果,get()等待任务执行完毕才会继续执行。
Integer sum = task.get();
System.out.println(sum);
}
}
同上交给线程池
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建一个线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
//2.创一个可调用对象
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "开始计算");
int sum = 0;
for (int i = 0; i < 20; i++) {
sum += i;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "计算结束");
return sum;
}
};
//3.交给线程池
//Future:表示将要执行完毕任务的结果。
Future<Integer> future = executorService.submit(callable);
//4.关闭
executorService.shutdown();
//5.获取结果
System.out.println(future.get());
}
}
Lock接口
- JDK5加入,与synchronized比较,显示定义,结构更灵活。
- 提供更多实用性方法,功能更强大,性能更优越。
- 常用方法:
- void lock()//获取锁,如锁被占用,则等待。
- boolean tryLock()//尝试获取锁(成功返回true,失败返回false,不阻塞。)
- void unlock()//释放锁
public class Main {
public static void main(String[] args) {
//创建一个锁
Lock lock=new ReentrantLock();
Runnable runnable=new Runnable() {
private int ticket=100;
@Override
public void run() {
while (true){
//上锁
lock.lock();
try {
if(ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
ticket--;
}finally {
//解锁放到finally,保证一定能够解锁。避免死锁。
lock.unlock();
}
}
}
};
ExecutorService executorService = Executors.newFixedThreadPool(4);
for (int i = 0; i < 4; i++) {
executorService.submit(runnable);
}
executorService.shutdown();
}
}
JDK1.6之后synchronized的优化:
(1) JDK1.6之前只有两种状态:无锁和重量级锁,JDK1.6之后有四种状态:无锁、偏向锁、轻量级锁、重量级锁
无锁–>偏向锁-- >轻量级锁–>重量级锁,只能升级不能降级。
(2)优化策略:锁消除、锁粗化、自旋锁Gjdk1.4包含)、 自适应自旋锁
自旋锁是在轻量级锁升级为重量级时使用的优化策略。
public static void main(String[] args) {
//局部变量 锁清除 JVM配合 开启逃逸分析XX:+DoEscapeAnalysis ,锁消除标记。--XX:+EliminateLocks
Object object = new Object();
synchronized (object){
System.out.println("...................");
}
//锁粗化,需要获取100遍锁,所以放到外面。
for (int i = 0; i < 100; i++) {
synchronized (object){
System.out.println(i);
}
}
//实际上,放到外面
synchronized (object){
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
重入锁
//可重入锁
synchronized (object){
for (int i = 0; i < 100; i++) {
synchronized (object){
System.out.println(i);
}
}
}
-
ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能。
class MyList{ private Lock locker=new ReentrantLock(); private String[]strs={"A","B","","",""}; private int count=2; public void add(String value){ locker.lock(); //显示开启锁 try{ strs[count]=value; try{ Thead.Sleep(1000); }catch(InterruptedException e){} count++; }finally{ locker.unlock();//显示释放锁 } } }
public static void main(String[] args) {
//创建一个锁
Lock lock=new ReentrantLock();
Runnable runnable=new Runnable() {
private int ticket=100;
@Override
public void run() {
while (true){
//上锁
lock.lock();
try {
if(ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
ticket--;
}finally {
//解锁放到finally,保证一定能够解锁。避免死锁。
lock.unlock();
}
}
}
};
ExecutorService executorService = Executors.newFixedThreadPool(4);
for (int i = 0; i < 4; i++) {
executorService.submit(runnable);
}
executorService.shutdown();
}
读写锁
- ReentrantReadWriteLock:
- 一种支持一写多读的同步锁,读写分离,可分别分配读锁,写锁。
- 支持多次分配读锁,使多个读操作可以并发执行。
- 互斥规则:
- 写-写:互斥,阻塞。
- 读-写:互斥,读阻塞写,写阻塞读。
- 读-读:不互斥,不阻塞。
- 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
public class MyClass {
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock=reentrantReadWriteLock.writeLock();
private int value;
public int getValue()throws Exception{
readLock.lock();//开启读锁
try{
Thread.sleep(1000);
return value;
}finally {
readLock.unlock();
}
}
public void setValue(int value)throws Exception{
writeLock.lock();
try{
Thread.sleep(1000);
this.value=value;
}finally {
writeLock.unlock();
}
}
}
public class TestReadWriteLock {
public static void main(String[] args) {
final MyClass myClass = new MyClass();
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
myClass.setValue(1);
} catch (Exception e) {
}
}
};
Runnable runnable1 = new Runnable() {
@Override
public void run() {
try {
myClass.getValue();
} catch (Exception e) {
}
}
};
ExecutorService executorService = Executors.newFixedThreadPool(20);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 2; i++) {
executorService.submit(runnable);
}
for (int i = 0; i < 18; i++) {
executorService.submit(runnable1);
}
executorService.shutdown();
while (!executorService.isTerminated()) {
}//如果线程未全部结束,等待
System.out.println(System.currentTimeMillis() - startTime);
}
}
实例
- ReentrantReadWriteLock 效率高
//ReadWrite.java
public class ReadWrite {
private int value;
// private Lock lock=new ReentrantLock();
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public int getValue() {
// lock.lock();
lock.readLock().lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("获取了" + value);
return value;
} finally {
// lock.lock()
lock.readLock().unlock();
}
}
public void setValue(int value) {
// lock.lock();
lock.writeLock().lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.value = value;
System.out.println("设置了" + value);
} finally {
// lock.unlock();
lock.writeLock().unlock();
}
}
}
public class TestWriteRead {
public static void main(String[] args) {
//读写数据
ReadWrite readWrite = new ReadWrite();
//可以运行对象
Runnable read = new Runnable() {
@Override
public void run() {
int v = readWrite.getValue();
}
};
Runnable write = new Runnable() {
@Override
public void run() {
readWrite.setValue(new Random().nextInt(100));
}
};
long start = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(20);
for (int i = 0; i < 2; i++) {
executorService.submit(write);
}
for (int i = 0; i < 18; i++) {
executorService.submit(read);
}
//关闭线程池
executorService.shutdown();
//计算时间
while (!executorService.isTerminated()) {
}
System.out.println(System.currentTimeMillis() - start);
}
}
Condition
- Conditon接口也提供了类似Objet的监视器方法 ,与Lock配合可以实现等待/通知模式。
- Condition可以通俗的理解为条件队列,当一个线程在调用了await方法以后,直到线程等待的某个条件为真的时候才会被唤醒
方法名 | 描述 |
---|---|
await() | 当前线程进入等待状态 |
signal() | 唤醒一个等待线程 |
signalAll() | 唤醒所有等待线程 |
//Bread.java
public class Bread {
private int id;
private String productName;
//setter,getter and constructor
}
//BreadCon.java
public class BreadCon {
private Bread []breadCons=new Bread[6];
private int size;
private Lock lock=new ReentrantLock();
private Condition proCondition=lock.newCondition();
private Condition conCondition=lock.newCondition();
public void put(Bread bread){
lock.lock();
try {
while (size>5){
try {
proCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
breadCons[size]=bread;
size++;
System.out.println(Thread.currentThread().getName()+"生产了"+bread.getId()+"号包子");
conCondition.signal();
}finally {
lock.unlock();
}
}
public void take(){
lock.lock();
try {
while (size<=0){
try {
conCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
size--;
Bread bread=breadCons[size];
System.out.println(Thread.currentThread().getName()+"吃了"+bread.getProductName()+"家的包子");
proCondition.signal();
}finally {
lock.unlock();
}
}
}
//Main.java
public class Main {
public static void main(String[] args) {
BreadCon con = new BreadCon();
Runnable produce=new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
con.put(new Bread(i,Thread.currentThread().getName()));
}
}
};
Runnable consume=new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
con.take();
}
}
};
Thread thread1=new Thread(produce,"生产者一号");
Thread thread2=new Thread(produce,"吃货一号");
Thread thread3=new Thread(consume,"生产者二号");
Thread thread4=new Thread(consume,"吃货二号");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
面试题
使用三个线程输出20个ABC
//Alternative.java
public class Alternative {
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
private Condition conditionC = lock.newCondition();
private int num = 1;
public void printA() {
lock.lock();
try {
if (num != 1) {
try {
conditionA.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num = 2;
System.out.print("A");
conditionB.signal();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
if (num != 2) {
try {
conditionB.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num = 3;
System.out.print("B");
conditionC.signal();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
if (num != 3) {
try {
conditionC.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num = 1;
System.out.print("C ");
conditionA.signal();
} finally {
lock.unlock();
}
}
}
//Test.java
public static void main(String[] args) {
Alternative alternative=new Alternative();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
alternative.printA();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
alternative.printB();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
alternative.printC();
}
}
}).start();
}
synchronized和Lock的区别
类别 | synchronized | Lock |
---|---|---|
存在层次 | Java的关键字,在jvm层面上 | 是一个类 |
锁释放 | 1.获取锁的线程执行完同步代码,释放锁 2.线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
锁的获取 | 假设A线程获取锁,B线程等待。如果A线程阻塞,B线程会一直等待 | Lock可以尝试获得锁,线程可以不用一直等待 |
锁的状态 | 不发判断 | 可以判断 |
锁类型 | 可重入,不可中断,非公平 | 可重入,可中断可公平或非公平 |
性能 | 少量同步 | 大量同步 |
线程安全的集合
- Collection体系集合,以及线程安全集合。
Collections中的工具方法
-
Collections工具类中提供了多个可以获取线程安全集合的方法。
- public staticCollectionsynchronizedCollection(Collectionc)
- public statcListsynchronizedList(Listlist)
- public static Set synchronizedSet(Set s)
- public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
- public static SortedSet synchronizedSortedSet(SortedSet s)
- public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
-
JDK1.2提供,接口同意,维护性高,但性能没有提升,均以synchronized实现
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
Collection<String> strings = Collections.synchronizedCollection(arrayList);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5; j++) {
strings.add(Thread.currentThread().getName() + ".." + j);
System.out.println(strings.toString());
}
System.out.println(strings.size());
}
}).start();
}
}
CopyOnWriteArrayList
- 线程安全的ArraylList,加强版读写分离。
- 写有锁,读无锁,读写之间不阻塞,由于读写锁。
- 写入时,先copy一个容器副本,在添加新元素,最后替换引用
- 使用方式与ArrayList无异
public static void main(String[] args) {
//创建集合
CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5; j++) {
arrayList.add(Thread.currentThread().getName() + ".." + j);
}
System.out.println(arrayList.toString());
System.out.println(arrayList.size());
}
}).start();
}
}
public class TestCopyOnWriteArrayList[
public static void main(String[]args){
List<String>list=new CopyOnWriteArrayList<>();
}
]
CopyOnWriteArraySet
- 线程安全的Set,底层使用CopyOnWriteArrayList实现
- 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组
- 如存在元素,则不添加,元素不能重复,判断依据为equals().
public class TestCopyOnWriteArraySet{
public static void main(String[]args){
Set<String>set=new CopyOnWriteArraySet<>();
}
}
public static void main(String[] args) {
CopyOnWriteArraySet<String> arraySet = new CopyOnWriteArraySet<>();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5; j++) {
arraySet.add(Thread.currentThread().getName() + "..." + j);
}
System.out.println(arraySet.toString());
System.out.println(arraySet.size());
}
}).start();
}
}
ConcurrentHashMap
- 初始容量默认为16段(Segment) ,使用分段锁设计。
- 不对整个Map加锁,而是为每个Segment加锁
- 当多个对象存入同一个Segment时,才需要互斥。
- 最理性状态为16个对象分别存入16个Segment,并行数量为16.
- 使用方式与HashMap无异
public class TestConcurrentHashMap{
public static void main(String[]args){
Map<String,String>map=new ConcurrentHashMap<String,String>();
}
}
public static void main(String[] args) {
// HashMap<String, String> map = new HashMap<>();
ConcurrentHashMap<String,String>map=new ConcurrentHashMap<>();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5; j++) {
map.put(Thread.currentThread().getName() + "" + j, j + "");
}
System.out.println(map.toString());
System.out.println(map.size());
}
}).start();
}
}
CAS算法
- CAS:Compare And Swap(比较交换算法)
- 其实这种方式是基于硬件平台的汇编指令,是套硬件来实现,效率高。
- 并且比较和交换过程是同步的。
- CAS是一种乐观锁。
- CAS比较交换算法,修改的方法包含三个核心参数(V(value),E(Except),N(new))
- V:要更新的变量,E:预期值,N:新值
- 只有当V==E时,V=N;否则表示已被更新过,则取消当前操作。
模拟一下CAS,并不是真正的
//CAS.java
public class CAS {
private int v;
private int version;
public synchronized int getV(){
return v;
}
public synchronized int getVersion(){
return version;
}
public synchronized boolean compareAndSwap(int e,int evenVersion,int n){
if(e==v&&version==evenVersion){
v=n;
version++;
return true;
}else {
return false;
}
}
}
//Test.java
public static void main(String[] args) {
CAS cas = new CAS();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (; ; ) {
boolean b = cas.compareAndSwap(cas.getV(), cas.getVersion(), new Random().nextInt(100));
if (b) {
System.out.println("修改成功");
break;
} else {
System.out.println("修改失败" + Thread.currentThread().getName());
}
}
}
}).start();
}
乐观锁和悲观锁
- 乐观锁:
- 总是认为是线程安全,不怕别的线程修改变量,如果修改了在更新尝试,直到成功。
- CAS是乐观锁。
- 悲观锁:
- 总是认为线程不安全,不管什么情况都要加锁,要是获取锁失败,就阻塞。
- synchronized是悲观锁。
Queue接口(队列)
- Collection的子接口,表示FIFO(First In First Out)
- 常用方法:
- 抛出异常:
- boolean add(E e)//顺序添加一个元素(到达上限后,再添加会出现异常)
- E remove()//获得第一个元素并移除(如果队列没有元素时,则抛出异常)
- E element()//获得第一个元素但不移除(如果队列没有元素时,则抛出异常
- 返回特殊值:
- boolean offer(E e)//顺序添加一个元素(到达上限后,再添加会返回false)
- E poll()//获得第一个元素并移除(如果队列没有元素时,则返回Null)
- E keep()//获得第一个元素但不移除(如果队列没有元素时,则返回Null)
- 抛出异常:
ConcurrentLinkedQueue
- **线程安全,**可高效读写的队列,高并发下性能最好的队列。
- 无锁,CAS比较交换算法,修改的方法包括三个核心参数(V,E,N)
- V:要更新的变量,E:预期值,N:新值。
- 只有当VE时,VN;否则表示已被更新过,则取消当前操作。
public class TestConcurrentLinkedQueue{
public static void main(String[]args){
Queue<String>queue=new ConcurrentLinkedQueue<String>();
queue.offer("Hello");
queue.offer("World");
queue.poll();
queue.peek();
}
}
BlockingQueue接口(阻塞队列)
- Queue的子接口,阻塞的队列,增加了两个线程状态为无限等待的方法
- 方法:
- void put(E e)//将制定元素插入此队列中,如果没有可用空间,则等待。
- E take()//获取并移除此队列头部元素,如果没有可用元素,则等待
- 可用于解决生产者,消费者问题。
阻塞队列
-
ArrayBlockingQueue:
数组结构实现,有界队列
public class TestArrayBlockingQueue{ public static void main(String[]args){ BlockingQueue<String>abq=new ArrayBlockingQueue<>(); } }
-
LinkedBlockingQueue:
-
链表结构实现,无界队列。(默认上限Integer.MAX_VALUE)
public class TestLinkedBlockingQueue{ public static void main(String[]args){ BlockingQueue<String>lbq=new LinkedBlockingQueue<>(); } }
下面输出可能有问题,因为ArrayBlockQueue内部加了锁,但是输出语句没有加锁,同步不了,但是ArrayBlockQueue内部数据没有问题。
-
public static void main(String[] args) {
LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>(6);
ArrayBlockingQueue<String> queue1 = new ArrayBlockingQueue<>(6);
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
queue1.put(Thread.currentThread().getName() + ">>>" + i);
System.out.println(Thread.currentThread().getName() + ">>>" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
queue1.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "<<<" + i);
}
}
}).start();
}
多线程三个特性
- 多线程要保证并发线程正确执行,必须要保证三个特性。
1.原子性(互斥性)
一个或多个操作不能被分割,要么全部执行,要么就都不执行。
2.可见性:
多个线程访问同一个变量,一个县城修改了这个变量,别的线程能立即看到修改的值。
3.有序性:
程序执行的顺序按照代码的先后顺序执行。但是处理器为了提高程序运行效率,可能会对输入代码进行优化,他不保证程序中各个语句的执行顺序和编写顺序一致。当时最终结果 是一致的。
synchronized可保证原子性和可见性。但不能保证有序性。
volatile可保证可见性和禁止性指令重拍,但不能保证原子性。
lock接口间接借助了volatile关键字间接的实现了可见性和有序性。
- i++是原子操作吗?
- 不是原子操作,i++ 有三步,读,改,赋值,过程中有可能被破坏
原子操作
public static void main(String[] args) {
// AtomicInteger integer = new AtomicInteger(0);
AtomicStampedReference<Integer> integer = new AtomicStampedReference<>(0, 0);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (; ; ) {
// int except = integer.get();
int except = integer.getReference();
int tamp=integer.getStamp();
// boolean b = integer.weakCompareAndSet(except, new Random().nextInt(100));
boolean b = integer.compareAndSet(except, new Random().nextInt(100), tamp, tamp+1);
if (b) {
System.out.println("修改成功" + Thread.currentThread().getName());
break;
} else {
System.out.println("修改失败" + Thread.currentThread().getName());
}
}
}
}).start();
}
}