接上一篇https://blog.csdn.net/weixin_44035017/article/details/101293121
多线程并发
线程同步
在多线程的程序中,容易发生资源抢占的问题,如同两个人同时过独木桥。所以在多线程编程中需要防止这些资源的冲突。java提供线程同步机制来防止资源访问的冲突。
(1)线程安全
比如火车票售票系统,当票数大于0时,就会售出。但当只剩一张票的时候,两个线程同时访问这段代码,第一个线程将票售出,与此同时第二线程也已经执行完成判读票是否大于0的操作,并得出票大于0的结论,于是也将票售出,两个线程都将票售出,但实际票只有一张。
1.火车票
public class SafeTest implements Runnable {
int num=10;
@Override
public void run() {
while(true) {
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("票数:" + num--);
}
}
}
public static void main(String[] args) {
SafeTest t=new SafeTest();
Thread thread1=new Thread(t);
Thread thread2=new Thread(t);
Thread thread3=new Thread(t);
Thread thread4=new Thread(t);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
输出结果:
票数:10
票数:9
票数:9
票数:9
票数:8
票数:5
票数:6
票数:7
票数:4
票数:3
票数:2
票数:4
票数:1
票数:-2
票数:-1
票数:0
上述代码结果可以看出,产生了脏数据,这就是线程不同步导致的。比如线程A刚进入if 语句的时候,num=1;但在这时线程B刚执行完num–,这时num就变成了0,再假如其他线程也刚执行完num–,这样num就变成了负数。这就是线程不同步导致的
除了负数还存在相同票数,这就是线程的工作空间与主存的交互问题
假设主存有十张票,线程a要将主存的数据拷贝到工作空间,所以线程a为十张票,线程a做–操作,然后就9张票返回去,覆盖主存的十张票,但在覆盖之前线程b已经拷贝了之前的十张票了,线程b做–操作后返回主存还是9张票,这就是线程不同步导致的不安全问题
2.取钱
public class Test {
public static void main(String[] args) {
//账户
Account account =new Account(100,"张三");
Take you = new Take(account,80,"张三取钱");
Take wife = new Take(account,90,"张三的女朋友取钱");
you.start();
wife.start();
}
}
class Account{
int money; //金额
String name; //名称
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//模拟取款
class Take extends Thread{
Account account ; //取钱的账户
int TakeMoney ;//取的钱数
int packetMoney ; //口袋的总数
public Take(Account account, int drawingMoney,String name) {
super(name);
this.account = account;
this.TakeMoney = drawingMoney;
}
@Override
public void run() {
if(account.money -TakeMoney<0) {
return;
}
try {
Thread.sleep(1000); //模拟延时
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -=TakeMoney;
packetMoney +=TakeMoney;
System.out.println(this.getName()+" 账户余额为:"+account.money);
System.out.println(this.getName()+" 口袋的钱为:"+packetMoney);
}
}
输出结果:
张三的女朋友取钱 账户余额为:-70
张三的女朋友取钱 口袋的钱为:90
张三取钱 账户余额为:-70
张三取钱 口袋的钱为:80
显然加上了if(account.money -TakeMoney<0) 并没有用,这也是线程不安全造成的
3.操作容器
public class Test {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();
for(int i=0;i<10000;i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}) .start();
}
System.out.println(list.size());
}
}
输出结果:
9986
显然发生了数据丢失,没有达到10000。这也是线程不安全造成的
当需要做数据的修改操作时,应该考虑线程的安全问题。仅仅只做读取数据时,可以不用考虑线程同步
(2)线程并发
线程并发是同一个对象多个线程同时操作
比如账户
同一个对象:大家各自操作各自的账户,就不会出现问题
多个线程::同一个对象,只有一个人去操作这个对象,也不会出现问题
同时:多个线程都在同时操作时,才会出现问题。比如同一个账户,a和b取钱,但a上午取得,b下午取得,也不会发生问题
解决线程并发的方法:队列+锁
现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。 比 如:上公交车,大家都想上去。解决办法就是排队。
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。 这时候,我们就需要用到“线程同步”。 线程同步其实就 是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
然后在队列里选一个线程,那如何知道某个线程已经获得对象了呢,这就需要锁机制了。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带 来了访问冲突的问题。为了保证数据在方法中被访问时的正确性,在访问 时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源, 其他线程必须等待,使用后释放锁即可。
线程同步存在一些问题:
一个线程持有锁会导致其它所有需要此锁的线程挂起;
在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时, 引起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁可能会导致优先级倒置,引起性能问题
(3)线程同步
synchronized关键字,它包括两种用法:synchronized 方法和 synchronized 块。
锁一个对象(资源),可以锁一个具体的对象。
如果在本类中,你操作的是一个成员方法,那么你锁的就是this.如果是一个静态方法,那么它锁的就是对象的模子(class对象 反射机制)
synchronized 方法控制对“成员变量|类变量”对象的访问,若将一个大的方法声明为synchronized 将会大大影响效率。比如一个方法里面操作对象(资源)的两个属性:a属性和b属性 。但仅仅b属性涉及修改,a属性只读。当你加锁了,其他像访问 a 的也只能等待,但是 修改才会引起线程的不安全。这就是synchronized的范围太大了。这可以借助synchronized块
范围太大影响效率,但范围太小可能锁不住。这也是一个难点
加锁时,还应该考虑锁的对不对,比如两个对象a,b,需要操作b对象,但是锁成了a对象或者this对象,还是会造成不安全
(2)线程同步机制
解决资源共享问题,就是给定时间内只允许一个线程访问共享资源,这就需要给共享资源上锁。上了锁,其他线程无法访问这个资源。
1.同步方法
synchronized void f() //在方法前加synchronized 关键字
public class SafeTest implements Runnable {
int num=10;
private boolean flag = true;
@Override
public void run() {
while(flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sell();
}
}
private synchronized void sell(){ //线程同步方法
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("票数:" + num--);
}else{
flag = false;
return ;
}
}
public static void main(String[] args) {
SafeTest t=new SafeTest();
Thread thread1=new Thread(t);
Thread thread2=new Thread(t);
Thread thread3=new Thread(t);
Thread thread4=new Thread(t);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
输出结果:
票数:10
票数:9
票数:8
票数:7
票数:6
票数:5
票数:4
票数:3
票数:2
票数:1
synchronized方法锁的是对象(资源),上面的sell()方法是成员方法,锁的是this对象,而sell()方法里就有操作this对象的动作 num–.如果操作的不是this对象,那就没锁住
比如下面
public class UnsafeTest02 {
public static void main(String[] args) {
//账户
Account account =new Account(100,"张三");
Take you = new Take(account,80,"张三取钱");
Take wife = new Take(account,90,"张三的女朋友取钱");
you.start();
wife.start();
}
}
class Account{
int money; //金额
String name; //名称
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//模拟取款
class Take extends Thread{
Account account ; //取钱的账户
int TakeMoney ;//取的钱数
int packetMoney ; //口袋的总数
public Take(Account account, int drawingMoney,String name) {
super(name);
this.account = account;
this.TakeMoney = drawingMoney;
}
@Override
public void run() {
take();
}
private synchronized void take(){ //锁的是this
if(account.money -TakeMoney<0) {
return;
}
try {
Thread.sleep(1000); //模拟延时
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -=TakeMoney;
packetMoney +=TakeMoney;
System.out.println(this.getName()+" 账户余额为:"+account.money);
System.out.println(this.getName()+" 口袋的钱为:"+packetMoney);
}
}
输出结果:
张三取钱 账户余额为:-70
张三的女朋友取钱 账户余额为:-70
张三取钱 口袋的钱为:80
张三的女朋友取钱 口袋的钱为:90
synchronized void take()锁的是this,但是需要加锁的不是this而是account对象。所以还是造成了线程的不安全.所以应该使用synchronized同步块
2.同步块
使用synchronized关键字
synchronized(Object){
}
Object称之为同步监视器 Object可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监 视器是this即该对象本身,或class即类的模子
通常将共享资源放置在synchronized定义的区域内,这样当其他线程也获取到这个锁时,必须等待锁被释放时才能被才能进入该区域。Object为任意一个对象,每个对象都存在一个标志位,并具有两个值,0和1.一个线程运行到同步块时,首先检查该对象的标志位,如果为0,说明此同步块中存在其他线程在运行。这时,该线程处于就绪状态,直到处于同步块中的线程执行完同步块的代码为止。这时该对象的标志位设置为1,该线程才能执行同步块中的代码,并将Object对象的标志位设为0,防止其他线程执行同步块中的代码。
public class UnsafeTest02 {
public static void main(String[] args) {
//账户
Account account =new Account(100,"张三");
Take you = new Take(account,80,"张三取钱");
Take wife = new Take(account,90,"张三的女朋友取钱");
you.start();
wife.start();
}
}
class Account{
int money; //金额
String name; //名称
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//模拟取款
class Take extends Thread{
Account account ; //取钱的账户
int TakeMoney ;//取的钱数
int packetMoney ; //口袋的总数
public Take(Account account, int drawingMoney,String name) {
super(name);
this.account = account;
this.TakeMoney = drawingMoney;
}
@Override
public void run() {
take();
}
private void take(){
synchronized (account) {
if (account.money - TakeMoney < 0) {
System.out.println("余额不足");
return;
}
try {
Thread.sleep(1000); //模拟延时
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -= TakeMoney;
packetMoney += TakeMoney;
System.out.println(this.getName() + " 账户余额为:" + account.money);
System.out.println(this.getName() + " 口袋的钱为:" + packetMoney);
}
}
}
输出结果:
张三取钱 账户余额为:20
张三取钱 口袋的钱为:80
余额不足
这就不会出现负数,当张三取钱完还剩20,张三的女朋友取钱90时,余额不足取不了钱
当多个线程都进来的时候都需要等,但是账户此时已经没钱了,后面的线程排队进入资源肯定是没有结果的。就好比10个人去食堂窗口买饭,到第五个人的时候饭没了,但是synchronized关键字导致所有人都必须排队,明明没饭了还要去排队去访问资源。所以可以在synchronized前加一个判断来提升并发的性能。
private void take(){
if(account.money<=0){ //性能优化
return;
}
synchronized (account) {
if (account.money - TakeMoney < 0) {
System.out.println("余额不足");
return;
}
try {
Thread.sleep(1000); //模拟延时
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -= TakeMoney;
packetMoney += TakeMoney;
System.out.println(this.getName() + " 账户余额为:" + account.money);
System.out.println(this.getName() + " 口袋的钱为:" + packetMoney);
}
}
操作容器
public class Test {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();
for(int i=0;i<10000;i++) {
new Thread(()->{
//同步块
synchronized(list) {
list.add(Thread.currentThread().getName());
}
}) .start();
}
Thread.sleep(10000);
System.out.println(list.size());
}
}
输出结果:
10000
加入Thread.sleep(10000)是为了等所有线程执行完再去统计。没有synchronized同步块,等待之后统计还是没有用
package com.THREAD;
public class SafeTest implements Runnable {
int num=10;
@Override
public void run() {
while(true) {
synchronized (this) { //加锁
if (num > 0) {
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("票数:" + num--);
}
}
}
}
public static void main(String[] args) {
SafeTest t=new SafeTest();
Thread thread1=new Thread(t);
Thread thread2=new Thread(t);
Thread thread3=new Thread(t);
Thread thread4=new Thread(t);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
输出结果:
票数:10
票数:9
票数:8
票数:7
票数:6
票数:5
票数:4
票数:3
票数:2
票数:1
从上面结果可以看出,输出结果是按顺序依次递减的,这就是线程同步的效果
多线程并发性能分析
同步块可以范围更小的锁定资源,在范围更小的情况下尽可能的提升它的性能
(1)范围太大,导致效率太低
public class SafeTest implements Runnable {
int num=10;
private boolean flag = true;
@Override
public void run() {
while(flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sell();
}
}
private void sell(){ //线程同步方法
synchronized(this) {
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("票数:" + num--);
} else {
flag = false;
return;
}
}
}
public static void main(String[] args) {
SafeTest t=new SafeTest();
Thread thread1=new Thread(t);
Thread thread2=new Thread(t);
Thread thread3=new Thread(t);
Thread thread4=new Thread(t);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}输出结果:
票数:10
票数:9
票数:8
票数:7
票数:6
票数:5
票数:4
票数:3
票数:2
票数:1
很显然锁住了,但是范围太大 ,效率低下.思考一下只是num做改变,所以考虑能不能只锁num
(2)对象不对,导致线程不安全
public class SafeTest implements Runnable {
int num=10;
private boolean flag = true;
@Override
public void run() {
while(flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sell();
}
}
private void sell(){
synchronized((Integer)num) { //
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("票数:" + num--);
} else {
flag = false;
return;
}
}
}
public static void main(String[] args) {
SafeTest t=new SafeTest();
Thread thread1=new Thread(t);
Thread thread2=new Thread(t);
Thread thread3=new Thread(t);
Thread thread4=new Thread(t);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
输出结果:
票数:10
票数:9
票数:8
票数:8
票数:7
票数:6
票数:5
票数:4
票数:3
票数:2
票数:1
票数:0
票数:-1
很显然没锁住,这是因为synchronized(Obj)只能传入对象,而(Integer)num每次是不同的对象,比如num为1时的Integer对象和num为2时的Integer对象是不同的。而且flag也没有锁住
(3)范围太小导致没锁住
如果只锁flag
private void sell(){
synchronized(this) {
if(num<=0) {
flag = false;
return ;
}
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
}
这是锁不住的,假设三个线程a,b,c,还剩一张票,当a线程进入synchronized时锁住了发现还有票没有改变flag,然后退出synchronized块,并等待200毫秒。在等待的过程中并没有做num–操作。因为等待时已经释放了锁,所以线程b进入了synchronized块,发现还有一张票。然后退出synchronized块线程b等待。
这时线程c可以进入synchronized块。进入了之后,因为线程a,b都在等待,它们都没有做num–操作,所以这时线程c还是没有改变flag,它也继续往下走了。线程a,b,c都进入这个方法,所以当线程a做–,还剩0张票,但此时线程b和c已经进来了,所以他们也可以做 - -操作,所以还会导致负数造成线程不安全
private void sell(){
if(num<=0) {//考虑的是没有票的情况
flag = false;
return ;
}
synchronized(this) {
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("票数"+num--);
}
}
输出结果:
票数10
票数9
票数8
票数7
票数6
票数5
票数4
票数3
票数2
票数1
票数0
票数-1
票数-2
如果这么锁,还是会导致线程不安全。情况还是跟刚才一样,在休眠未改变num时,线程a,b,c都已经判断完if(num<=0),然后去排队。
(4)线程安全并尽可能锁定合理范围
private void sell(){
if(num<=0) {//考虑的是没有票的情况
flag = false;
return ;
}
synchronized(this) {
if (num <= 0) { //考虑最后的1张票
flag = false;
return;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("票数"+num--);
}
}
输出结果:
票数10
票数9
票数8
票数7
票数6
票数5
票数4
票数3
票数2
票数1
合理的范围不是指代码 ,是指数据的完整性
这是个双重检测,主要考虑零界值得问题
所以多用synchronized块,少用synchronized方法
死锁
多个线程各自占有一些共享资源,并且互相 等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
某一个同步块同时拥有“两个以上对 象的锁”时(锁套锁),就可能会发生“死锁”的问题
比如一手交钱,一手交货
两个人a,b,a有货,b有钱。a说一手交钱,一手交货。b说一手交货,一手交钱。两个人都不松口,不信任对方。这就造成死锁局面
public class DeadLock {
public static void main(String[] args) {
Change c1=new Change(1,"张三");
Change c2=new Change(0,"李四");
c1.start();
c2.start();
}
}
class Thing{
}
class Money{
}
class Change extends Thread{
static Thing thing=new Thing();
static Money money=new Money();
int choice;
String person;
public Change(int choice,String person){
this.choice=choice;
this.person=person;
}
@Override
public void run() {
change();
}
private void change() {
if(choice==0){
synchronized (thing){
System.out.println("我有货,你交钱");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (money){
System.out.println(this.person+"拿到钱了");
}
}
}else{
synchronized (money){
System.out.println("我有钱,你交货");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (thing){
System.out.println(this.person+"拿到货了");
}
}
}
}
}
这就造成了死锁的尴尬局面,对方互相拥有对方资源的锁,互相等待对方释放资源的锁,解决办法就是不要锁套锁。
public class DeadLock {
public static void main(String[] args) {
Change c1=new Change(1,"张三");
Change c2=new Change(0,"李四");
c1.start();
c2.start();
}
}
class Thing{
}
class Money{
}
class Change extends Thread{
static Thing thing=new Thing();
static Money money=new Money();
int choice;
String person;
public Change(int choice,String person){
this.choice=choice;
this.person=person;
}
@Override
public void run() {
change();
}
private void change() {
if(choice==0){
synchronized (thing){
System.out.println("我有货,你交钱");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (money){
System.out.println(this.person+"拿到钱了");
}
}else{
synchronized (money){
System.out.println("我有钱,你交货");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (thing){
System.out.println(this.person+"拿到货了");
}
}
}
}
输出结果:
我有货,你交钱
我有钱,你交货
张三拿到货了
李四拿到钱了
并发容器
public class Test {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();
for(int i=0;i<10000;i++) {
new Thread(()->{
//同步块
synchronized(list) {
list.add(Thread.currentThread().getName());
}
}) .start();
}
Thread.sleep(10000);
System.out.println(list.size());
}
}
输出结果:
10000
在操作容器时,我们使用synchronized块将list锁住,实际在Juc的并发编程中,有对应的并发容器,其内部已经实现了锁
在java.util.concurrent包下有CopyOnWriteArrayList
public class Test {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for(int i=0;i<10000;i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}) .start();
}
Thread.sleep(10000);
System.out.println(list.size());
}
}
输出结果:
10000
线程协作
生产者消费者模式
生产者和消费者问题
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费;
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生 产并等待,直到仓库中的产品被消费者取走为止;
如果仓库中放有产品,则消费者可以将产品取走消费,否则停 止消费并等待,直到仓库中再次放入产品为止。
线程通信
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消 费者之间相互依赖,互为条件
对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后, 又需要马上通知消费者消费 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品。
在生产者消费者问题中,仅有synchronized是不够的 。synchronized可阻止并发更新同一个共享资源,实现了同步 。synchronized不能用来实现不同线程之间的消息传递(通信)
有两种解决办法
1.管程法
建立一个缓冲区。
生产者:负责生产数据的模块(这里模块可能是:方法、对象、线程、进程);
消费者:负责处理数据的模块(这里模块可能是:方法、对象、线程、进程);
缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区”;。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。
这样就解决了闲时太闲,忙时太忙的问题。
2.信号灯法
类似红绿灯
如何唤醒和等待,java的Object类就提供了方法
方法名 | 作用 |
---|---|
final void wait() | 表示线程一直等待,直到其他线程通知, 与sleep不同,会释放锁 |
final void wait(long timeout) | 指定等待的毫秒数 |
final void notifiy() 唤醒一个处于等待状态的线程 | |
final void notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
都只能在同步方法或者同步代码块中使用,否则会抛出异常
(1)管程法
模拟KFC卖汉堡
package Learn;
public class ThreadCommunication {
public static void main(String[] args) {
Buffer buffer=new Buffer();
new Productor(buffer).start();
new Customer(buffer).start();
}
}
class Customer extends Thread{
Buffer buffer;
public Customer(Buffer buffer){
this.buffer=buffer;
}
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("消费第"+buffer.pop().id+"个汉堡");
}
}
}
class Productor extends Thread{
Buffer buffer;
public Productor(Buffer buffer){
this.buffer=buffer;
}
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("生产第"+i+"个汉堡");
buffer.push(new Harmburger(i));
}
}
}
class Harmburger{
int id;
public Harmburger(int id) {
this.id = id;
}
}
class Buffer{
Harmburger harmburgers[]=new Harmburger[10];
int count=0;
public synchronized void push(Harmburger burger){
harmburgers[count++]=burger;
}
public synchronized Harmburger pop(){
count--;
Harmburger harmburger=harmburgers[count];
return harmburger;
}
}
这是没有加唤醒和等待的情况
报异常,数组越界。生产者一直在那生产,没有等待。尽管有消费者拿汉堡,但是还是超过了缓冲区的数组大小。
package Learn;
public class ThreadCommunication {
public static void main(String[] args) {
Buffer buffer=new Buffer();
new Productor(buffer).start();
new Customer(buffer).start();
}
}
class Customer extends Thread{
Buffer buffer;
public Customer(Buffer buffer){
this.buffer=buffer;
}
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("消费第"+buffer.pop().id+"个汉堡");
}
}
}
class Productor extends Thread{
Buffer buffer;
public Productor(Buffer buffer){
this.buffer=buffer;
}
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("生产第"+i+"个汉堡");
buffer.push(new Harmburger(i));
}
}
}
class Harmburger{
int id;
public Harmburger(int id) {
this.id = id;
}
}
class Buffer{
Harmburger harmburgers[]=new Harmburger[10];
int count=0;
public synchronized void push(Harmburger burger){
if(count==harmburgers.length){
try {
this.wait(); //线程阻塞 当消费者通知生产者时,解除阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
harmburgers[count++]=burger;
this.notifyAll(); //存在汉堡,通知消费者
}
public synchronized Harmburger pop(){
if(count==0){
try {
this.wait(); //线程阻塞 当生产者通知消费者时,阻塞解除
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
Harmburger harmburger=harmburgers[count];
this.notifyAll(); //存在空间 通知生产者可以生产
return harmburger;
}
}
没有发生阻塞
(2)信号灯法
使用标志位,模拟信号灯
package Learn;
public class ThreadCommunication2 {
public static void main(String[] args) {
Phone phone=new Phone();
new Father(phone).start();
new Son(phone).start();
}
}
class Father extends Thread{
Phone phone;
public Father(Phone phone) {
this.phone = phone;
}
@Override
public void run() {
for(int i=0;i<10;i++){
phone.Fathertalk("老爸的第"+i+"句话");
}
}
}
class Son extends Thread{
Phone phone;
public Son(Phone phone) {
this.phone = phone;
}
@Override
public void run() {
for(int i=0;i<10;i++){
phone.SonReceive();
}
}
}
class Phone{
String talk;
boolean flag=true;
public synchronized void Fathertalk(String talk){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("老爸说:"+talk);
this.notifyAll();
this.flag=!this.flag;
}
public synchronized void SonReceive(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("儿子收到");
this.notifyAll();
this.flag=!this.flag;
}
}
输出结果:
老爸说:老爸的第0句话
儿子收到
老爸说:老爸的第1句话
儿子收到
老爸说:老爸的第2句话
儿子收到
老爸说:老爸的第3句话
儿子收到
老爸说:老爸的第4句话
儿子收到
老爸说:老爸的第5句话
儿子收到
老爸说:老爸的第6句话
儿子收到
老爸说:老爸的第7句话
儿子收到
老爸说:老爸的第8句话
儿子收到
老爸说:老爸的第9句话
儿子收到
任务定时调度
(1)Timer/TimerTask
1.Timer
类似闹钟,本身就是线程。对应于每个Timer对象是单个后台线程,用于依次执行所有定时器的所有任务
线程调度任务以供将来在后台线程中执行的功能。 任务可以安排一次执行,或定期重复执行。
在最后一次对Timer对象的引用后,所有未完成的任务已完成执行,定时器的任务执行线程正常终止(并被收集到垃圾回收。 如果主叫方想要快速终止定时器的任务执行线程,则调用者应该调用定时器的cancel方法。
这个类是线程安全的:多个线程可以共享一个单独的Timer对象,而不需要外部同步。
构造方法
Timer() ; //创建一个新的计时器
Timer(boolean isDaemon); //创建一个新的定时器,可以设置为守护线程
Timer(String name) ; //其相关线程具有指定的名称。
Timer(String name, boolean isDaemon) ;
常用方法
方法 | 说明 |
---|---|
schedule(TimerTask task, Date time) | 在指定的时间安排指定的任务执行。 |
schedule(TimerTask task, Date firstTime, long period) | 从指定 的时间开始 ,对指定的任务执行重复的 固定延迟(每隔一段时间)执行 |
schedule(TimerTask task, long delay) | 在指定的延迟之后安排指定的任务执行 |
schedule(TimerTask task, long delay, long period) | 在指定 的延迟之后开始 ,重新执行 固定延迟执行的指定任务。 |
2.TimerTask
一个抽象类,该类实现了Runnable接口
可以由计时器进行一次性或重复执行的任务
需要重写run()方法
public class TimeTest {
public static void main(String[] args) {
Timer timer=new Timer();
timer.schedule(new Task(),1000,200); //1秒后开始,每隔200毫秒执行一次
}
}
class Task extends TimerTask{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("第"+i+"次");
}
}
}
每隔两百毫秒,它会继续执行。
public class TimeTest {
public static void main(String[] args) {
Timer timer=new Timer();
Calendar cal=new GregorianCalendar(2021,1,1,00,00,00); //日历类 2021年1月1日 0时0分0秒
timer.schedule(new Task(),cal.getTime(),200); //2021年开始执行,每隔200毫秒开始执行
}
}
class Task extends TimerTask{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("第"+i+"次");
}
}
}
(2)QUARTZ
任务调度框架,该框架由四大部分组成
1.Scheduler :调度器
2.Tigger:触发条件,采用DSL模式
3.JobDetall:需要处理的job
4.Job:执行逻辑
这个框架已经集成到Spring中
在官网可以下载该框架
打开解压包,将其lib下的文件放到项目的lib文件夹中。
然后将所有文件加入到项目的构建路径
在Quartz解压包下的example文件夹下的src文件夹下有官方的例子,可学习
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class HelloJob implements Job {
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
System.out.println("-------start---------");
System.out.println("Hello World! - " + new Date());
System.out.println("-------end---------");
}
}
import static org.quartz.DateBuilder.evenSecondDateAfterNow;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
public class QuartzTest {
public void run() throws Exception {
// 1、创建 Scheduler的工厂
SchedulerFactory sf = new StdSchedulerFactory();
//2、从工厂中获取调度器
Scheduler sched = sf.getScheduler();
// 3、创建JobDetail
JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();
// 时间
Date runTime = evenSecondDateAfterNow();
// 4、触发条件
Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();
// 5、注册任务和触发条件
sched.scheduleJob(job, trigger);
// 6、启动
sched.start();
try {
// 100秒后停止
Thread.sleep(100L * 1000L);
} catch (Exception e) {
}
sched.shutdown(true);
}
public static void main(String[] args) throws Exception {
QuartzTest example = new QuartzTest();
example.run();
}
}
将解压包下的example的example1打开。将里面的log4j导入到src下