文章目录
等待线程与唤醒线程
概述
学过计算机组成原理的同学,应该对这个比较熟悉,为了大家更好的理解,我们使用生产线程,消费线程作为例子,来给大家介绍。
生产线程:
1.先判断flag的值,如果flag== true,证明有包子,生产线程wait
2.如果flag为false,证明没有包子,就要生产包子
3.修改flag状态为true,证明生产完了,有包子了
4.唤醒消费进程 notify
消费线程:
1.先判断flag的值,如果flag== false,证明没有包子,消费线程wait
2.如果flag为true,证明有包子,就要消费包子
3.修改flag状态为false,证明消费完了,没有包子了
4.唤醒生产进程 notify
等待唤醒状态代码实现
package baozipu;
public class BaoZiPu {
//包子数量
private int count;
//是否有包子
private boolean flag;
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
public void getCount() {
System.out.println("消费了第"+count+"个包子");
}
public void setCount() {
count++;
System.out.println("生产了第"+count+"个包子");
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
生产者线程
package baozipu;
public class Produce implements Runnable {
private BaoZiPu baoZiPu;
public Produce(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100l);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (baoZiPu) {
//1.判断flag的值,如果为true证明有包子,生产线程wait
if (baoZiPu.isFlag()){
baoZiPu.setCount();
try {
baoZiPu.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果flag为flase,证明没有包子,就要生产包子
if (!baoZiPu.isFlag()){
baoZiPu.setCount();
//修改flag状态,证明生产完了
baoZiPu.setFlag(true);
//唤醒消费进程
baoZiPu.notify();
}
}
}
}
}
消费者线程
package baozipu;
public class Consumer implements Runnable{
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//flag为false证明没有包子,消费线程wait
synchronized (baoZiPu){
if (baoZiPu.isFlag() == false) {
try {
baoZiPu.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (baoZiPu.isFlag()){
//如果flag为true就要消费包子
baoZiPu.getCount();
//修改flag状态为false
baoZiPu.setFlag(false);
//唤醒生产进程
baoZiPu.notify();
}}
}}
}
测试类
package baozipu;
public class test01 {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();
Produce produce = new Produce(baoZiPu);
final Consumer consumer = new Consumer(baoZiPu);
new Thread(produce).start();
new Thread(consumer).start();
}
}
使用同步方法改造等待唤醒案例
我们将syn代码从消费者生产者线程中拿出来,整体加入到包子铺类中
public class BaoZiPu {
//包子数量
private int count;
//是否有包子
private boolean flag;
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
public synchronized void getCount() {
//flag为false证明没有包子,消费线程wait
if (flag == false) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (flag){
//如果flag为true就要消费包子
System.out.println("消费了第"+count+"个包子");
//修改flag状态为false
this.setFlag(false);
//唤醒生产进程
this.notify();
}
}
public synchronized void setCount() {
//1.判断flag的值,如果为true证明有包子,生产线程wait
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!flag) {
//如果flag为flase,证明没有包子,就要生产包子
count++;
System.out.println("生产了第" + count + "个包子");
//修改flag状态,证明生产完了
this.setFlag(true);
//唤醒消费进程
this.notify();
}
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
在生产者消费者进程中,只需要调用getcount,setcount方法即可
Lock锁
1.概述:是一个接口,充当锁对象
2.创建: ReentrantLock
3.方法:
void Lock() : 获取锁
void unlock(): 释放锁
public class ticket implements Runnable{
static int ticket = 100;
Object obj = new Object();
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取锁
lock.lock();
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
//释放锁
lock.unlock();
}
}
Lock和synchronized区别
lock,属于轻量级锁,乐观锁
底层原理:
CAS机制
Compare And Swap ------- 比较并交换
CAS机制
先从内存获取改变值
先和内存中的值比较,如果已经被改变,重新获取,重新计算
如果没有被改变,直接将改变后的值存到内存中
获取 改变 比较 获取 改变 比较 存
注意
乐观锁的操作,不是操作代码块的,而是操作一个变量
原子类:在java.util.concurrent.atomic包下面有很多原子类:
构造
AtomicInteger() 创建具有初始值0的新AtomicInteger ----- 相当于 int i = 0
AtomicIntger(int initivalValue) 创建具有给定初始值的新 AtomicInteger ---- 相当于int i = 值
方法
int addAndGet(int delta) 以原子方式将给定值与当前值相加
int getAndIncrement() ----- +1
int getAndDecrment() -------- -1
int addAndGet(int delta) ------- 加指定的值
import java.util.concurrent.atomic.AtomicInteger;
public class test02 {
public static void main(String[] args) {
//赋初值i=10
final AtomicInteger i = new AtomicInteger(10);
//以原子方式,将当前值与给定值相加
int sum = i.addAndGet(5);
System.out.println("sum + ="+sum);
}
}
乐观锁和悲观锁的区别
lock属于乐观锁,同时使用多个线程操作的是同一个变量
synchronized属于悲观锁,使用多个线程操作一段代码
乐观锁:线程A在操作变量是,允许线程B操作,只是会先判断,如果有问题,就放弃本次操作,判断没有问题就正常执行
悲观锁:当线程A正在操作的时候,才允许线程B执行,要等到A出来之后B才有可能进入执行
相对来说,悲观锁效率比较低,乐观锁效率比较高
当多个线程操作同一个数据时,会出现以下问题:
1.可见性
i=9 变量i的初始值为0,每个线程的操作都是减一,两个线程A和B同时访问变量,B先执行i-1,再将结果i=8同步到内存中,A线程
也执行i-1,这样i=9的状态就执行了两次,出现线程安全问题
线程安全问题产生的原因:一个线程对共享数据的修改不能立即为其他线程所见
解决 加上关键字:volatile
2.有序性
多行代码的编写顺序和编译顺序
有些时候,编译器在编译代码时,为了提高效率,会对代码进行重排
在多线程环境下,这种重排可能不是我们希望发生的,因为:重排,可能会影响另一个线程的结果,所以我们不需要代码进行重排
解决:给共享的变量加上关键字:volatile
3.原子性
指的是一个操作不可中断,即在多线程开发的环境下,一个操作一旦开始,就会在同一个CPU时间片内执行完毕
volatie解决不了原子性问题,所以为了多线程操作同一个数据出现的原子性问题,我们可以使用原子类
Atomicxxx类 ----- 代表具体数据类型 ---- 原子类的实现原理就是乐观锁
阻塞队列
概述 阻塞队列,是一个接口
获取:lock接口中的方法
Condition newCondition()
方法
void await() 等待
void signal() 唤醒
为啥要用condition这个接口
之前使用的wait和notify都是本地方法,频繁使用,会比较消耗系统资源
注意
Condition必须和Lock锁结合使用
对上述的同步代码块进行修改
示例如下
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BaoZiPu {
//包子数量
private int count;
//是否有包子
private boolean flag;
Lock lock = new ReentrantLock();
//为生产者创建Condition
Condition produceConditon = lock.newCondition();
Condition consumerConditon = lock.newCondition();
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
public void getCount() {
//flag为false证明没有包子,消费线程wait
lock.lock();
if (flag == false) {
try {
//this.wait();
consumerConditon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.lock();
if (flag) {
//如果flag为true就要消费包子
System.out.println("消费了第" + count + "个包子");
//修改flag状态为false
flag = false;
//唤醒生产进程
//this.notify();
consumerConditon.signal();
}
lock.unlock();
}
public void setCount() {
//1.判断flag的值,如果为true证明有包子,生产线程wait
lock.lock();
if (flag) {
try {
//this.wait();
produceConditon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!flag) {
//如果flag为flase,证明没有包子,就要生产包子
count++;
System.out.println("生产了第......." + count + "个包子");
//修改flag状态,证明生产完了
flag =true;
//唤醒消费进程
//this.notify();
consumerConditon.signal();
}
lock.unlock();
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
线程池
1.创建一个线程池对象,指定最多能创建多少条线程
2.来了第一个线程任务,判断池子中有没有线程,如果有,直接用,用完还回去
如果没有,创建线程对象,给第一个线程任务使用
3.来了第二个线程任务,判断池子中有没有线程,如果有,直接调用,用完还回去
如果没有,创建一个线程对象,给第二个线程任务使用
4.来了第三个线程任务,判断池子中有没有线程,如果有,直接调用,用完还回去,如果没有,等待,等着有线程任务执行完毕,归还了线程在使用
1.为啥要学线程池
因为我们会频繁的创建线程,去执行线程任务,执行完毕之后,要销毁线程,比较耗费内存资源
所以线程池中的线程可以循环利用
2.线程池对象:
Executors
3.获取:Executors中的静态方法
static ExecutorService newFixedThreadPool(interesting nThreads)
参数最多能创建多少条线程
返回值:ExecutorsService ---- 用来管理线程对象
4.执行线程任务:ExecutorsService中的方法
Future<?> submit(Runnable task)提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future
返回值:Future接口
用来接收run方法的返回值的,但是由于run方法没有返回值,所以不用Future接收
5.关闭线程池:用到的是ExecutorsSevice中的方法
void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接收新任务
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class test01 {
public static void main(String[] args) {
//创建线程池对象
ExecutorService es = Executors.newFixedThreadPool(3);
//提交线程任务
es.submit(new MyRunnable());
es.submit(new MyRunnable());
es.submit(new MyRunnable());
//关闭线程,不接受新任务
es.shutdown();
}
}
Callable接口
1.ExecutorService中的方法:
Future submit(Callable task)
2.Callable介绍:接口,类似于Runnable
3.Callable中的方法:
V call() — 用于设置线程任务,类似于run方法
4.call和run方法的区别:
a.相同点:
call方法和run方法都是设置线程任务的
b.不同点
run方法,没有返回值,且不能throws
call方法,没有返回值,且能throws
5.call方法的返回值是啥类型
a.Callable后面的泛型写啥类型,call方法的返回值类型就是啥类型
b.<>叫做泛型,规定的是操作的是什么类型的数据,只能传递引用类型
不对
对
6.Future接口:接收run方法或者call方法返回值的
Future接口中有一个方法: V get() 获取call方法的返回值
import java.util.concurrent.Callable;
public class Callable_1 implements Callable<String>{
@Override
public String call() throws Exception {
return "call me";
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class test01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
final ExecutorService es = Executors.newFixedThreadPool(3);
Future<String> future = es.submit(new Callable_1());
System.out.println(future.get());
}
}
定时器_Timer
1.Timer概述:定时器
2.构造:
Timer()
3.方法
void schedule(TimeTask task, Date firstTime, long period)
task:抽象类,可以设置线程任务
firstTime:从哪个时间开始,长记性线程任务
period:时间间隔
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class test03 {
public static void main(String[] args) {
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定时器执行!!");
}
},new Date(),2000L);
}
}
单例模式
单例模式:
单:单个,一个
例:实例,对象
目的:让一个类只产生一个对象供外界使用
饿汉式
1.什么是饿汉式
你饿了,很饥饿,想要迫不及待的干饭
单例模式是想要这个对象,所以饿汉式的单例模式是迫不及待的想要这个对象,要求这个对象赶紧产生
相关代码
public class Singleton {
/*
* 由于单例模式目的 是让一个类只产生一个对象
* 所以我们就不能让外界随便使用这个类的构造方法
* 所以将构造私有化
* */
private Singleton(){
}
/*
* 外界new不了,但是对象得产生,所以在本类中new
* 饿汉式:想着让这个对象赶紧创建出来,所以将对象变成static的
* */
private static Singleton singleton = new Singleton();
//对外提供公共接口,将自己本类中的对象给外界使用
public static Singleton getSingleton(){
return singleton;
}
}
public class test05 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println(Singleton.getSingleton());
}
}
}
懒汉式
简单理解:不着急new对象
public class Singleton_lan {
//简单理解,不着急new对象
private Singleton_lan(){}
/*
*由于是懒汉式,所以我们就不着急new对象了
* */
private static Singleton_lan singleton_lan = null;
/*
* 定义一个方法,对外返回一个都对象
* 什么时候调用,就什么时候new
* */
public static Singleton_lan getSingleton_lan(){
//如果Singleton不是null,就不要抢锁了,直接返回对象即可
if(singleton_lan == null){
synchronized (Singleton_lan.class){
if (singleton_lan==null){
singleton_lan = new Singleton_lan();
}
}
}
return singleton_lan;
}
}
public class test05 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println(Singleton_lan.getSingleton_lan());
}
}
}
小结
1.构造私有
2.在本类中new对象
3.定义方法,对外返回内部对象
正则表达式
正则表达式的概念及演示
1.概述:是字符串表示的一个规则
2.作用:用于校验
3.需求:要求输入一行数字
a.开头不能是0
b.必须是数字
c,数字必须是5-15位
4.String中有一个方法:
boolean matches(String regex) ------- 判断某个字符串是否符合regex正则表达式规则
常规方法代码
private static boolean method01(String number){
if (number.startsWith("0")){
return false;
}
char[] chars = number.toCharArray();
for (int i = 0; i < number.length(); i++) {
if (chars[i]<'0'||chars[i]>'9'){
return false;
}
}
if (number.length()<5 || number.length()> 15){
return false;
}
return true;
}
正则表达式代码
private static boolean method02(String number){
boolean result = number.matches("[1-9][0-9]{4,14}");
return result;
}
正则表达式-字符类
java.util.regex.Pattern:正则表达式的编译表达形式
正则表达式-----字符类:[]表示一个区间,范围可以自己定义
语法实例:
1. [abc]:代表a或者b,或者c字符串中的一个
2. [6abc]: 代表除a,b,c以外的任何字符
3. [a-z]: 代表a-z的所有小写字符中的一个
4. [A-Z]: 代表A-Z的所有大写字符的一个
5. [0-9]: 代表0-9之间的某一个数字字符
6. [a-zA-Z0-9]: 代表a-z,A-Z,0-9之间的任意一个字符
7. [a-dm-p]: a到d或m到p之间的任意一个字符
String name = "wang";
boolean result = name.matches("[w][g]");
System.out.println(result);
String name_2 = "李云龙";
boolean result_1 = name_2.matches("[李][云丁][龙]");
System.out.println(result_1);
System.out.println(name.matches("[a-z][a][^wedfsafd][g]"));
正则表达式-逻辑运算符
正则表达式-逻辑运算符
语法示例
1. &&:并且
2. |: 或者
System.out.println(name.matches("[[a-z]&&[^sdfvs]][a][n|e|w][g]"));
正则表达式–预定义字符
语法示例:
1.“.” 匹配任何字符。
2. “\d”:任何数字[0-9]的简写
3. “\D”: 任何非数字0-9的简写
4. “\s”: 空白字符
5. “\S”: 非空白字符
6. “\w”:单词字符,[a-zA-Z0-9]的简写
7. “\W”非单词数字
System.out.println("999".matches("\\d\\d\\d"));
正则表达式-数量词
语法示例:x代表着字符
1.x?:x出现的数量 零次或者一次
2.x*:x出现的数量 零次或者多次 任意次
3.x+:x出现的数量 多次或者一次 x>=1
4.x{n}:x出现的数量 恰好n次
5.x{n,}:x出现的数量 至少n次,x>=n次
6.x{n,m}: x出现的数量 n次到m次
System.out.println("1111".matches("\\d{3}"));
System.out.println("18766985325".matches("[1]\\d{1,11}"));
正则表达式-分组括号()
示例
System.out.println("123123123123".matches("(123)*"));
String类中和正则表达式相关的方法
String类中和正则表达式相关的方法有:
boolean matches(String regex)判断字符串是否匹配给定的正则表达式
String[] split(String regex) 根据给定正则表达式的匹配拆分此字符串
String replaceAll(String regex, String replacement)把满足正则表达式的字符串,替换为新的字符