我们使用多线程模拟车站卖票,车站的票数一开始都是确定好的,多个窗口使用多个线程并发执行来模拟;
方式1:自定义类继承Thread类
public class MyTest1 {
public static void main(String[] args) {
sellTickets th1 = new sellTickets("线程-A");
sellTickets th2 = new sellTickets("线程-B");
sellTickets th3 = new sellTickets("线程-C");
th1.start();
th2.start();
th3.start();
}
}
public class sellTickets extends Thread {
private static int tickets = 100;
public sellTickets(String name) {
super(name);
}
@Override
public void run() {
while (true) {
if (tickets >= 1) {
try {
/*售票时网络是不能实时传输的,
总是存在延迟的情况,所以,在出售一张票以后,
需要一点时间的延迟,
这里增加的这行代码是为了更好的演示多线程的随机性与模拟网络延迟*/
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩:" + (tickets--) + "张票");
}
}
}
}
public class MyTest2 {
public static void main(String[] args) {
sellRunnable runnable = new sellRunnable();
Thread th1 = new Thread(runnable);
Thread th2 = new Thread(runnable);
Thread th3 = new Thread(runnable);
th1.setName("线程-A");
th2.setName("线程-B");
th3.setName("线程-C");
th1.start();
th2.start();
th3.start();
}
}
public class sellRunnable implements Runnable {
//这里不需要写成static,测试类只创建了一个任务
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets >= 1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩:" + (tickets--) + "张票");
}
}
}
}
public class MyTest3 {
public static void main(String[] args) {
sellCallable<String> callable = new sellCallable<>();
FutureTask task1 = new FutureTask(callable);
FutureTask task2 = new FutureTask(callable);
FutureTask task3 = new FutureTask(callable);
Thread th1 = new Thread(task1,"线程-A");
Thread th2 = new Thread(task2,"线程-B");
Thread th3 = new Thread(task3,"线程-C");
th1.start();
th2.start();
th3.start();
}
}
import java.util.concurrent.Callable;
public class sellCallable<String> implements Callable<String> {
private int tickets = 100;
@Override
public String call() throws Exception {
while(true){
if (tickets >= 1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩:" + (tickets--) + "张票");
}
}
}
}
0票或者负数票
?1、假设当th1线程这时候获得了CPU的执行权,这个时候假设票数=1了,因为我们在run()里面睡眠了50ms,在这50ms内,很有可能其他线程获得了CPU的执行权,
2、假设这时候th2获得了执行权,他也进入了while循环,判断此时的票数还是1,因此进入if循环,
3、假设这时候th1睡醒了,th2进入睡眠,th1执行了卖票语句,这时候票数变为0,
4、th2线程睡醒之后,也执行了卖票语句,这时候票数就变成了负数,或者同样情况下可能出现的0票;
JVM 内存模型
:主要目标:
定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不一样,指包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,不会被共享。
规则:
所有的变量都存储在主内存中,每个线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量;
read(读取)
:作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用;
load(载入)
:作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中;
相同票数
?由于线程的原子性造成的;原子性指的是不可分割性,线程中对于卖票的操作不是原子性操作,首先,我们需要将三个线程都共享的数据票数从主存中读取到每个线程的工作内存,然后在每个线程的工作内存中进行修改,修改完再把这个数据写回到主存当中;
1、在这个过程当中,假设线程th1先获得CPU执行权,他读取到了票数4,
2、然后在自己的工作内存当中修改了票数为3,还没来得及刷回主存当中,
3、线程th2就抢走了CPU执行权,读取到了票数4,
4、它也改了票数为3,
5、这时候th1又抢回了CPU执行权,将票数3刷回主存,因此票数显示3;
6、这个时候th2抢走了cpu执行权,票数也显示了3;
最终造成显示相同票数的原因;
数据安全
的问题,出现数据安全的问题主要是由于以下几点原因:1、是不是一个多线程环境;
2、多个线程有没有共享数据;
3、有没有多条语句在操作共享变量;
把有可能出现数据安全问题的代码使用同步代码块进行包裹(锁住),让任意时刻只能有一个线程执行即可
; synchronized(对象){
//不能在括号了直接 new 对象 new 了就没效果要被同步的代码 ;
//这个对象可以使用任意的Java对象代替;
}
-----自定义卖票类:
public class sellTickets extends Thread {
private static int tickets = 100;
//创建一个三个线程共同持有的一把锁对象
private static Object obj = new Object();
public sellTickets(String name) {
super(name);
}
@Override
public void run() {
while (true) {
synchronized (obj) {
if (tickets >= 1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩:" + (tickets--) + "张票");
}
}
}
}
}
------测试类:
public class MyTest {
public static void main(String[] args) {
sellTickets th1 = new sellTickets("线程-A");
sellTickets th2 = new sellTickets("线程-B");
sellTickets th3 = new sellTickets("线程-C");
th1.start();
th2.start();
th3.start();
}
}
再也没有出现数据安全问题;
这里要注意:三个线程必须使用同一个锁对象,因为只有这样,才能保证某一时间段内只有一个线程可以持有这把锁,其中一个线程抢到了CPU执行权,锁住了门,其他线程只能等在门外,如果三个对象各自使用一把锁,就没有任何意义了;
1、什么是synchronized?
synchronized关键字可以实现一个简单的策略来
防止线程干扰
和内存一致性
错误,如果一个对象是对多个线程可见的,那么对该对象的所有读写都将通过同步的方式来进行;
2、synchronized包括哪两个JVM重要的指令?
monitor enter
和monitor exit
3、synchronized锁的是什么?
- 普通同步方法 ---------------> 锁的是当前实例对象;
- 静态同步方法---------------> 锁的是当前类的Class对象;
- 同步方法块 ---------------> 锁的是synchonized括号里配置的对象;
同步方法与静态同步方法
;-----测试类:
public class MyTest1 {
public static void main(String[] args) {
sellRunnable runnable = new sellRunnable();
Thread th1 = new Thread(runnable,"线程-A");
Thread th2 = new Thread(runnable,"线程-B");
Thread th3 = new Thread(runnable,"线程-C");
th1.start();
th2.start();
th3.start();
}
}
----自定义卖票任务类:
public class sellRunnable implements Runnable{
private static int tickets = 100;
@Override
public synchronized void run() {
while (true) {
if (tickets >= 1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩:" + (tickets--) + "张票");
}
}
}
}
----测试类:
public class MyTest {
public static void main(String[] args) {
sellRunnable runnable = new sellRunnable();
Thread th1 = new Thread(runnable,"线程A");
Thread th2 = new Thread(runnable,"线程B");
Thread th3 = new Thread(runnable,"线程C");
th1.start();
th2.start();
th3.start();
}
}
----自定义Runnable类:
public class sellRunnable implements Runnable{
//这个票让三个线程共享
static int piao = 100; //共享数据
@Override
public void run() {
while (true) {
//th1 th2 th3
maiPiao();
//th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
}
}
//静态同步方法:默认用的锁对对象,用的是当前类的字节码对象
public static synchronized void maiPiao() {
if (piao >= 1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
}
}
}
避免了数据安全问题,这个时候sychronized
默认持有锁对象是当前类的字节码文件对象,它属于类锁;
1、每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁
;
2、线程进入同步代码块或同步方法的时候会自动获得该锁,在退出同步代码块或同步方法时会释放该锁;
3、获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法;
4、Java内置锁是一个互斥锁
,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去;
5、Java的对象锁和类锁:Java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的
,类锁是用于类的静态方法或者一个类的class对象上的
。
6、我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象, 所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
7、但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的
,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。
8、 一个类的对象锁和另一个类的对象锁是没有关联的,当一个线程获得A类的对象锁时,它同时也可以获得B类的对象锁。
----测试类:
public class MyTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread th1 = new Thread(runnable,"线程A");
Thread th2 = new Thread(runnable,"线程B");
Thread th3 = new Thread(runnable,"线程C");
th1.start();
th2.start();
th3.start();
}
}
-----自定义类:
public class MyRunnable implements Runnable {
public static int ticket = 100;
int i = 0;
@Override
public void run() {
while (true) {
if (i % 2 == 0) {
synchronized (MyRunnable.class) {
if (ticket >= 1) {
//模拟一下真实的售票环境,有网络延迟。
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + " 张票");
}
}
} else {
sellTicket();
}
i++;
}
}
private static synchronized void sellTicket() {
if (ticket >= 1) {
//模拟一下真实的售票环境,有网络延迟。
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + " 张票");
}
}
}
我想应该是这样的:首先对程序来讲同步的部分很影响运行效率,而一个方法通常是先创建一些局部变量,再对这些变量做一些操作,如运算,显示等等;而同步所覆盖的代码越多,对效率的影响就越严重。因此我们通常尽量缩小其影响范围;
同步的出现解决了多线程的安全问题;
当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,必须等待或者阻塞,这对高并发的系统是致命的,这很容易导致系统的崩溃。如果某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。这是一个致命的问题;
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,比较抽象,为了更清晰的表达如何加锁和释放锁,JDK 1.5以后,并发包新增Lock接口来实现锁功能;
特性 | 描述 |
---|---|
尝试非阻塞地获取锁 | 当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁 |
能被中断的获取锁 | 与sychronized不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁被释放 |
超时获取锁 | 在指定的截止时间之前获取锁,如果截至时间到了仍然无法获取,则返回 |
1、概述:
这个是 JDK @since 1.5 添加的一种颗粒度更小的锁,它完全可以替代 synchronized
关键字来实现它的所有功能,而且 ReentrantLock
锁的灵活度要远远大于前者;
从类结构图看出,ReentrantLock 实现了 Lock 接口,ReentrantLock 只是 Lock 接口的一个实现而已。
java.util.concurrent.locks.Lock
它们都是java.util.concurrent 包
里面的内容(俗称 JUC、并发包),也都是 JDK 1.5 开始加入的;
2、为什么叫重入锁
ReentrantLock,我们把它拆开来看就明了了,Re-Entrant-Lock:即表示
可重新反复进入的锁,但仅限于当前线程
;
public void m() {
lock.lock();
lock.lock();
try {
// ... method body
} finally {
lock.unlock()
lock.unlock()
}
}
如示例代码所示,当前线程可以反复加锁,但也需要释放同样加锁次数的锁,即重入了多少次,就要释放多少次,不然也会导入锁不被释放;
试想一下,如果不设计成可重入锁,那自己如果反复给自己加锁,不是会把自己加死锁了吗
?
获取锁,有以下三种情况:
锁空闲:直接获取锁并返回,同时设置锁持有者数量为:1;
当前线程持有锁:直接获取锁并返回,同时锁持有者数量递增1;
其他线程持有锁:当前线程会休眠等待,直至获取锁为止;
(2)lockInterruptibly()
获取锁,逻辑和 lock() 方法一样,但这个方法在获取锁过程中能响应中断。
(3)tryLock()
从关键字字面理解,这是在尝试获取锁,获取成功返回:true,获取失败返回:false, 这个方法不会等待,有以下三种情况:
锁空闲:直接获取锁并返回:true,同时设置锁持有者数量为:1;
当前线程持有锁:直接获取锁并返回:true,同时锁持有者数量递增1;
其他线程持有锁:获取锁失败,返回:false;
(4)tryLock(long timeout, TimeUnit unit)
逻辑和 tryLock() 差不多,只是这个方法是带时间的。
(5)unlock()
释放锁,每次锁持有者数量递减 1,直到 0 为止。所以,现在知道为什么 lock 多少次,就要对应 unlock 多少次了吧。
(6)newCondition
返回一个这个锁的 Condition 实例,可以实现 synchronized 关键字类似 wait/ notify 实现多线程通信的功能,不过这个比 wait/ notify 要更灵活,更强大!
大概用法如下:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
加锁和释放锁都在方法里面进行,可以自由控制,比 synchronized 更灵活,更方便。但要注意的是,释放锁操作必须在 finally 里面,不然如果出现异常导致锁不能被正常释放,进而会卡死后续所有访问该锁的线程。
synchronized 是重入锁吗?
4、synchronized 是重入锁吗
你可能会说不是,因为 ReentrantLock 既然是重入锁,根据推理,相反,那 synchronized 肯定就不是重入锁,那你就错了,答案是:yes,看下面的例子:
public synchronized void operation(){
add();
}
public synchronized void add(){
}
operation 方法调用了 add 方法,两个方法都是用 synchronized 修饰的,add() 方法可以成功获取当前线程 operation() 方法已经获取到的锁,说明 synchronized 就是可重入锁。
5、卖票案例演示:
import java.util.concurrent.locks.ReentrantLock;
public class MyTest1 {
public static void main(String[] args) {
myLockThread th1 = new myLockThread("线程-1");
myLockThread th2 = new myLockThread("线程-2");
myLockThread th3 = new myLockThread("线程-3");
th1.start();
th2.start();
th3.start();
}
}
class myLockThread extends Thread{
public static ReentrantLock lock=new ReentrantLock();
public static int tickets=100;
public myLockThread(String s) {
super(s);
}
@Override
public void run() {
while (true){
lock.lock();
if(tickets>=1){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (tickets--) + " 张票");
}
lock.unlock();
}
}
}
之前在学习集合与自定义类的时候,提到有些类是属于线程安全效率低,而有些则是线程不安全效率高,这么说的原因是什么?
1、ArraryList 与 Vector 集合
2、StringBuilder 与 StringBuffer 类
由此可见,这些线程安全的类是因为他们的成员方法都加了关键字sychronized
,保证了一段时间内只能有一个线程来执行这个方法里面的逻辑,也就保证了数据安全;
指的是两个或两个以上的线程,因为同时抢占CPU资源而出现的相互等待现象;
------------------------------------------------------------------------
代码演示:
-----工具类:
public class ObjectUtils {
//创建两个静态的对象充当锁对象
public static final Object objA = new Object();
public static final Object objB = new Object();
}
------自定义线程类:
public class MyThread extends Thread {
boolean flag;
public MyThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (ObjectUtils.objA) {
System.out.println("true线程持有A锁");
synchronized (ObjectUtils.objB) {
System.out.println("true线程持有B锁");
}
}
} else {
synchronized (ObjectUtils.objB) {
System.out.println("false线程持有B锁");
synchronized (ObjectUtils.objA) {
System.out.println("false线程持有A锁");
}
}
}
}
}
---测试类:
public class MyTest {
public static void main(String[] args) {
MyThread th1 = new MyThread(true);
MyThread th2 = new MyThread(false);
th1.start();
th2.start();
}
}
某一次执行结果:
另外一次执行结果:
出现了死锁现象,为什么会出现这样的现象:
1、true线程最先抢占了CPU资源,持有了A锁;
2、flase线程紧接着抢占了CPU资源,持有了B锁;
3、true线程等待false线程执行完毕释放B锁,false线程等待true线程执行完毕释放A锁,但是他们都处于互相等待状态,谁也不让谁,就出现了死锁现象;
4、这就相当于:中国人和美国人一起吃饭,中国人使用的筷子,美国人使用的刀和叉;中国人获取到了美国人的刀,美国人获取到了中国人的一根筷子,谁也不让谁,谁都吃不成饭;
在实际的软件开发过程中,经常会碰到如下场景:
- 某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等);
- 产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。
单单抽象出生产者和消费者,还够不上是生产者/消费者模式;- 该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介,生产者把数据放入缓冲区,而消费者从缓冲区取出数据;
- 下图是对生产者消费者的理解图:
1、你把信写好——相当于生产者制造数据
2、你把信放入邮筒——相当于生产者把数据放入缓冲区
3、邮递员把信从邮筒取出——相当于消费者把数据取出缓冲区
4、邮递员把信拿去邮局做相应的处理——相当于消费者处理数据- 这个生产者消费者模式也叫做
等待——唤醒机制
;
可能会有这样的疑问:这个缓冲区有什么用?为什么不让生产者直接调用消费者的某个函数,直接把数据传递过去?搞出这么一个缓冲区干什么?其实这里面是大有讲究的,大概有如下一些好处;
1、解耦:
2、支持并发(concurrency)
3、支持忙闲不均
1、我们使用Student类模拟缓存,使用getThread类模拟消费者,使用setThread类来模拟生产者;
2、首先需要明确,生产者消费者模型希望达到的效果是:生产一个学生对象,消费一个学生对象
,也就是set线程的run()需要增加学生信息(比如设置 [“张三”,23]),get线程的run()需要获取学生信息 [获取到 “张三”,23这个学生对象],但是学生对象是new出来的,两个线程之间没办法共享这个数据,怎么才能共享呢?我们可以给两个线程提供一个有参构造,在测试类里面就创建好学生对象,然后将一个学生对象传递给两个线程,这样可以实现学生对象的资源共享
;
----测试类:
public class MyTest {
public static void main(String[] args) {
Student student = new Student();
setThread th1 = new setThread(student);
getThread th2 = new getThread(student);
th1.start();
th2.start();
}
}
----生产者:
public class setThread extends Thread {
int i = 1;
Student student;
public setThread(Student student) {
this.student = student;
}
@Override
public void run() {
while (true) {
//没有资源生产资源
if (i % 2 == 0) {
//生产资源
student.name = "张三";
student.age = 23;
} else {
//生产资源 th1
student.name = "李四";
student.age = 24;
}
i++;
}
}
}
-----消费者:
public class getThread extends Thread {
private Student student;
public getThread(Student student) {
this.student = student;
}
@Override
public void run() {
//消费资源
while (true) {
//有了资源就消费
System.out.println(student.name + "===" + student.age);
}
}
}
----学生类:
public class Student {
public int age;
public String name;
}
某一次运行结果:
另外一次运行结果:
3、上面的运行结果可以看出,程序出现了数据安全的问题,本来希望达到生产一个消费一个的效果没有出现,明明生产的是李四–24,张三–23,却出现了李四–23,张三–24的现象;因为线程一旦开启,就会具有随机性,有可能还没生产,就消费了,也有可能生产到一半,就被消费了,造成名字与年龄不匹配;
4、怎么解决数据安全的问题呢?可以给生产资源和消费资源的代码加sychronized锁,锁住这部分代码,保证在没有生产好资源的时候,不能消费资源;并且需要注意的是,我们生产和消费线程必须使用的同一个锁住,这样才能保证一段时间内只能执行一个线程的任务,这时候,他们共享资源学生对象可以充当这个锁对象;
5、但是这样写代码还是不行,因为我们生产者一次只能生产一个资源,但是由于线程的随机性,有可能生产了一个资源,但是却在重复消费这个资源;
6、我们怎么解决上面的问题呢?在生产线程里面,我们希望生产完成一个资源,消费一个资源;
作为生产者来说,我生产一个资源,就通知消费线程来消费这个资源;作为消费线程来说,我消了资源,我就等着,并且通知生产线程来生产;
这时候就需要用到等待——唤醒机制
;
Object 类中:
void wait ()
——在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待;
- 作用是使当前执行代码的线程进行等待,wait()是Object类通用的方法,该方法用来将当前线程置入"预执行队列"中,并在 wait()所在的代码处停止执行,直到接到通知或中断为止。
- 在调用wait之前线程需要获得该对象的对象级别的锁。代码体现上,即只能是同步方法或同步代码块内。调用wait()后当前线程释放锁。
void wait (long timeout)
——在其他线程调用此对象的 notify () 方法或 notifyAll () 方法,或者超过指定的时间量前,导致当前线程等待;
void notify ()
——唤醒在此对象监视器上等待的单个线程;
- notify()也是Object类的通用方法,也要在同步方法或同步代码块内调用,该方法用来通知哪些可能灯光该对象的对象锁的其他线程,如果有多个线程等待,则随机挑选出其中一个呈wait状态的线程,对其发出通知 notify,并让它等待获取该对象的对象锁。
void notifyAll ()
——唤醒在此对象监视器上等待的所有线程;
- notify等于说将等待队列中的一个线程移动到同步队列中,而notifyAll是将等待队列中的所有线程全部移动到同步队列中。
7、考虑如何判断是否已经生产好了学生对象,因为不能出现还没生产就消费了的现象,可以给学生对象加一个布尔类型的值作为标记,如果还没有生产或者是生产之后被消费了该学生资源,将标记置为false,并使得当前线程处于等待状态;如果资源已经生产好了,就去通知消费,也就是唤醒;这里使用共享资源Student资源让线程等待或者唤醒;
-----生产者:
public class setThread extends Thread {
int i = 1;
Student student;
public setThread(Student student) {
this.student = student;
}
@Override
public void run() {
while (true) {
synchronized (student) {
if (student.flag) {
//有资源,生产者线程就等待
try {
//th1
student.wait();//线程一旦等待,就要立马释放锁,在哪里等待,被唤醒后,就从哪里开始执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有资源生产资源
if (i % 2 == 0) {
//生产资源
student.name = "张三";
student.age = 23;
} else {
//生产资源 th1
student.name = "李四";
student.age = 24;
}
//有了资源通知消费线程去消费线程
//修改标记
student.flag = true;
student.notify();
//通知 唤醒之后,线程还得再次争抢时间片 th1
}
i++;
}
}
}
-----消费者:
public class getThread extends Thread {
private Student student;
public getThread(Student student) {
this.student = student;
}
@Override
public void run() {
//消费资源
while (true) {
synchronized (student) {
if (!student.flag) {
//false
//消费线程没有资源,就等着
try {
student.wait(); //一旦等待,就会释放锁,在哪里等待,被唤醒后,就从这里执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有了资源就消费
System.out.println(student.name + "===" + student.age);
//消费了就没有了
//通知生成者线程去生产
student.flag = false;//修改标记
student.notify();
}
}
}
}
----缓存:
public class Student {
public int age;
//定义一个标记,用来表示是否有资源。 false 表示没有资源, true表示有资源。
public boolean flag = false;
public String name;
}
-----测试类:
public class MyTest1 {
public static void main(String[] args) {
Student student = new Student();
setThread th1 = new setThread(student);
getThread th2 = new getThread(student);
th1.start();
th2.start();
}
}
public class MyTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread th = new Thread(runnable);
th.start();
while (true){
if(runnable.getFlag()){
System.out.println("进来执行了!");
break;
}
}
}
}
public class MyRunnable implements Runnable {
public boolean flag = false;
public boolean getFlag() {
return this.flag;
}
@Override
public void run() {
System.out.println("线程进来执行了!");
flag = true;
}
}
sychronized
关键字,保证程序顺利执行;为什么可以顺利执行的原因:
- Java程序在编为字节码文件的时候,代码的执行顺序有可能和写的顺序不一样,也就是不知道什么时候flag会写回主存,所以造成了上面的代码出现卡死;
- 上面的程序加锁应该给读数据、写数据都要加锁;
- Sychronized关键字还有一个作用是 确保变量的内存可见性,写数据的时候读数据线程去申请同一个锁,它的工作内存会被设置为无效,然后读线程会重新从主存中加载它要访问的变量到它的工作内存中;
- 因此就保证了读完之后再写上去程序顺利执行;
public class MyRunnable implements Runnable {
public boolean flag = false;
public boolean getFlag() {
return this.flag;
}
@Override
public void run() {
synchronized (ObjectUtils.objA){
System.out.println("线程进来执行了!");
flag = true;
}
}
}
public class MyTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread th = new Thread(runnable);
th.start();
while (true){
synchronized (ObjectUtils.objA){
if(runnable.getFlag()){
System.out.println("进来执行了!");
break;
}
}
}
//线程进来执行了!
//进来执行了!
}
}
sychronized
关键字也可以,但是这样一来程序可以顺利执行,但是这样造成效率一定会降低,有没有更好的办法?我们可以引入Volatile关键字;1、Volatile关键字是轻量级的,会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,它保证了 内存的可见性问题;
2、 而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性;
代码示例:
-----测试类:
public class MyTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread th = new Thread(runnable);
th.start();
while (true) {
if (runnable.getFlag()) {
System.out.println("进来执行了!");
break;
}
}
//线程进来执行了!
//进来执行了!
}
}
----自定义任务类:
public class MyRunnable implements Runnable {
public volatile boolean flag = false;
public boolean getFlag() {
return this.flag;
}
@Override
public void run() {
System.out.println("线程进来执行了!");
flag = true;
}
}
3、另外,通过 synchronized 和 Lock 也能够保证可见性,synchronized 和 Lock 能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中,因此可以保证可见性;
4、volatile 关键字:相较于 synchronized 是一种较为轻量级的同步策略;
5、volatile 变量,用来确保将变量的更新操作通知到其他线程;
6、可以将 volatile 看做一个轻量级的锁,但是又与锁有些不同:
互斥关系
"原子性操作"
1、首先要了解的是,volatile可以保证可见性和顺序性,这些都很好理解,那么它为什么不能保证原子性呢?
2、首先需要了解的是,Java中只有对基本类型变量的赋值和读取是原子操作,如i = 1的赋值操作,但是像j = i或者i++这样的操作都不是原子操作,因为他们都进行了多次原子操作,比如先读取i的值,再将i的值赋值给j,两个原子操作加起来就不是原子操作了;
3、举例:
①线程A首先得到了 i 的初始值100,但是还没来得及修改,就阻塞了;
②这时线程B开始了,它也得到了 i 的值,由于 i 的值未被修改,即使是被volatile
修饰,主存的变量还没变化,那么线程B得到的值也是100;
③之后对其进行加1操作,得到101后,将新值写入到缓存中,再刷入主存中;
④根据可见性的原则,这个主存的值可以被其他线程可见;
⑤问题来了,线程A已经读取到了 i 的值为100,也就是说读取的这个原子操作已经结束了,所以这个可见性来的有点晚,线程A阻塞结束后,继续将100这个值加1,得到101,再将值写到缓存,最后刷入主存;
所以即便是volatile具有可见性,也不能保证对它修饰的变量具有原子性。
class MyRunnable implements Runnable {
int piao = 0;
@Override
public void run() {
while (true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(piao++);
}
}
}
public class MyTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread th1 = new Thread(runnable, "线程--A");
Thread th2 = new Thread(runnable, "线程--B");
Thread th3 = new Thread(runnable, "线程--C");
th1.start();
th2.start();
th3.start();
}
}
piao++
操作是一个非原子性操作,因此出现了数据安全问题;1、CAS算法概述
硬件对并发的支持
,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问;2、CAS算法原理
3、图解:
上面的图可知,两个线程其实就是在比较谁将新值刷回主存更快,你读到的不一定是此时内存中的值;
假如有三个线程,都读取到了主存中的100作为预估值,并进行修改操作,修改为101,但是线程1刷回主存很快,将101写入主存,其他两个线程将预估值和主存中的值一对比,对不上,因此刷回失败;就是在比较谁刷的快;
4、应用
java.util.concurrent.atomic
包下提供了一些原子操作的常用类:
我们只需要把变量对应的类型换为上面的类型就好了,这些类型里面比如AtomicInteger 提供的自加、自减操作都可以确保原子性操作;
import java.util.concurrent.atomic.AtomicInteger;
class MyRunnable implements Runnable {
AtomicInteger piao = new AtomicInteger(0);
@Override
public void run() {
while (true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(piao.getAndIncrement());
}
}
}
public class MyTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread th1 = new Thread(runnable, "线程--A");
Thread th2 = new Thread(runnable, "线程--B");
Thread th3 = new Thread(runnable, "线程--C");
th1.start();
th2.start();
th3.start();
}
}
使用匿名内部类更简单的开启线程:
—方式1:
public class MyTest1 {
public static void main(String[] args) {
Thread th = new Thread() {
@Override
public void run() {
System.out.println("这是一个线程");
}
};
th.start();
//这是一个线程
}
}
—方式2:
public class MyTest1 {
public static void main(String[] args) {
Thread th = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("这是一个线程任务");
}
});
th.start();
//这是一个线程任务
}
}
1、程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互;
2、而使用线程池可以很好的提高性能,他本质就是一个存储一定数量线程对象的容器,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池;
3、线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用;
4、在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池;
1、 JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
public static ExecutorService newCachedThreadPool():
根据任务的数量来创建线程对应的线程个数
public static ExecutorService newFixedThreadPool(int nThreads):
固定初始化几个线程
public static ExecutorService newSingleThreadExecutor():
初始化一个线程的线程池
2、这些方法的返回值是ExecutorService对象
,该对象表示一个线程池
,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法:
线程池返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()来获取返回值,get()方法会阻塞当前线程直到任务完成;get(long timeout,TimeUnit unit)可以设置超时时间;
3、使用步骤:
创建线程池对象
创建Runnable实例
提交Runnable实例
关闭线程池
4、为什么使用线程池
降低系统消耗:重复利用已经创建的线程降低线程创建和销毁造成的资源消耗。
提高响应速度:当任务到达时,任务不需要等到线程创建就可以立即执行。
提供线程可以管理性:可以通过设置合理分配、调优、监控。
5、线程池工作流程
(1)判断核心线程池里的线程是否都有在执行任务,否->创建一个新工作线程来执行任务,是->走下个流程。
(2)判断工作队列是否已满,否->新任务存储在这个工作队列里,是->走下个流程。
(3)判断线程池里的线程是否都在工作状态,否->创建一个新的工作线程来执行任务,是->走下个流程。
(4)按照设置的策略来处理无法执行的任务。
6、代码示例:
import java.util.concurrent.*;
public class MyTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建一个线程池对象
ExecutorService pool = Executors.newCachedThreadPool();
//提交任务,传递一个任务类的参数
Future<?> future = pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("这是一个Runnable里面的任务");
}
});
//创建一个任务类的参数
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("这是一个Callable里面的任务");
return 20;
}
};
//提交任务
Future<Integer> submit = pool.submit(callable);
//获取任务的返回值
System.out.println("Callable里面的任务的返回值:" + submit.get());
//关闭线程池
pool.shutdown();
/*这是一个Runnable里面的任务
这是一个Callable里面的任务
Callable里面的任务的返回值:20*/
}
}
Timer:
public Timer()
public void schedule(TimerTask task, long delay):
--------------------安排在指定的时间执行指定的任务
public void schedule(TimerTask task,long delay,long period);
--------------------task - 所要安排的任务。
--------------------delay - 执行任务前的延迟时间,单位是毫秒。
--------------------period - 执行各后续任务之间的时间间隔,单位是毫秒。
public void schedule(TimerTask task, Date time):
public void schedule(TimerTask task, Date firstTime, long period):
TimerTask:定时任务
public abstract void run()
public boolean cancel()
代码示例1:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;
public class MyTest1 {
public static void main(String[] args) throws ParseException {
//创建一个定时器
Timer timer = new Timer();
//创建一个定时任务
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("这是一个定时任务!");
}
};
//定时执行一个任务
timer.schedule(task,new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2020-06-11 0:06:30"));
//这是一个定时任务!
}
}
代码示例2:
import java.text.ParseException;
import java.util.Timer;
import java.util.TimerTask;
public class MyTest2 {
public static void main(String[] args) throws ParseException {
//创建一个定时器对象
Timer timer = new Timer();
//将定时器传递过去,方便在任务结束后关闭定时器
MyTimerTsk task = new MyTimerTsk(timer);
//第一次执行是在当前系统时间后的2s后,以后每隔1s之后执行一次任务
//等3秒第一次执行任务,以后间隔1秒重复执行定时任务
timer.schedule(task, 3000,1000);
}
}
class MyTimerTsk extends TimerTask {
private Timer timer;
public MyTimerTsk(Timer timer) {
this.timer = timer;
}
@Override
public void run() {
System.out.println("砰~爆炸了!");
//任务执行完了,取消定时器
//timer.cancel();可以选择执行一次任务后就关闭定时器
}
}
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
public class MyTest3 {
public static void main(String[] args) {
Timer timer = new Timer();
MyTimerTask task = new MyTimerTask(timer);
//间隔3s之后执行该任务
timer.schedule(task,3000);
//删除文件完成
}
}
class MyTimerTask extends TimerTask {
private Timer timer;
public MyTimerTask(Timer timer) {
this.timer = timer;
}
@Override
public void run() {
delFile();
System.out.println("删除文件完成");
timer.cancel();
}
private void delFile() {
File file = new File("D:\\桌面图标\\练习文件打包");
delFiles(file);
}
private void delFiles(File file) {
File[] files = file.listFiles();
for (File f : files) {
if (f.isFile()) {
f.delete();
} else {
delFiles(f);
}
}
file.delete();
}
}
——优化代码的第一步
——让程序更稳定、更灵活
——构建扩展性更好的系统
——让项目拥有变化的能力
——系统有更高的灵活性
——更好的扩展性
创建型模式(创建对象的): 单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式;
行为型模式(对象的功能): 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式;
结构型模式(对象的组成): 模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
class AniamlFactory {
//私有构造
private AniamlFactory() {
}
public static Animal getAnimal(String name) {
if ("cat".equals(name)) {
return new Cat();
} else if ("dog".equals(name)) {
return new Dog();
} else if ("tiger".equals(name)) {
return new Tiger();
} else {
return null;
}
}
}
abstract class Animal {
public abstract void eat();
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Tiger extends Animal {
@Override
public void eat() {
System.out.println("老虎吃肉");
}
}
public class MyTest {
public static void main(String[] args) {
Animal an = AniamlFactory.getAnimal("cat");
an.eat();
an = AniamlFactory.getAnimal("dog");
an.eat();
an = AniamlFactory.getAnimal("tiger");
an.eat();
/*猫吃鱼
狗吃骨头
老虎吃肉*/
}
}
public class MyTest1 {
public static void main(String[] args) {
Animal an = new DogFactory().createAnimal();
an.eat();
an = new CatFactroy().createAnimal();
an.eat();
}
}
abstract class Animal {
public abstract void eat();
}
interface BigFactory {
//创建产品的方法
Animal createAnimal();
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
class CatFactroy implements BigFactory {
@Override
public Animal createAnimal() {
return new Cat();
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class DogFactory implements BigFactory{
@Override
public Animal createAnimal() {
return new Dog();
}
}
1、懒汉式
public class MyTest {
public static void main(String[] args) {
Student student = Student.getStudent();
Student student1 = Student.getStudent();
System.out.println(student == student1);
//true
}
}
class Student {
public static Student stu = null;
private Student() {
}
//多线程环境下能保证这个方法的一定是创建一个对象吗?
//加上synchronized保证多线程环境下的使用也是单列的
public synchronized static Student getStudent() {
if (stu == null) {
stu = new Student();
}
return stu;
}
}
2、饿汉式
public class MyTest1 {
public static void main(String[] args) {
Teacher tea1 = Teacher.getTeacher();
Teacher tea2 = Teacher.getTeacher();
System.out.println(tea1 == tea2);
//true
}
}
class Teacher {
public static Teacher tea = new Teacher();
private Teacher() {
}
public static Teacher getTeacher() {
return tea;
}
}
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
public class MyTest2 {
public static void main(String[] args) {
Timer timer = new Timer();
MyTimerTask task = new MyTimerTask(timer);
timer.schedule(task,2000);
}
}
class MyTimerTask extends TimerTask{
private Timer timer;
public MyTimerTask(Timer timer) {
this.timer = timer;
}
@Override
public void run() {
Runtime runtime = Runtime.getRuntime();
try {
runtime.exec("calc");
//关机的命令为:shutdown \s \t 0
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("任务执行完成!");
timer.cancel();
}
}
需求: 计算一个for循环执行的时间
abstract class CalcClass {
public void calc() {
//算法骨架
long start = System.currentTimeMillis();
calcMethod();
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "毫秒");
}
public abstract void calcMethod();
}
class CalcFor extends CalcClass {
@Override
public void calcMethod() {
for (int i = 0; i < 5; i++) {
System.out.println("abc");
}
}
}
class CopyFile extends CalcClass {
@Override
public void calcMethod() {
System.out.println("复制文件");
}
}
-----测试类:
public class MyTest3 {
public static void main(String[] args) {
CalcClass js = new CalcFor();
js.calc();
js = new CopyFile();
js.calc();
/*abc
abc
abc
abc
abc
耗时:1毫秒
复制文件
耗时:0毫秒*/
}
}
interface Phone {
void call();
}
class BZPhone implements Phone {
private Phone phone;
//你要包装哪个类,把那个类传过来
public BZPhone(Phone phone) {
this.phone = phone;
}
@Override
public void call() {
this.phone.call();
}
}
class GamePhone extends BZPhone {
public GamePhone(Phone phone) {
super(phone);
}
@Override
public void call() {
super.call();
System.out.println("游戏功能");
}
}
class IPhone implements Phone {
public void call() {
System.out.println("打电话");
}
}
class MusicPhone extends BZPhone {
public MusicPhone(Phone phone) {
super(phone);
}
@Override
public void call() {
super.call();
System.out.println("听歌的功能");
}
}
class VideoPhone extends BZPhone {
public VideoPhone(Phone phone) {
super(phone);
}
@Override
public void call() {
super.call();
System.out.println("看视频的功能");
}
}
----测试类:
public class MyTest4 {
public static void main(String[] args) {
Phone iPhone = new IPhone();
iPhone.call();
System.out.println("=====================");
MusicPhone musicPhone = new MusicPhone(iPhone);
musicPhone.call();
System.out.println("====================");
VideoPhone videoPhone = new VideoPhone(iPhone);
videoPhone.call();
System.out.println("=============================");
VideoPhone videoPhone1 = new VideoPhone(new MusicPhone(iPhone));
videoPhone1.call();
System.out.println("============================");
MusicPhone musicPhone1 = new MusicPhone(new VideoPhone(iPhone));
musicPhone1.call();
System.out.println("====================");
GamePhone gamePhone = new GamePhone(new MusicPhone(iPhone));
gamePhone.call();
System.out.println("===========================");
GamePhone gamePhone1 = new GamePhone(new MusicPhone(new VideoPhone(iPhone)));
gamePhone1.call();
/*打电话
=====================
打电话
听歌的功能
====================
打电话
看视频的功能
=============================
打电话
听歌的功能
看视频的功能
============================
打电话
看视频的功能
听歌的功能
====================
打电话
听歌的功能
游戏功能
===========================
打电话
看视频的功能
听歌的功能
游戏功能*/
}
}
岗位类 求职者 猎头(注册方法,注销方法,发布方法)
import java.util.ArrayList;
public class Hunter {
//定义两个集合,用来装求职者,和工作岗位
private ArrayList<JobSeeker> jobSeekers = new ArrayList<>();
private ArrayList<Job> jobs = new ArrayList<>();
//注册工作岗位
public void addJob(Job job) {
jobs.add(job);
//工作岗位过来之后,通知求职者
notifyJobSeeker(job);
}
private void notifyJobSeeker(Job job) {
for (JobSeeker jobSeeker : jobSeekers) {
System.out.println(jobSeeker.getName() + "你好!有一份工作:" + job.getJobName() + "薪资:" + job.getSal() + "欢迎你前去面试");
}
}
//注册求助者
public void addJobSeeker(JobSeeker jobSeeker) {
jobSeekers.add(jobSeeker);
}
//注销
public void removeJobSeeker(JobSeeker jobSeeker) {
jobSeekers.remove(jobSeeker);
}
}
public class Job {
private String jobName;
private double sal;
public Job() {
}
public Job(String jobName, double sal) {
this.jobName = jobName;
this.sal = sal;
}
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
}
public class JobSeeker {
private String name;
public JobSeeker() {
}
public JobSeeker(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class MyTest5 {
public static void main(String[] args) {
//观察者 = 订阅者 + 发布者
JobSeeker zs = new JobSeeker("张三");
JobSeeker ls = new JobSeeker("李四");
JobSeeker ww = new JobSeeker("王五");
//在猎头出注册
Hunter hunter = new Hunter();
hunter.addJobSeeker(zs);
hunter.addJobSeeker(ls);
hunter.addJobSeeker(ww);
Job job = new Job("Java开发工程师", 8000);
hunter.addJob(job);
System.out.println("=====================");
Job job2 = new Job("前端开发工程师", 18000);
hunter.addJob(job2);
System.out.println("========================");
//注销
hunter.removeJobSeeker(ww);
Job job3 = new Job("运维工程师", 5000);
hunter.addJob(job3);
}
}