多线程详解
多线程详解
多线程实际上就是一种多个任务同时处理的技术。
比如一个银行只有一个窗口,所有人到这里来排队取钱,这是我们平时的程序,所有人都要排队进行,线性执行。多线程技术则是开设了多个窗口一同对来的客户进行业务处理,多个人可在同一个时间执行取钱这个操作。
如果你理解了这个概念。我们就要从微观上面继续讲解。
在微观上这个例子并不能很好地解释多线程发生时的状况。
进程与线程
这里要先讲一个小知识,就是时间片轮转技术。它是多线程在微观上的实现,将CPU的运行时间分为多个小小的时间片,每个任务(线程)会被分配这些时间片,当被分到的时候就意味着CPU已经正在处理这个人任务。当这个时间片(分配的运行时间)运行结束(时间到了)。线程将会让出CPU资源,给其他线程。
您可能想问,说这个实际上似乎没有什么意义,毕竟所有的任务所需要的时间都是一样的,你分开进行,和一同排队进行CPU所要工作的时间不是一样的吗?
当然如此,但是你忽略了一个重点.就是任务会有阻塞这个问题.如果任务发成阻塞,再不是多线程的情况下,所有的线程都要等待.CPU不做任何运算,等待阻塞任务重新需要CPU资源.
但是在多线程中这个被阻塞的线程会让出资源,让其他资源进行执行.效率提高.也就是说,只要有任务,CPU就是在执行的,最大限度的利用了CPU资源.
有了这个先到知识,我们开始了解进程和线程。
我更愿意将线程和进程的关系看成这个样子。(也许不大准确)
我们操作计算机中的程序完成各种各样的任务,而每个程序则是依靠进程来工作,这在任务管理器中有很好的展示。
我们可以看见Typora这个软件有两个进程,占有了CPU。当然你也可以将运行的所有的exe程序理解为一个进程.
计算机依靠进程来分配各种计算机资源(内存,空间等....).
而线程就是依靠计算机分配给进程的这些资源来进行工作的,可以这么说线程是进程的实体化.
线程实际上是一个由程序入口,一个计算机指令序列和一个程序出口构成的,它依靠在进程中的资源(内存,空间)进行工作,线程本身不占据多少空间,(实际上也是占据一些的.非常少,可以忽略不计.)而CPU资源实际上是直接被分配给线程的,用以运行其中的计算机指令.也就是我们说的多线程,实际上是线程同步运行发生的各种问题.
线程中有的资源
一个指向当前指令的指针 --必须要知道要执行什么方法吧?所以要有一个
一个栈 --虚拟机栈
一个寄存器值集合 --程序计数器?
一个私有数据区
(我觉得就是JVM线程隔离部分)
所以我们总结一下:
- 计算机靠进程分配资源(系统进行资源分配的独立单位),线程则是被分配给CPU资源(CPU调度的独立单位).
- 线程是进程的实体
- 线程无空间,进程有独立空间概念.多个线程共享
- 一个程序有多个进程,一个进程有多个线程
java创建线程
线程创建有两种方法,分别是继承Thread和实现Runnable接口.
我们在讲解这两种方式之前要了解一部分相关的知识,打开javaApi我们可以发现,Thread类实际上实现了Runnable接口,他们之间具有多太关系.也就是说,Thread可以向上转型位Runnable类.
两者的关系没有什么本质的差别,实际上都是差不多的.
那么问题来了,为什么要出现这个Runnable方法呢?这不是多此一举嘛!
并不是的,我们知道在java中只能单继承,也就是说只能有一个父类.
在这种情况下,所有子类(Obeject的子类这种不算)都会出现问题,他们根本无法在实现多线程了.
package TEST;
public class HOMEWORK1 {
}
class home extends HOMEWORK1,Thread{//这里无法通过编译,会有红色波浪线
}
所以要有Runnable这种实现多线程的方式.
1.Thread实现多线程
方式很简单,只要让多线程的实现类直接继承Thread并实现run()方法就行.
class Alogin extends Thread{
public void run(){
LoginServer.dopost1("a","aa");
}
}
启动方式比较简单,直接实例化调用start()方法即可.
2.Runnable实现多线程
package TEST;
//测试类
public class HOMEWORK1 {
public static void main(String[] args) {
home home = new home();
Thread thread = new Thread(home);//必须将Runnable对象传入Thread中才可以
thread.start();//必须调start()开始,如果调用run()则只会按照一个普通程序执行,不会有多线程的特性
}
}
//实现Runnable的类
class home implements Runnable {
public void run() {
System.out.println(Thread.currentThread());
}
}
3.Runnable与Thread
看了上面两个内容我们似乎知道了这两者之间的差别,
但是我们不要忘了Thread既然实现了Runnable接口就意味着可以向上转型,如下代码是正确的.
public class HOMEWORK1 {
public static void main(String[] args) {
Runnable home = new home();//实现Thread的类可以被向上转型为Runnable对象
Thread thread = new Thread(home);//依然可以成功实现
thread.start();
}
}
class home extends Thread {
public void run() {
System.out.println(Thread.currentThread());
}
}
4.线程在实践中的一些特性
实际上多线程序在运行中有一些和普通程序也有区别.其比较核心的就是随机性.
public class HOMEWORK1 {
public static void main(String[] args) {
Runnable home = new home();
Thread thread = new Thread(home);
thread.start();
System.out.println("他先执行----执行成功");
}
}
说白了就是跟线程相关的代码未必会按照顺序执行.
如下是输出:
用的还是上一个例子中的那个线程类,我们发现并没有因为 thread.start();
在 System.out.println("他先执行----执行成功");
中先执行,就先动用这个线程.这就是随机性.(这是因为异步执行,main方法代表的是一个main线程,而Thread对象激活的thread线程和main不是一个线程,所以他俩同时执行.)
他先执行----执行成功
Thread[Thread-1,5,main]
要想解决这个问题就加一个sleep()就ok!
如下样子:
public class HOMEWORK1 {
public static void main(String[] args) throws InterruptedException {
Runnable home = new home();
Thread thread = new Thread(home);
thread.start();
Thread.sleep(3000);
System.out.println("他先执行----执行成功");
}
}
Thread[Thread-1,5,main]
他先执行----执行成功
附加内容--停止线程
一个线程既然能启动,就能终止。那么在多线程中如何终止线程。
实际上多线程中并不能主动终止线程,这里的主动终止是指调用方法去终止。
不能说完全没有,实际上也有一个方法
stop()
但因为容易导致问题,所以这个代码也就不再使用了.
所以停止线程一般使用一些复杂一些的方式,主要有如下几个:
return停止 //实际上是让run()方法终止,线程自动死亡
interrupt停止//这个操作实际上要复杂很多,要混合其他方法进行停止
沉睡中停止,sleep() //实际上也是利用异常停止
异常法停止
1.interrupt停止
interrupt
实际上不能停止线程,只会给线程一个停止标志,可以被专门的方法检测到.你必须用相关的方法才能让相关的线程停止.
public class MyThread extends Thread {
public void run(){
for (int i = 0; i <100 ; i++) {
System.out.println(i);
}
}
}
class RUN{
/**
* 完整的执行了程序,说明
* interrupt并没有中断程序,正常来讲是要用5000000这种大数据的,但是电脑不好就用100代替.
* 所以这里要假设一下,主线程停止大的两秒中,myThread线程不会消失.
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(2000);
myThread.interrupt();//中断信号
}
而这个让线程停止的方法则是
isterrupted
isInterrupted
两者的作用都是测试线程是否已经被中断
但是两者还是有差别的.
public class MyThread extends Thread {
@Override
public void run() {
super.run();
for (int i = 0; i <500000 ; i++) {
System.out.println(i);
}
}
}
class Run{
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(1000);
thread.interrupt();
System.out.println("是否停止"+thread.interrupted());//false
System.out.println("是否停止:"+thread.interrupted());//false
//这是因为isInterrupted判断的是当前线程,这个当前线程就是 main main一直没有给过中断
System.out.println("end!");
}
}
class Run2{
public static void main(String[] args) {
Thread.currentThread().interrupt();
System.out.println(Thread.currentThread().interrupted());
System.out.println(Thread.currentThread().interrupted());
/**
* true
* false
* interrupted(会发生置位
*/
}
}
//如何正确使用isInterrupted
class RUN3{
public static void main(String[] args) throws InterruptedException {
MyThread thread=new MyThread();
thread.start();
Thread.sleep(1000);
thread.interrupt();
System.out.println("是否停止1?"+thread.isInterrupted());//true
System.out.println("是否停止2?"+thread.isInterrupted());//true
}
}
从上面的代码中我们可以看到两者的差别isterrupted
方法会导致停止标志置位,即第一次调用如果答案是true,则将停止标志重新置为false.
interrupt
方法如果在线程处于sleep()时挂起调用那么就会出现抛出一个异常
而isInterrupted
则是只判断线程是否停止,而不对其进行任何操作.
2.return停止
return停止实际上我会在之后讲,即如果run()方法执行完毕,线程自动死亡.所以return可以结束run()方法,run()方法结束则线程死亡,线程死亡了自然线程就结束了.
这就是return结束线程的逻辑.
3.沉睡中停止,sleep() ,异常法停止
这两个问题其实也是一个问题.其核心在于如果在线程中出现异常,线程会自动停止执行,并释放锁.
而**沉睡中停止,sleep() **方法就是依靠这个
Thread.sleep();
Thread.interrupt;
这样程序就会报错,自然程序就停止了.
5.同步
a.多线程问题的由来
在讲解之后的同步问题之前,我们还要讲解一下为什么要有同步的方法的存在.
出现这种问题的原因就在于多个线程使用同一个资源.
这种对同一个资源的操作容易导致了问题.
如果一个资源没用这种共享发生,就不会有问题.
例子如下:
package TEST;
public class HOMEWORK1 {
public static void main(String[] args) throws InterruptedException {
Runnable home = new home();
System.out.println("线程不共享资源");
home1 home1 = new home1();
home1 home2 = new home1();
home1.start();
home2.start();
Thread.sleep(1000);
System.out.println("线程共享资源");
Thread thread = new Thread(home,"一号线程");
Thread thread1 = new Thread(home,"二号线程");
Thread thread3 = new Thread(home,"三号线程");
thread.start();
thread1.start();
thread3.start();
}
}
class home extends Thread {
private int count=5;
public void run() {
count--;//共享资源每次线程都会减1,值会变化(而且还不是正确的变化,这就是所谓的脏读)
System.out.println(Thread.currentThread()+" 的值为"+count);
}
}
class home1 extends Thread {
private int count=5;
public void run() {
count--;//不共享资源.每个都是独立的值不会变化
System.out.println(Thread.currentThread()+" 的值为"+count);
}
}
输出的值
Thread[Thread-1,5,main] 的值为4
Thread[Thread-2,5,main] 的值为4
线程共享资源
Thread[三号线程,5,main] 的值为3
Thread[二号线程,5,main] 的值为2
Thread[一号线程,5,main] 的值为3
其实从这个就可以看出来,到底是什么情况了.按照我们的想法count被执行递减.即4,3,2.
可是问题来了,并没有这样发生--这就是共享资源的多线程导致的问题,叫做脏读,同一时间中多个线程的操作出现了问题.
解决办法
要想解决这个问题,有两种主要的办法.
Synchronized
和Reentrant
类两种方式.
Synchronized方法
这种方法实际上就是将加上它的方法,变成一个临界区,类似于打印机,只能有一个人同时使用打印机,而其他人在这个时候只能等待.
也就说如果一个方法加上了Synchronized方法,那么其他线程就只能排队运行性这个方法.一个线程遇见这个关键字的时候就会试图去获取这个锁,可是如果获取失败,他会等待,并且不断地尝试直到成功.如果有多个线程,那么就会发生争抢.
将上面代码home类更改一下:
class home extends Thread {
private int count=5;
synchronized public void run() {//加入了同步关键字
count--;
System.out.println(Thread.currentThread()+" 的值为"+count);
}
}
我们发现这回输出的内容就正确了
Thread[一号线程,5,main] 的值为4
Thread[三号线程,5,main] 的值为3
Thread[二号线程,5,main] 的值为2
这里有个要注意的事情就是synchronized方法必须用在有共享的对象上.他必须被很多个线程一同使用,才有加入同步的意义,要不然加入同步就是行为艺术,没什么大用.
而且synchronized对象并不会影响对于非synchronized方法的调用.
对于这个例子:
例子 1-1
class method{
private int count=5;
public void str(){
System.out.println(Thread.currentThread()+"执行");
}
synchronized public void gong(){
//死循环
while(count==5){
// System.out.println("chenggong");
}
}
}
当一个线程调用gong()
方法的时候,他永远也无法退出.但是另一个线程调用str()方法却依然可以执行.
package TEST;
public class test2 {
public static void main(String[] args) throws InterruptedException {
method method = new method();
thread thread = new thread("一号线程",method);
thread.start();
Thread.sleep(1000);
thread2 thread2 = new thread2("二号线程",method);
thread2.start();
}
}
class thread extends Thread{
private method method;
public thread(String name, method method){
super(name);
this.method=method;
}
@Override
public void run() {
super.run();
method.str();
method.gong();
}
}
class thread2 extends Thread{
private method method;
public thread2(String name, method method){
super(name);
this.method=method;
}
@Override
public void run() {
super.run();
method.str();
method.gong();
// method.str();
}
}
输出结果如下:
Thread[一号线程,5,main]执行5
Thread[二号线程,5,main]执行5
实际上加上synchronized关键字的方法并不是单纯的锁上了方法,而是对对象的锁.
像一个场景:
共享的类中有两个同步方法,一个写方法一个读方法.
线程1调用了写方法.而同时线程2调用了读方法,那么这最终的结果肯定不是我们想要的.
所以我们要调用
怎么解释这个问题呢
那就要更改一下方法了
class method{
private int count=5;
public void str(){
count--;
System.out.println(Thread.currentThread()+"执行"+count);//如果另一个线程调用count--会让死循环停止,可是这一切并没有发生.死循环依然存在.这可能就是证明了什么叫给对象加锁了吧.
}
public void gong(){
while(count==5){
}
}
}
还有一点要注意就是如果出现异常,自动放弃锁.即一个线程调用方法运行导致抛出了错误,那这个锁会自动被释放.
为了证明这一点,我还是换一下方法
class method{
private int count=5;
public void str(){
// count--;
System.out.println(Thread.currentThread()+"执行"+count);
}
public void gong() throws Exception {
int i=0;
while(count==5){
i++;
if(i==10){//如果i==10的时候会抛出一个异常
throw new Exception("infiniteLoop StopsExecution!");
}
}
}
}
返回的结果是
java.lang.Exception: infiniteLoop StopsExecution!
at TEST.method.gong(test2.java:59)
at TEST.thread.run(test2.java:24)
Thread[二号线程,5,main]执行5
java.lang.Exception: infiniteLoop StopsExecution!
at TEST.method.gong(test2.java:59)
at TEST.thread2.run(test2.java:41)
正常情况下(例子1-1的情况)第二个线程是无法执行的,但是现在抛出两个错误.说明第二个线程执行了.那么这个问题也很好的说明了,出现异常,自动放弃锁..
有一下性质要记住:
- 同步不具有继承性
- 出现异常锁自动释放
- 在日常操作中单纯给写方法加入同步并不会让最终的值没有问题,实际上如果读方法不加锁,最重的结果还是一样的
synchronized同步语句块
如果一个方法中操作过于复杂,但是用到同步的部分又少之又少,那么我们就可以采用同步语句块技术.
以前是将整个方法都给封锁住,现在可以只封锁一个方法中的一部分,可以说很好的节省了时间.
做一个小栗子:
class method{
private int count=0;
public void method(){
for (int i = 0; i <3 ; i++) {//每个线程的第一段0-2数字不一定是连续的
System.out.println(Thread.currentThread().getName()+" "+i);
}
synchronized (this){//每个线程的第二段0-2数字一定是连续的
for (int i = 0; i <3; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
}
输出结果
线程A 0 <--线程A 第一段0-2开始
线程B 0 <--线程B 第一段0-2开始
线程B 1
线程B 2
线程B 0 <---线程B 第二段0-2开始
线程B 1
线程B 2 <---线程B 第二段0-2结束
线程A 1 <--线程A 第一段0-2结束
线程A 2
线程A 0 <--线程A 第二段0-2开始
线程A 1
线程A 2 <--线程A 第二段0-2结束
我们发现第二段永远时连在一起的,但是第一段却是交叉打印的.
这也就是说:当一个线程访问object对象的synchronized代码块的时候,另一个线程依然可以访问非synchronized代码块.
这也意味着synchronized代码块有了更小的封锁位置,也意味着更高的效率.
当然我们举得例子使用的this其他的对象其实也可以但是千万要记得,要使用一个对象才好使,要不然就没有同步效果了.
例子:
class method{
private int count=0;
public void method(){
String string=new String();
for (int i = 0; i <3 ; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
synchronized (string){//修改的地方.每个调用的线程实际上都是用一个全新的对象,因为每当调用的时候才会产生一个全新的string对象
for (int i = 0; i <3; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
}
结果如下
线程A 0
线程A 1
线程A 2
线程A 0
线程B 0
线程B 1
线程A 1
线程B 2
线程B 0
线程B 1
线程B 2
线程A 2
我们可以发现,这里完全是混乱的,意味着并没有实现同步.
同步的特例
静态同步synchronized方法与synchronized(class)
这两种方式分别是个static方法加入一个同步关键词以及让同步代码块中放入一个.class
这些方式实际上是对类加锁,所有的对象都将排队执行.
例子如下:
class method{
private int count=0;
public void method(){
String string=new String();
for (int i = 0; i <3 ; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
synchronized (method.class){//给整个对象加锁
for (int i = 0; i <3; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
}
//测试类也有了变化
public class test3 {
public static void main(String[] args) {
method method = new method();
method method1=new method();
thread1 thread1 = new thread1("线程A",method);//传入了不同的method对象
thread3 thread3 = new thread3("线程B",method1);//传入了不同的method对象
thread1.start();
thread3.start();
}
}
记不记得我们刚开始讲的,如果传入不同的对象,结果会是两个线程交替执行.
结果如下.你会发现他们是同步的,同步的令人惊压.这其实就是锁住了整个类的结果.所有的对象都会被视作同一个对象(在同步代码块眼中),他们会同步进行.
线程A 0
线程A 1
线程A 2
线程B 0
线程B 1
线程B 2
ReentrantLock类
这个类是新增的一个类,其本质来讲似乎和synchronized方法是一样的.调用lock的线程自动持有对象监视器,其他线程只有等待被锁释放时再次争抢.使用和效果synchronized关键字一样,几乎没有什么区别.
这个方法也是锁住整个对象中所有使用了锁的部分.
小栗子:
package Thread.ConditionTestMoreMethod;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyService {
private Lock lock = new ReentrantLock();//声明锁对象
public void methodA() throws InterruptedException {
lock.lock();//所住
System.out.println("MethodA :" + Thread.currentThread().getName() + "time=" + System.currentTimeMillis() + "start");
Thread.sleep(5000);//停止一段时间看看会不会被劫走
System.out.println("MethodA :" + Thread.currentThread().getName() + "time" + System.currentTimeMillis() + "end");
lock.unlock();//解除锁定
}
public void methodB() throws InterruptedException {
lock.lock();
System.out.println("MethodA :" + Thread.currentThread().getName() + "time" + System.currentTimeMillis() + "start");
Thread.sleep(5000);
System.out.println("MethodB :" + Thread.currentThread().getName() + "time" + System.currentTimeMillis() + "end");
lock.unlock();
}
}
class ThreadA extends Thread {
private MyService service;
public ThreadA(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
try {
service.methodA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadAA extends Thread {
private MyService service;
public ThreadAA(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
try {
service.methodA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadB extends Thread {
private MyService service;
public ThreadB(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
try {
service.methodB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadBB extends Thread {
private MyService service;
public ThreadBB(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
try {
service.methodB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Test {
public static void main(String[] args) {
MyService myService = new MyService();
ThreadA threadA = new ThreadA(myService);
threadA.setName("A");//这是Thread自带的方法
threadA.start();
ThreadAA threadAA = new ThreadAA(myService);
threadAA.setName("AA");
threadAA.start();
ThreadB threadB = new ThreadB(myService);
threadB.setName("B");
threadB.start();
ThreadBB threadBB = new ThreadBB(myService);
threadBB.setName("BB");
threadBB.start();
}
}
结果
MethodA :A time=1589378808963start
MethodA :A time1589378808963end
MethodA :AA time=1589378808964start
MethodA :AA time1589378808964end
MethodA :B time1589378808965start
MethodB :B time1589378808965end
MethodA :BB time1589378808966start
MethodB :BB time1589378808966end
这是一个对象锁,就是将整个对象全部锁住.就如同上面的结果一样.在访问一个线程MethodA的时候其他线程是连B都碰不了的.即使MethodA和MethodB毫无关联.
在lock对象中也存在notify()
和wait()
这种方法,只不过使用Condition对象来调用的.Condition提供的方法最主要的好处在于他提供了一种叫做选择性通知
的方式.这个功能是很重要的.像是notify()
和wait()
这种方式是由Object提供的.而对他们的操作也是由JVM随机操作的.要想唤醒其他线程就必须将所有的剩余线程都唤醒.无疑这将导致严重的效率问题.
你可能对此有些疑惑,但是不必担心,请在下面的例子中仔细注意Condition对象的数量.
为此我们准备了一个小例子:
package Thread.MustUseConditon;
import Thread.ConditionTestMoreMethod.MyService;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MustUseCondition {
Lock lock = new ReentrantLock();//创建锁
public Condition a = lock.newCondition();//每个条件对象发出的指令只跟自己这个对象的有关系.
public Condition b = lock.newCondition();//而wait( 这种方法则是对所有都要上锁.所以也正是因此
public void awaitA() throws InterruptedException {
lock.lock();//不加锁,condtion对象中的await这些方法是无法使用的.很糟糕
System.out.println("begin await时间为" + System.currentTimeMillis() + " " + Thread.currentThread().getName());
a.await();//等待,看看什么时候回复.
System.out.println("end await时间为" + System.currentTimeMillis() + " " + Thread.currentThread().getName());
lock.unlock();//千万记得解锁,要不然就死锁了
}
public void awaitb() throws InterruptedException {
lock.lock();//不加锁,condtion对象中的await这些方法是无法使用的.很糟糕
System.out.println("begin awaitB时间为" + System.currentTimeMillis() + " " + Thread.currentThread().getName());
b.await();//等待,看看什么时候回复.
System.out.println("end awaitB时间为" + System.currentTimeMillis() + " " + Thread.currentThread().getName());
lock.unlock();//千万记得解锁,要不然就死锁了
}
public void signallA() throws InterruptedException {
lock.lock();//不加锁,condtion对象中的await这些方法是无法使用的.很糟糕
System.out.println("signall时间为" + System.currentTimeMillis() + " " + Thread.currentThread().getName());
a.signalAll();//释放全部锁
lock.unlock();
}
//下面的方法并不会被调用,实际上一直到结束,这个都不会被调用.
public void signallB() throws InterruptedException {
lock.lock();//不加锁,condtion对象中的await这些方法是无法使用的.很糟糕
System.out.println("signall时间为" + System.currentTimeMillis() + " " + Thread.currentThread().getName());
b.signalAll();//释放全部锁
lock.unlock();
}
}
class ThreadA extends Thread {
private MustUseCondition service;
public ThreadA(MustUseCondition service) {
super();
this.service = service;
}
@Override
public void run() {
try {
service.awaitA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadB extends Thread {
private MustUseCondition service;
public ThreadB(MustUseCondition service) {
super();
this.service = service;
}
@Override
public void run() {
try {
service.awaitb();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class test {
public static void main(String[] args) throws InterruptedException {
MustUseCondition mustUseCondition = new MustUseCondition();
ThreadA threadA = new ThreadA(mustUseCondition);
threadA.setName("A");
threadA.start();
ThreadB threadB = new ThreadB(mustUseCondition);
threadB.setName("B");
threadB.start();
Thread.sleep(3000);//确保A和B都停止
mustUseCondition.signallA();
}
}
可能并不小,但是我们可以用比较简单的语言来解释这个程序所要表达的意思.
Synchronized方法中,如果使用wait()
方法,这个线程会等待.要想再启动,必须等待signal()
方法.跟所有的线程一同启动.
但是实际上很多线程是没有必要启动的.condition对象为我们提供了一个新的方式,我们这回可以声明不同的condition对象,让他们为我们有等待需求的condition对象进行分类,让有关联的使用同一个condition对象(调用await()
方法).
这样就可以大大的增加效率.
生产者消费者模式
生产者消费者模式,实际上就是一个类中的两个不同的方法有这一个公用的信号量.经过设计,这个信号量可以锁住所有使用本方法的线程.只有调用另一个方法(一共两个方法)才能解锁.
package Thread.ConditionTest;
import java.util.concurrent.locks.*;
public class ConditionTest {
private ReentrantLock lock1 = new ReentrantLock();
private Condition condition = lock1.newCondition();
private boolean hasValue = false;
//生产者
public void set() throws InterruptedException {
lock1.lock();//先锁住,这样所有访问这个线程的内容就全都卡住了.
while (hasValue) {
condition.await();
}//用循环才能卡住线程,要不然一会一格Signall又全起来了
hasValue = true;
System.out.println("打印********");
condition.signal();
lock1.unlock();
}
//消费者
public void get() throws InterruptedException {
lock1.lock();//先锁住
while (!hasValue) {
condition.await();
}//用循环才能卡住线程,要不然一会一格Signall又全起来了
hasValue = false;
System.out.println("打印!!!!!!!!!!!");
condition.signal();
lock1.unlock();
}
}
//测试部分
class ThreadA extends Thread {
private ConditionTest service;
public ThreadA(ConditionTest service) {
super();
this.service = service;
}
@Override
public void run() {
try {
for (int i = 0; i < 4; i++) {
service.get();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadB extends Thread {
private ConditionTest service;
public ThreadB(ConditionTest service) {
super();
this.service = service;
}
@Override
public void run() {
try {
for (int i = 0; i < 4; i++) {
service.set();//开4个线程
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class test {
public static void main(String[] args) {
ConditionTest conditionTest = new ConditionTest();
ThreadA threadA = new ThreadA(conditionTest);
threadA.setName("A");
threadA.start();
ThreadB threadB = new ThreadB(conditionTest);
threadB.start();
}
}
打印********
打印!!!!!!!!!!!
打印********
打印!!!!!!!!!!!
打印********
打印!!!!!!!!!!!
打印********
打印!!!!!!!!!!!
结果可以很清楚的看到,内容是交替打印的,但是我们一次开启了8个线程也就是说.有相当一部分线程会发生阻塞(这是用到了线程的随机性).这就是生产者消费者模型的问题.
公平锁非公平锁
公平锁:表示线程获取所得顺序是按照线程加锁的顺序来分配的,即先来先得到的FIFO先进先出顺序.而非公平锁:就是一种获取锁的抢先机制,是随机获得锁的,和非公平锁不一样的是先来不一定先得到.
(这个不要跟线程的随机性搞混.这个是在线程的随机性基础之上的行为.而非导致随机性的原因)
这里就只讲一下使用的方法
lock=new ReentrantLock(isFair);//公平锁,打印出来的内容不是乱序
lock=new ReentrantLock(false);//非公平锁.打印出来的内容会是乱序
一些特殊的方法
int getHoldCount()
的作用是查询当前线程保持此锁定的个数,也就是调用lock()方法的次数.
我们将生产者消费者的代码进行一点小的修改
public class ConditionTest {
private ReentrantLock lock1 = new ReentrantLock();
private Condition condition = lock1.newCondition();
private boolean hasValue = false;
public void set() throws InterruptedException {
lock1.lock();.
while (hasValue) {
condition.await();
}
hasValue = true;
System.out.println("set调用了几次lock"+lock1.getHoldCount());//这里修改了
condition.signal();
get();
lock1.unlock();
}
public void get() throws InterruptedException {
lock1.lock();
while (!hasValue) {
condition.await();
}
hasValue = false;
System.out.println("get锁住了多少个"+lock1.getHoldCount());//这里修改了
condition.signal();
lock1.unlock();
}
}
结果如下
set调用了几次lock1
get锁住了多少个2
set调用了几次lock1
get锁住了多少个2
set调用了几次lock1
get锁住了多少个2
set调用了几次lock1
get锁住了多少个2
这个代码中所有对于调用get的线程都被锁住了.
int getQueueLength()
返回正等待获取此锁定的线程估计数,比如5个线程,1一个线程执行,那么调用方法后返回4.
为了方便,我们更改了一些生产者消费者的代码.并且只运行这个代码.
public void set() throws InterruptedException {
lock1.lock();//先锁住,这样所有访问这个线程的内容就全都卡住了.
// while (hasValue) {
// condition.await();
// }//用循环才能卡住线程,要不然一会一格Signall又全起来了
hasValue = true;
Thread.sleep(1000);
System.out.println("set调用了几次lock"+lock1.getQueueLength());
condition.signal();
get();
lock1.unlock();
}
public static void main(String[] args) {
ConditionTest conditionTest = new ConditionTest();
for (int i = 0; i < 4; i++) {
ThreadA threadA = new ThreadA(conditionTest);
threadA.start();
}
//如下代码将开启4个再run中调用set方法的线程.
set调用了几次lock3
set调用了几次lock3
set调用了几次lock3
set调用了几次lock3
set调用了几次lock2
set调用了几次lock2
这是一部分结果,我们可以看到这个基本陈工.