并发编程:
能并行运行多个程序或者一个程序中多个部分的能力。
进程&线程:
进程之间是相互隔离的,一个进程无法直接访问另一个进程的数据。一个进程包括由操作系统分配的内存空间,包含一个或多个线程。
线程是进程的一部分,
在多线程的处理之中,可以利用Runnable描述多个线程操作的资源,而Thread描述每一个线程对象,于是当多个线程访问同一资源的时候
下面编写一个简单的买票程序,将创建若干个线程对象实现买票的处理操作。
范例:实现卖票操作
如果追加了延迟,问题就暴露出来了。而实际上这个问题一直都在,这个问题就叫线程的不同步,要想解决就得同步
线程同步
经过分析之后已经可以确认同步问题缩产生的主要原因了,那么下面就需要进行同步问题的解决,但是解决同步问题的关键是锁、指的是当某一个线程执行操作的时候,其他线程外面等待。
如果要想在在程序之中实现这把锁的功能,就可以使用syncchronized关键字来实现,利用此关键字可以定义同步方法或同步代码块
在同步代码块的操作里面的代码只允许一个线程执行。
利用同步代码块进行处理
synchronized(同步对象){同步代码操作}
一般要进行同步对象处理的时候可以采用当前对象this进行同步
范例:利用同步代码块解决数据同步访问问题
加入同步处理之后,程序的整体性能下降了,同步实际上会造成性能的降低。
利用同步方法解决
只需要在方法定义上使用synchronized关键字即可
观察java类库会发现,系统中许多的类上使用的同步处理采用的都是同步方法
死锁
死锁是在进行多线程同步的处理之中有可能产生的一种问题。
所谓的死锁只的是若干个线程彼此互相等待的状态
范例:死锁的展示
现在死锁造成的主要原因是因为彼此都在互相等待着对方先让出资源。
死锁实际上是一种开发中出现的不确定的状态,
有的时候代码如果处理不当则会不定期出现死锁。
这是属于正常开发中的调试问题。
若干个线程访问同一资源师一定要进行同步处理,而过多的同步会造成死锁。
死锁是同步引起的!!!
在多线程的开发过程之中最为著名的案例就是生产者与消费者操作,该操作的主要流程如下:
生产者负责信息内容的生产
每当生产者生产完成一项完整的信息之后消费者要从这里面取走信息
如果生产者没有生产完则消费者要等待它生产完成,如果消费者还没有对信息进行消费,则生产者应该等待消费处理完成后再继续生产
程序的基本实现
可以将生产者与消费者定义为两个独立的线程类对象,但是对于现在生产的数据,可以使用如下的组成
通过整个代码的执行会发现
1 数据不同步了
2 应该是生产一个取走一个,结果是重复生产和重复取走
解决数据同步
如果要解决问题,首先解决的就是数据同步的处理问题,如果要想解决数据同步最简单的做法是使用synchronized关键字定义同步代码块或同步方法
于是这个时候对于同步的处理就可以直接在message类中完成
范例:解决同步操作
修改message类和producer类把王建和宇宙大帅哥绑定在一起。。。
在进行同步处理的时候肯定需要有一个同步的处理对象,那么此时肯定要将同步操作交由message类处理是最合适的
这个时候发现数据已经可以正常的保持一致了,但是对于重复操作的问题依然存在的。
线程等待与唤醒
想要解决生产者与消费者的问题,那么最好的解决方法就是使用等待与唤醒机制
而对于等待与唤醒的操作主要依靠的是object类中提供的方法处理的。
如果此时有若干个等待线程的话,那么notify()表示的是唤醒第一个等待的,而其它的线程继续等待
而notityall表示会唤醒所有等待的线程,优先级高的可能先执行
对于当前的问题主要的解决应该通过Message类完成处理
这种处理刑事就是在进行多线程开发过程之中最原始的处理方案,整个的等待、同步、唤醒机制都由开发者自行通过原生代码实现控制。
优雅的停止线程
在多线程操作之中如果要启动多线程肯定使用的是Thread类中的start()方法,而如果对于多线程需要进行停止处理
Thread类原本提供有stop()方法,但是这些方法从jdk1.2版本开始就已经将其废除了,而且一直到现在也不再建议出现在你的代码之中
而出了stop方法外还有一些方法也被禁用了
之所以废掉这些方法,主要的原因是因为这些方法有可能导致线程的死锁
要想实现线程的停止需要通过一种柔和的方式来进行
万一有其它的线程去控制着这个flag的内容,那么这个时候对于线程的停止也不是说停就立刻停止的。
而是会在执行的过程中判断flag的内容来完成
就像飞行员开飞机,如果指挥中心命令飞行员停止飞行,那么飞行员也不是立马就停下不开了,这样就会坠机,而是观察一下哪里能降落飞机
守护线程
假设有一个人并且有个保镖,一定是在这个人活着的时候保护,死了就没用了。
所以在多线程里面可以进行守护线程的定义,也就是说如果现在主线程的程序活着其它的线程还在执行的时候,那么守护线程将一直存在。
并且运行在后台状态
在Thread类里面提供有如下的守护线程的操作方法
如果把用户线程的Integer.max_value改成10,会发现用户线程跑完了,守护线程还在跑
daemonThread.setDaemon(true)
如果用这个代码设置daemonTrhead为守护线程后
可以发现所有的守护线程都是围绕在用户线程的周围,
用户线程跑完了,守护线程也会跟着跑完
在整个jvm里面最大的守护线程就是GC线程
程序执行中GC线程会一直存在,如果程序执行完毕,GC线程也将消失
volatile关键字
在多线程的定义之中,volatile关键字主要是在属性定义上使用的,表示此属性为直接数据操作,而不进行副本的拷贝处理
这样的话在一些书上就将其错误的理解为同步属性了
在正常进行变量处理的时候往往会经理如下的几个步骤
而一个属性上追加了volatile关键字,表示的就是不使用副本,而是直接操作原始变量,相当于节约了拷贝副本重新保存的步骤