一、多线程
1.1 线程简介
程序: 是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码(程序是静态的)
进程: 是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。
线程: 进程可进一步细化为线程, 是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程的。线程是CPU调度和执行的单位
注意:很多多线程是模拟出来的,真正的多线程是指多个CPU。如果是模拟出来的多线程,即在同一个CPU下,同一时间CPU只能执行一段代码,因为切换很快就有同时执行的错觉
并行和并发
并行:多个CPU同时执行多个任务
并发:一个CPU“同时”执行多个任务(采用时间片切换)
核心概念
- 线程就是独立执行的路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程、GC线程
- main()称之为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开劈了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序不能人为干预
- 对同一份资源操作时,会存在抢资源的问题,需要加入并发控制
- 线程会带来额外开销,如CPU调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
1.2 线程的创建
线程不一定立即执行,CPU安排调度
第一种:继承Thread类
第一步:自定义线程类继承Thread类
第二步:重写run()方法,编写线程执行体
第三步:创建线程对象,调用start()方法启动线程
第二种:实现Runnable接口
第一步:自定义线程类实现Runnable接口
第二步:重写run()方法,编写线程执行体
第三步:创建线程对象,通过线程对象调用start()方法启动线程
(推荐使用,避免了单继承的局限性,方便一个对象被多个线程使用)
第三种:实现Callable接口 (现阶段了解)
第一步:实现Callable接口,需要返回值
第二步:重写call方法,需要抛出异常
第三步:创建目标对象
第四步:创建执行服务:ExecutorServices ser = Executors.newFixedThreadPool(3);
第五步:提交执行:Future< Boolean > result1 = ser.submit(t1)
第六步:获取结果:boolean r1 = result.get()
第七步:关闭服务:ser.shutdownNow();
扩展
扩展1:静态代理
https://www.cnblogs.com/tele-share/p/9464295.html
静态代理应用:
对比以实现Runnable接口的形式创建多线程,可以发现,代理角色Thread类不需要我们创建,我们只需要写委托对象
实现Runnable接口.把委托对象的引用传递给Thread,借助Thread对象来开启线程即可
扩展2:lambda表达式
优点:
避免匿名内部类定义过多,简洁
Lambda演变方式:
1、前提:定义一个函数式接口(接口中只包含一个抽象方法),对于函数式接口可以通过Lambda表达式创建该接口的对象
2、实现类,创建对象,对象调用方法
3、静态内部类,创建对象,对象调用方法
4、局部内部类,创建对象,对象调用方法
5、匿名内部类,没有类的名称,必须借助接口或者父类
6、lambda表达式(用于函数式接口)
lambda表达式应用:
Runnable接口中只有public abstract void run();方法,符合函数式接口,可以使用Lambda表达式创建该接口的对象,之后传入Thread中(要传入Runnable实现类)
new Thread( () -> System.out.println("多学习多线程。。")).start();
1.3 线程状态
五大状态:
1.4 线程常用方法
start():启动当前线程,表面上调用start方法,实际在调用线程里面的run方法
run():线程类 继承 Thread类 或者 实现Runnable接口的时候,都要重新实现这个run方法,run方法里面是线程要执行的内容
currentThread :Thread类中一个静态方法:获取当前正在执行的线程
setName: 设置线程名字
getName: 读取线程名字
stop方法:
过期方法,不建议使用
建议线程正常停止
建议使用标志位,设置flag
//自己编写stop方法
public class Test07 implements Runnable{
//设置停止的标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run方法"+i++);
}
}
//使用标志位flag,自己设置stop()方法
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
Test07 t = new Test07();
new Thread(t).start();
for (int i = 0; i < 100; i++) {
System.out.println("main--"+i);
if (i == 90){
t.stop();
System.out.println("该线程停止了");
}
}
}
}
sleep方法:
sleep指定当前线程阻塞的毫秒数
sleep存在异常InterruptedException
sleep时间达到后线程进入就绪状态
sleep可以模拟网络延时,倒计时等
每一个对象都有一个所,sleep不会释放锁
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
//测试sleep,倒计时,打印系统时间
public class Test08 {
public static void main(String[] args) {
//打印系统时间
DateFormat df = new SimpleDateFormat("HH:mm:ss");
while (true){
Date d = new Date();
System.out.println(df.format(d));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*十秒倒计时
try {
tenDown();
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
//十秒钟倒计时
public static void tenDown() throws InterruptedException {
int i = 10;
while (true){
System.out.println(i--);
Thread.sleep(1000);
if (i<=0){
break;
}
}
}
}
yield方法:
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让CPU重新调度,礼让不一定成功!看CPU心情
//测试礼让yield
public class Test09 {
public static void main(String[] args) {
MyYield my = new MyYield();
new Thread(my,"a").start();
new Thread(my,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行");
Thread.yield();//线程礼让
System.out.println(Thread.currentThread().getName()+"执行结束");
}
}
join方法:
join合并线程,待此线程执行完之后再执行其他线程,其他线程阻塞
可以想象成插队
//测试join
public class Test10 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("VIP来了"+i);
}
}
public static void main(String[] args) throws InterruptedException {
Test10 t = new Test10();
Thread thread = new Thread(t);
thread.start();
for (int i = 0; i < 100; i++) {
if (i==50){
thread.join();
}
System.out.println("main--"+i);
}
}
}
Thread.State 查看线程状态
//观测线程的状态
public class Test11 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("----------");
});
Thread.State state = thread.getState();
System.out.println(state);
thread.start();
state = thread.getState();//更新状态
System.out.println(state);
while (state != Thread.State.TERMINATED){
Thread.sleep(100);
state = thread.getState();//每次都要更新状态
System.out.println(state);
}
}
}
设置线程优先级
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 1
Thread.NORM_PRIORITY = 1
getPriority(),serPriority(int XXX)
//测试优先级
public class Test12 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"----"+Thread.currentThread().getPriority());//主程序的
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority,"t1");
Thread t2 = new Thread(myPriority,"t2");
Thread t3 = new Thread(myPriority,"t3");
Thread t4 = new Thread(myPriority,"t4");
Thread t5 = new Thread(myPriority,"t5");
//先设置优先级,再启动
t1.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(2);
t3.start();
t4.setPriority(1);
t4.start();
t5.setPriority(10);
t5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"----"+Thread.currentThread().getPriority());
}
}
守护线程:
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕(main…)
虚拟机不用等待守护线程执行完毕
如:后台记录操作日志,监控内存,垃圾回收等待…
Thread thread = new Thread(god);
thread.setDaemon(true);//true表示守护线程,默认false表示用户线程
thread.start();
1.4 线程同步(线程安全问题)
多线程操作同一资源
并发:同一个对象被多个线程同时操作
线程同步其实就是一种等待机制 / 排队
队列+锁 才能保证线程同步的安全性
synchronized
不安全的例子
//线程不安全的集合
import java.util.ArrayList;
import java.util.List;
//ArrayList不安全的集合
public class Test13 {
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());//结果不等于10000,因为有可能两个线程同时写一个位置,发生覆盖
}
}
解决方法:
1、同步方法
2、同步代码块
3、Lock锁
synchronized 方法 默认锁的是这个对象(this),类本身;synchronized代码块可以锁任何对象锁的对象就是变化的量,需要增删改的对象
synchronized加在操作变化的量的方法或代码块上
import java.util.ArrayList;
import java.util.List;
//synchronized代码块,解决ArrayList不安全的集合
public class Test15 {
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(3000);//不加这一段代码结果仍然不一样
//去掉sleep不安全的原因应该是:输出的操作总稍快于最后几步(创建线程和list.add)
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
死锁
产生死锁的四个必要条件:
1、互斥条件:一个资源每次只能被一个进程使用
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持
3、不剥夺条件:进程已获得资源,在未使用完之前,不能强行剥夺
4、循环等待:若干进程之间形成的一种头尾相接的循环等待资源关系
Lock锁
JDK5.0开始Java提供了通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。
ReentrantLock类实现了Lock,它拥有和synchronized相同的并发性内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
lock()方法加锁,unlock()方法解锁
推荐放在try{} finally{} 代码块中
import java.util.concurrent.locks.ReentrantLock;
//Lock锁解决买票问题
public class Test16 {
public static void main(String[] args) {
TestBuy buy = new TestBuy();
new Thread(buy).start();
new Thread(buy).start();
new Thread(buy).start();
}
}
class TestBuy implements Runnable{
private int ticketNum = 10;
private final ReentrantLock lock = new ReentrantLock();//显示的定义锁
@Override
public void run() {
while (true){
try {
lock.lock();//加锁
if (ticketNum>0){
System.out.println(ticketNum--);
}else {
break;
}
} finally {
lock.unlock();//解锁
}
}
}
}
synchronized和Lock的对比
1、Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
2、Lock只有代码块锁,synchronized有代码块锁和方法锁
3、使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多子类)
4、优先使用顺序:Lock > 同步代码块(已经进入方法体,分配了相应资源) > 同步方法(在方法体外)
1.5 线程通信
几种解决线程之间通信问题的方法
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
注意:均是Object类的方法。都只能在同步方法或者代码块中使用,否则抛出异常IllegalMonitorStateException |
解决方法一:管程法 加一个缓冲区
//生产者消费者问题的第一种解决方式,使用缓冲区
//管程法
public class Test17 {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Producer(container).start();
new Consumer(container).start();
}
}
//生产者
class Producer extends Thread{
SynContainer container;//都需要的对象
public Producer (SynContainer container){
this.container = container;
}
//生产方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了第"+i+"只鸡");
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;//都需要的对象
public Consumer (SynContainer container){
this.container = container;
}
//消费方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了--->第"+container.pop().id+"只鸡");
}
}
}
//产品
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer{
//产品
Chicken[] chickens = new Chicken[10];
//计数
int count = 0;
//生产者生产
public synchronized void push(Chicken chicken){
//如果容器满了就通知消费者消费
if (count == chickens.length){
//通知消费者消费,线程等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有满就放入容器中
chickens[count] = chicken;
count++;
//通知消费者可以消费了
this.notifyAll();
}
//消费者消费
public synchronized Chicken pop(){
//判断,如果没有就通知生产者,消费者等待
if (count == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有的话就能消费
count--;
Chicken chicken = chickens[count];
//吃完了,通知生产者
this.notifyAll();
return chicken;
}
}
解决方法二:信号灯法 加一个标志位
//生产者消费者问题的第二种解决方式,使用标志位
//标志位法
public class Test18 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者---演员
class Player extends Thread{
TV tv = new TV();//都需要的对象
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2==0){
this.tv.play("表演快乐大本营");
}else {
this.tv.play("广告广告广告");
}
}
}
}
//消费者---观众
class Watcher extends Thread{
TV tv = new TV();//都需要的对象
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//产品---节目
class TV{
String voice;//表演内容
//演员表演,观众等待 T
//观众观看,演员等待 F
boolean flag = true;
//表演
public synchronized void play(String voice){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:"+voice);
//通知观众观看
this.notifyAll();
this.voice = voice;
this.flag = !this.flag;
}
//观看
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了--"+voice);
//通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
线程池
思路:提前创建好了多个线程,放如线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建、销毁,重复利用。类似于生活中的公共交通工具。
好处:
1、提高响应速度(减少了创建新线程的时间)
2、降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
3、便于线程管理(…)
线程池相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor
– void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
– < T >Future < T >submit(Callable< T > task):执行任务,有返回值,一般用来执行Callable
– void shutdown:关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//线程池
public class Test19 {
public static void main(String[] args) {
//创建服务,创建线程池
//newFixedThreadPool需要传入线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
二、JUC
2.1 前期准备
API开发文档
空的Maven项目
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
2.2 什么是JUC
java.util.concurrent
java.util.concurrent.atomic – 原子性
java.util.concurrent.locks – lock锁
回顾
Callable接口 是 java.util.concurrent 下的
Lock接口 是 java.util.concurrent.locks 下的
2.3 进程和线程
进程:是程序的一次执行过程。一个进程可以包含多个线程
Java默认有几个线程?两个 main 和 GC
线程:线程是CPU调度和执行的单位
Java中通过Thread、Runnable、Callable开启线程
java真的能开启线程吗?? 不能
是调用了本地的C++方法,无法直接操作硬件
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
并发和并行
并发:多线程操作同一个资源 — 一个CPU,模拟多线程
并行:多个人一起行走 — 多个CPU,同时执行多个线程;线程池
public static void main(String[] args) {
//获取CPU核数
//CPU密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
并发编程的本质: 充分利用CPU资源
线程的状态???
NEW-----新生状态
RUNNABLE-----运行状态
BLOCKED-----阻塞状态
WAITING-----等待状态
TIMED_WAITING-----超时等待
TERMINATED-----死亡状态
wait和sleep的区别
1、来自不同的类,wait来自Object,sleep来自Thread
2、关于锁的释放,wait会释放锁,sleep不会
3、使用范围不同,wait必须在同步代码块中,sleep可以任何地方
4、是否需要捕获异常,wait不用,sleep必须捕获异常
2.4 Lock锁(重点)
传统的Synchronized锁
//买票的例子
/**
* 真正的线程开发,即公司中的多线程开发
* 线程就是一个单独的资源类,没有其他的附属操作
* 传统操作方法:在操作资源的sale()方法前加关键字synchronized
*/
public class SaleTicketDemo1 {
public static void main(String[] args) {
//并发:多线程同时操作一个资源
Ticket ticket = new Ticket();
//使用Lambda表达式
new Thread(() ->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"A").start();
new Thread(() ->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"B").start();
new Thread(() ->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类-只有属性和方法 不用实现Runnable接口 这才是真正的面向对象编程 OOP
class Ticket{
private int nums = 30;
public synchronized void sale(){
if (nums>0){
System.out.println(Thread.currentThread().getName()+"买了第"+(nums--)+"张票,还剩"+nums);
}
}
}
Lock接口
实现类
ReentrantLock , ReentrantReadWriteLock.ReadLock , ReentrantReadWriteLock.WriteLock
公平锁:非常公平,可以先来后到
非公平锁:十分不公平,可以插队(默认)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SaleTicketDemo2 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
//使用Lambda表达式
new Thread(() ->{ for (int i = 0; i < 40; i++) ticket.sale(); },"A").start();
new Thread(() ->{ for (int i = 0; i < 40; i++) ticket.sale(); },"B").start();
new Thread(() ->{ for (int i = 0; i < 40; i++) ticket.sale(); },"C").start();
}
}
class Ticket2{
private int nums = 60;
Lock lock = new ReentrantLock();
public synchronized void sale(){
lock.lock();//上锁
try {//执行代码
if (nums>0){
System.out.println(Thread.currentThread().getName()+"买了第"+(nums--)+"张票,还剩"+nums);
}
}finally {
lock.unlock();//解锁
}
}
}
Synchronized和Lock的区别
1、Synchronized 是内置的关键字,Lock是一个Java类
2、Synchronized 无法判断锁的状态,Lock可以判断是否获取到锁
3、Synchronized 会自动释放锁,Lock必须要手动释放锁!!
4、Synchronized 线程1(获得锁阻塞)、线程2(一直等);Lock锁就不会一直等下去
5、Synchronized 可重入锁,不可中断,非公平;Lock可重入锁,可以判断锁,默认非公平(可以手动设置)
6、Synchronized 适合锁少量的代码同步问题;Lock适合锁大量的同步代码块
锁是什么?如何判断锁的是谁?
见本文 2.6 8锁现象
2.5 生产者消费者问题
//生产者消费者问题
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//资源类,只有属性和方法
class Data {
private int num = 0;
//判断等待,执行业务,通知别人
public synchronized void increment() throws InterruptedException {
if (num != 0){
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知其他线程,我+1完成
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if (num == 0){
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知其他线程,我-1完成
this.notifyAll();
}
}
上述代码问题:两个线程还能运行,多个线程的话运行就出错了!!产生虚假唤醒问题
因为使用了if语句,官方推荐使用while循环
JUC中的解决办法
对应关系
官方文档中的实例如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//生产者消费者问题
public class B {
public static void main(String[] args) {
Data2 data2 = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//资源类,只有属性和方法
class Data2 {
private int num = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//condition.await();等待
//condition.signalAll();通知
//判断等待,执行业务,通知别人
public void increment() throws InterruptedException {
lock.lock();
try{
while (num != 0){
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知其他线程,我+1完成
condition.signalAll();
}finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();
try{
while (num == 0){
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知其他线程,我-1完成
condition.signalAll();
}finally {
lock.unlock();
}
}
}
任何一个新技术的引入都不仅仅是实现了原来的方法,肯定有优势和补充
Condition 精准的通知和唤醒线程
上述代码执行的乱序的,想要按顺序执行A B C D
Condition可以实现,代码如下
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 实现 A B C 三个线程顺序执行
*/
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread( ()->{
for (int i = 0; i < 10; i++) {
data.printA();
}
} ,"A").start();
new Thread( ()->{
for (int i = 0; i < 10; i++) {
data.printB();
}
} ,"B").start();
new Thread( ()->{
for (int i = 0; i < 10; i++) {
data.printC();
}
} ,"C").start();
}
}
class Data3{
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int nums = 1 ;//1打印A 2打印B 3打印C
public void printA(){
lock.lock();
try {//业务代码 判断等待,执行,唤醒指定线程
while (nums != 1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAA");
//精准唤醒
nums = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {//业务代码 判断等待,执行,唤醒指定线程
while (nums != 2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"=>BBB");
nums = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {//业务代码 判断等待,执行,唤醒指定线程
while (nums != 3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"=>CCC");
nums = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
2.6 8锁现象
锁是什么?如何判断锁的是谁?
new的对象 class类
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.send();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);//JUC下的sleep方法
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
//send和call两个方法锁的是同一个 对象,谁先得到谁就先执行
public synchronized void send(){
try {
TimeUnit.SECONDS.sleep(4);//JUC下的sleep方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发消息");
}
public synchronized void call(){
System.out.println("打电话");
}
}
问题:
1、send和call方法都为synchronized,一个对象,两个线程,先发消息还是先打电话???–先发短信再打电话
2、send中延迟4秒,call和send方法都为synchronized,一个对象,两个线程,先发消息还是先打电话???–先发短信再打电话
3、send方法为synchronized,hello方法为普通方法,一个对象,两个线程,先发消息还是先hello???–先hello再发短信
4、send和call方法都为synchronized,两个个对象,两个线程,先发消息还是先打电话???–先打电话再发消息
5、send和call方法都为static synchronized,一个对象,两个线程,先发消息还是先打电话???–先发消息后打电话
6、send和call方法都为static synchronized,两个对象,两个线程,先发消息还是先打电话???–先发消息后打电话
7、send方法为static synchronized,call方法为synchronized,一个对象,两个线程,先发消息还是先打电话???–先打电话再发短信
8、send方法为static synchronized,call方法为synchronized,两个对象,两个线程,先发消息还是先打电话???–先打电话再发短信
解释:
1.synchronized方法锁定对象是方法的调用者,即本例中 锁住的是phone这个对象
2.send和call两个方法锁的是同一个 对象,谁先得到谁就先执行
3.虽为同一个对象,但是普通方法不受锁的限制
4.两个对象时一个方法锁对应的对象,两把锁互不影响
5.static是静态方法,类一加载就有了,锁的是Class,,Phone3只有一个全局唯一的Class对象
6.phone和phone1两个对象都是Phone3的实例,Class只有一个,被锁了
7.静态同步方法锁的是Class类模板,同步方法锁的是调用者对象,两者锁的不是同一个东西
8.静态同步方法锁的是Class类模板,同步方法锁的是调用者对象,两者锁的不是同一个东西
2.7 集合类不安全
List类不安全
解决办法:
1、List< String > list = new Vector();–并发条件下安全—Vector底层使用了Synchronized
2、List< String > list = Collections.synchronizedList(new ArrayList<>());—工具类
3、List< String > list = new CopyOnWriteArrayList<>();----JUC包下的—底层应用了Lock
Set类不安全
解决办法:
1、Set< String> list = Collections.synchronizedSet(new HashSet<>());—工具类
2、Set< String> list = new CopyOnWriteArraySet<>();----JUC包下的—底层应用了Lock
Map类不安全
解决方法:
1、Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
2、Map<String,String> map = new ConcurrentHashMap<>();
2.8 Callable
实现Callable接口好处:(1)有返回值 (2)能抛出异常
缺点:线程创建比较麻烦
注意点:
1、启动线程的Thread中只能传入Runnable,Callable通过传入FutureTask变为可传入的方法,来启动线程
2、结果会被缓存提高效率
3、get方法会产生阻塞,一般放到最后或者异步通信。
2.9 常用的辅助类
CountDownLatch
CyclicBarrier
Semaphore(信号量)
2.10 读写锁
2.11 阻塞队列
2.12 线程池(重点)
三大方法、七大参数、四种拒绝策略
//三大方法
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
ExecutorService threadPool = Executors.newFixedThreadPool(5); //固定的5个
ExecutorService threadPool = Executors.newCachedThreadPool(); //可伸缩的,遇强则强,遇弱则弱
三大方法的本质: ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
七大参数: ThreadPoolExecutor中有7个参数
int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大线程池大小
long keepAliveTime,//多产时间没有被调用会被释放
TimeUnit unit,//单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂,一般不用动
RejectedExecutionHandler handler//拒绝策略
四种拒绝策略
/**
* 1.new ThreadPoolExecutor.AbortPolicy() //银行满了,还有人进来,直接拒绝,抛出异常
* 2.new ThreadPoolExecutor.CallerRunsPolicy() //哪里来的回哪去,如main方法中来的回main方法中
* 3.new ThreadPoolExecutor.DiscardPolicy() //队列满了丢掉任务,不会抛出异常
* 4.new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常!
*/
线程池的最大小如何设置?
CPU密集型和IO密集型
CPU密集型:几核的就是几,可以保持CPU高效— Runtime.getRuntime().availableProcessors()
IO密集型:> 判断程序中十分消耗IO的线程
一般为消耗IO线程的两倍
2.13 四大函数式接口(必须掌握)
新时代的程序员必须要会的:lambda表达式、链式编程、函数式接口、Stream流式计算
函数型接口
断定型接口
消费型接口
供给行接口
2.14 Stream流式计算
java.util.stream
2.15 ForkJoin
分支合并
并行执行任务,提高效率
2.16 异步回调
和Ajax一样,Java中也有异步回调
java.util.concurrent.CompletableFuture
unAsync(Runnable runnable)
返回一个新的CompletableFuture,它在运行给定操作后由运行在 ForkJoinPool.commonPool()中的任务 异步完成。
2.17 JMM
谈谈Volatile
1、保证可见性
2、不保证原子性
3、禁止指令重排
什么是JMM??
Java内存模型,不存在的东西,概念!约定!
关于JMM的一些同步约定:
1、线程解锁前,必须把共享变量 立刻 刷回主存
2、线程加锁前,必须读取主存中最新值到工作内存中
3、枷锁和解锁的是同一把锁
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
- lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
- unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
- assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
- write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值 - 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
2.18 Volatile
1、保证可见性
2、不保证原子性
原子性:不可分割
线程A在执行任务的时候,是不能被打扰的,也不能被分割.要么同时成功,要么同时失败.
如果不使用lock和synchronized,怎么保证原子性???
: 使用原子类,解决原子性问题
原子类在java.util.concurrent.atomic包下,底层原理为CAS,直接和操作系统挂钩!在内存中修改值!
Unsafe类是很特殊的存在
3、禁止指令重排
什么是指令重排?
:你写的程序,计算机并不是按照你写的那样执行的
源代码–>编译器优化的重排–>指令并行的重排–>内存系统也会重排–>执行
int x = 1;
int y = 2;
x += 5;
y = 2 * x;
我们期望的是: 1234, 2134, 1324
volatile可以避免指令重排: 内存屏障: CPU指令, 作用:
- 保证特定的操作的执行顺序
- 可以保证某些变量的内存可见性(利用这些特性,volatile实现了可见性)
Volatile 是可以保证可见性, 不能保证原子性,由于内存屏障可以避免指令重排的现象产生 !
2.19 单例模型
饿汉式
public class HungryMan {
private static HungryMan HUNGRYMAN = new HungryMan();
private HungryMan(){
}
public static HungryMan getInstance(){
return HUNGRYMAN;
}
}
懒汉式
//单线程安全
public class LazyMan {
private static LazyMan lazyMan = null;
private LazyMan(){
}
public static LazyMan getInstance(){
if(lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
DCL(double-checked locking)懒汉式,volatile
import java.lang.reflect.Constructor;
public class LazyManThread {
private static volatile LazyManThread lazyManThread = null;
private static boolean isExist = false;
private LazyManThread() {
synchronized (LazyManThread.class) {
if (!isExist) {
isExist = true;
} else {
throw new RuntimeException("禁止使用反射创建该对象");
}
}
}
private LazyManThread(int a){
synchronized (LazyManThread.class){
if(lazyManThread != null){
throw new RuntimeException("禁止使用反射创建该对象");
}
}
}
public static LazyManThread getInstance() {
//if只会判断一次,当两个线程同时判断时一个线程就会在同步代码块中等待
if (lazyManThread == null) {
//不直接使用同步的原因,提高执行效率
synchronized (LazyManThread.class) {
if (lazyManThread == null) {
lazyManThread = new LazyManThread();
}
}
}
/**
* 由于对象创建不是原子性操作
* 1. 分配内存空间
* 2. 使用构造器创建对象
* 3. 将对象指向内存空间
*/
/**
* 可能会发生指令重排
* 123
*
* 132
*
* 这是就需使用volatile关键字来防止指令重排
*/
return lazyManThread;
}
public static void main(String[] args) throws Exception {
// LazyManThread instance = LazyManThread.getInstance();
Constructor<LazyManThread> declaredConstructor = LazyManThread.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyManThread lazyManThread = declaredConstructor.newInstance();
LazyManThread instance = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(lazyManThread);
}
}
静态内部类
public class LazyMan1 {
private LazyMan1() {}
public static final LazyMan1 getInstance(){
return innerClass.LAZY_MAN_1;
}
public static class innerClass {
private static final LazyMan1 LAZY_MAN_1 = new LazyMan1();
}
}
单例不安全,可以通过反射来破坏
枚举
import java.lang.reflect.Constructor;
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance1 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
枚举实现的单例是安全的
枚举最终反编译代码
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingle.java
package com.czp.single;
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/czp/single/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
2.20 深入理解CAS
什么是CAS
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
// CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
// 期望、更新
// public final boolean compareAndSet(int expect, int update)
// 如果我期望的值达到了,那么就更新,否则,就不更新, CAS 是CPU的并发原语!
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get()); atomicInteger.getAndIncrement()
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
Unsafe类
CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!
缺点:
1、 循环会耗时
2、一次性只能保证一个共享变量的原子性
3、ABA问题
CAS中的ABA问题(狸猫换太子)
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
// CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
// 期望、更新
// public final boolean compareAndSet(int expect, int update)
// 如果我期望的值达到了,那么就更新,否则,就不更新, CAS 是CPU的并发原语!
// ============== 捣乱的线程 ==================
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
// ============== 期望的线程 ==================
System.out.println(atomicInteger.compareAndSet(2020, 6666));
System.out.println(atomicInteger.get());
}
}
2.21 原子引用
解决ABA 问题,引入原子引用! 对应的思想:乐观锁!
带版本号的原子操作
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CASDemo {
//AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
// 正常在业务操作,这里面比较的都是一个个对象
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
// CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("a1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println("a2=>"+atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3=>"+atomicStampedReference.getStamp());
},"a").start();
// 乐观锁的原理相同!
new Thread(()->{ int stamp = atomicStampedReference.getStamp();
// 获得版本号
System.out.println("b1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1));
System.out.println("b2=>"+atomicStampedReference.getStamp());
},"b").start();
}
}
注意:
Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实
例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;