JAVA JUC

非 Y 不嫁゛ 提交于 2020-03-04 03:55:54

文章目录

JAVA JUC

一、Volatile 关键字-内存可见性

1、java线程的6种状态Thread.state:

NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED

新建、运行、阻塞、等待、超时等待、终止。

2、JAVA多线程:判断 干活 改标志位加唤醒通知(详见Thread.start()方法)
3.一共两个线程

一个thread线程,一个main线程,两个线程同时执行

  • 第一个线程是改值
  • 第二个线程是读值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qm9OTL48-1583231778310)(A01B1760CDC34D1CA290AD4A80485983)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5p0YtecE-1583231778313)(AAE2604868124D699D302FD37AA1FD8A)]

  • 主存中flag的值开始是false,线程1读取数据并进行了改值,但是由于main线程使用while true结构对主存中的值进行读取,速度非常快。因此,main线程中的值是false。由于读取速度的问题造成了内存的可见性问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YdUEBQ66-1583231778337)(9BDF0F58788A49E5B50F93E192BAA840)]

(1)同步锁:synchronized关键字,使两个线程同步

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lDzxVtuR-1583231778339)(E7F3082E440C4247A1C38A0E61423D97)]

但是,使用了同步锁造成效率非常低。一个线程持有锁,另外线程来判断,就会造成阻塞悬挂。因此效率极低。

(2)Volatile关键字

当多个线程进行操作共享数据时,可以保证内存中的数据可见。
相较于 synchronized 是一种较为轻量级的同步策略。

注意:

  • Volatile修饰的变量的操作都是在主存中完成的
  • 由于数据每次都从主存中直接进行读取,因此相较于什么都不加线程效率会变低,但是比synchronized要高。
  • Volatile效率低的原因是因为JVM底层使用重排序进行优化,使用Volatile效率低的原因是,JVM底层不能进行重排序。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DMbN2upV-1583231778343)(B26AADADD5F2452BBCBF53194B7A67D2)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R7UhuIsU-1583231778353)(3EBBF7D6118543A38801AB6E818FEB6E)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q63EJzGg-1583231778355)(5B1A85267D41473EA100092E97061810)]

(3)Volatile与Scnchronized比较
  • 相较于 synchronized关键字Volatile只是一个轻量级的同步策略
  • Volatile不具备互斥性,Synchronized是互斥锁(互斥:一个线程使用另一个线程不能够进行访问)。Volatile所有的访问都在主存中进行,因此一个线程访问时另一个线程依然能够进行访问。
  • Volatile不能保证变量的原子性(原子性:不可分割性)

二、原子变量与CAS算法

1、i ++ 的原子性问题:i++ 的操作实际上分为三个步骤“读-改-写”
int i = 10;
i = i++; //最后答案是i的结果10

原因是在计算机底层进行如下操作:

int temp = i;
i = i+1;
i = temp;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7HCMHfmO-1583231778360)(A8874983D8464C97B333816DDA9C6B51)]

产生原因:

多个线程同时读取并操作主存中的数据,本身需要各个线程的操作并不独立,但是各个线程之间互相割裂(如图所示,都进行了加1的操作)。此时使用Volatile修饰并不能解决原子性问题。(Volatile这是将线程放到主存中进行操作并没有其他的影响,因此并不能解决原子性问题)。

2、原子变量:JDK1.5以后 java.util.concurrent.atomic 包下提供了常用的原子变量
(1)原子变量特性
  • volatile 保证内存可见性(里面封装的变量都用volatile进行修饰,以保证内存可见性)
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-35HepKEE-1583231778362)(8EF6A0346EC6488593C4E8A31A69E259)]
  • CAS(Compare-And-Swap) 算法保证数据变量的原子性
  • CAS 算法是硬件对于并发操作的支持
  • CAS 包含了三个操作数:①内存值 V ②预估值 A ③更新值 B
    (当且仅当 V == A 时, V = B; 否则,不会执行任何操作。)
(2)原子性的解决过程:(先读取内存值,再进行比较,再进行赋值和写入)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6A3Zz0AM-1583231778368)(402ADF828CA34BA6BB78D2A5E41AA56B)]

  • 如图,首先线程1读取主存中的数据,V=0,A=0,经过加1操作B=1,此时V==A,因此,成功写入主存变量值变为1.
  • 线程2进行读取,V=0,A=1,B经过加一操作,因此V!=A,因此线程2不能够写入。然后线程2再讲V读取成1,B再进行加一操作此时进行下一轮的写入。
  • 有且只有一个线程会成功
  • 效率高,原因是:当一个线程没有读取成功是,它不会放弃该线程CPU使用权,而是再尝试继续读入,继续更新数据,因此效率是高的。(高于同步锁效率很多,缺点是自己写的算法多)
(3)原子变量的使用:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LGqScINr-1583231778370)(7A588E1B6CDF465D89AA2F7ABED87D4A)]

三、模拟CAS算法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WcREDpGK-1583231778372)(3F0D018E7FA4493FA29A45492C63A4F5)]

四、ConcurrentHashMap锁分段机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a5jfC941-1583231778374)(B294CCAD23D746F3AAB187199A500E2E)]

1、hashmap和hashtable区别:
  • hashmap是线程不安全的,hashtable是线程安全的(效率低)
  • hashmap和hashtable底层是一样的,只不过hashtable有锁,而且是锁整个表。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3oOmeOD6-1583231778376)(C8BB0FAA077B483A974F8657759677B5)]

  • 当许多线程来访问hashtable时,只有一个线程能够进入。相当于原来的并行改成了串行,因此导致了效率低下。
  • hashtable对复合操作来说也是线程不安全的。如图,contants操作有一个锁,put操作也有一个锁。当进行此线程时,contants完成后很有可能另外一个线程也是添加操作,就将原来不在的元素put进去了,而现有的程序还没有完成put操作,此时造成了线程不安全的问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BBe9CbA6-1583231778379)(11E85F79DE31489E830FA10A9A20F052)]

2、ConCurrentHashMap采用“锁分段”机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rN5m0u7E-1583231778380)(1650BA751A534B349C1F92F1B4B5D5EA)]

不仅线程安全而且保证了并行效率高。并且提供了复合操作的方法,用于提高效率。

  • JDK1.8以后,在底层依然大量的采用了CAS算法解决HashTable的问题。(CAS可以理解为无锁算法)。
3、CopyOnWriteArrayList/CopyOnWriteArraySet : “写入并复制”
  • 线程安全,但是效率低(因为每次写入都会复制,适合进行迭代操作,并发迭代操作适合用这个)
  • 注意:添加操作多时,效率低,因为每次添加时都会进行复制,开销非常的大。并发迭代操作多时可以选择。
4、CountDownLatch(闭锁,在完成某些运算是,只有其他所有线程的运算全部完成,当前运算才继续执行)
  • CountDownLatch是一个同步辅助类,在完成一组正在其他线程中进行的操作之前,他允许一个或多个线程一直等待。
  • 闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:
    • 确保某个计算在其需要的所有资源都被初始化之后才继续执行;
    • 确保某个服务再其依赖的其他服务都已经启动之后才启动;
    • 等待直到某个操作所有的参与者都准备就绪再继续启动。
import java.util.concurrent.CountDownLatch;
/*
 * CountDownLatch :闭锁,在完成某些运算是,只有其他所有线程的运算全部完成,当前运算才继续执行
 */
public class TestCountDownLatch {

	public static void main(String[] args) {
		final CountDownLatch latch = new CountDownLatch(5);
		LatchDemo ld = new LatchDemo(latch);

		long start = System.currentTimeMillis();

		for (int i = 0; i < 5; i++) {
			new Thread(ld).start();
		}

		try {
			latch.await();
		} catch (InterruptedException e) {
		}

		long end = System.currentTimeMillis();

		System.out.println("耗费时间为:" + (end - start));
	}

}

class LatchDemo implements Runnable {

	private CountDownLatch latch;

	public LatchDemo(CountDownLatch latch) {
		this.latch = latch;
	}

	@Override
	public void run() {
		
		synchronized (this) {
			try {
				for (int i = 0; i < 50000; i++) {
					if (i % 2 == 0) {
						System.out.println(i);
					}
				}
			} finally {
				latch.countDown();
			}
		}
	}
}

四、创建执行线程的方式三:实现Callable接口

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/*
 * 一、创建执行线程的方式三:实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
 * 
 * 二、执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。  FutureTask 是  Future 接口的实现类
 */
public class TestCallable {
	
	public static void main(String[] args) {
		ThreadDemo td = new ThreadDemo();
		
		//1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
		FutureTask<Integer> result = new FutureTask<>(td);
		
		new Thread(result).start();
		
		//2.接收线程运算后的结果
		try {
			Integer sum = result.get();  //FutureTask 可用于 闭锁
			System.out.println(sum);
			System.out.println("------------------------------------");
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
	}

}

class ThreadDemo implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		
		for (int i = 0; i <= 100000; i++) {
			sum += i;
		}
		
		return sum;
	}
	
}

/*class ThreadDemo implements Runnable{
er
	@Override
	public void run() {
	}
	
}*/
  • 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
  • 执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。FutureTask 是 Future 接口的实现类
  • 当Thread(task).start()这个线程执行完成后,try catch这个线程才会执行。因此,futuretask相当于闭锁的效果,可用于闭锁,即前面执行完成后再执行当前线程。
  • FutureTask继承了Runnable和Future所以可以用于闭锁
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DRpDd28h-1583231778382)(FC7B2521B322487ABEE5A03D8669B771)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fh9ABJaL-1583231778383)(1050997440664B3BBE9C06DC3A0D370A)]

五、Lock同步锁

1、用于解决多线程安全问题的方式:

Synchronized关键字:隐式锁

  • 同步代码块
  • 同步方法

JDK1.5以后

  • 同步锁:显示锁,需要用lock()的方式上锁,必须通过unlock()的方式释放锁(更灵活的方式)。

注意:

  • 为了保证unlock()方法一定被执行,因此一般把unlock()方法放在finally里面。
  • 由于必须安全释放unlock()才能使用锁,因此同步锁方法同样存在安全问题。不能够完全的替代Sychronized方法,看情况使用。
  • Sychronized关键字是一种基于底层的关键字,JVM会帮助维护。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		new Thread(ticket, "window 1").start();
		new Thread(ticket, "window 2").start();
		new Thread(ticket, "window 3").start();
		
	}

}
class Ticket implements Runnable{
	private int tick = 100;
	private Lock lock = new ReentrantLock();
	
	@Override
	public void run() {
		while (true) {
			lock.lock();//上锁
			try {
				if (tick>0) {
					try {
						Thread.sleep(200);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}					System.out.println(Thread.currentThread().getName()+"完成售票,余票为:"+ --tick);
				}
				if (tick==0) {
					break;
				}//小bug
			} finally {
				lock.unlock();//释放锁
			}
		}
	}
}

六、虚假唤醒问题(生产者消费者案例)

添加和创建数据的线程:生产者线程

删除和销毁数据的线程:消费者线程

生产者线程过快,造成数据丢失

消费者线程过快,造成重复数据错误数据等问题

问题代码如下图:

import javax.xml.stream.events.StartDocument;
/*
 * 生产者和消费者案例
 */
public class TestProductorAndConsumer {
	public static void main(String[] args) {
		clerk clerk = new clerk();
		productor productor = new productor(clerk);
		Consumer consumer = new Consumer(clerk);
		new Thread(productor,"proA").start();
		new Thread(consumer,"conB").start();
	}
}
//店员
class clerk{
	private int product = 0;
	//进货
	public synchronized void get() {
		if (product>=10) {
			System.out.println("产品已满");
		}else {
			System.out.println(Thread.currentThread().getName()+ ":"+ ++product);
		}
	}
	//卖货
	public synchronized void sale() {
		if (product<=0) {
			System.out.println("缺货");
		}else {
			System.out.println(Thread.currentThread().getName()+ ":"+ --product);
		}
	}
}
//继承是因为生产者可能有多个
class productor implements Runnable{
	private clerk clerk;
	

	public productor(testJUC.clerk clerk) {
		this.clerk = clerk;
	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			clerk.get();
		}		
	}
}
//消费者
class Consumer implements Runnable{
	private clerk clerk;
	
	
	public Consumer(testJUC.clerk clerk) {
		this.clerk = clerk;
	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			clerk.sale();
		}
	}
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yqokWbpw-1583231778389)(C795EAAB90ED46ADBEF1CCBEC100BE63)]

其中一种情况,商品已经缺货还依然在出售。

1、解决方法一:使用wait()和notifyAll()方法进行解决
class clerk{
	private int product = 0;
	//进货
	public synchronized void get() {
		if (product>=10) {
			System.out.println("产品已满");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}else {
			System.out.println(Thread.currentThread().getName()+ ":"+ ++product);
			this.notifyAll();
		}
	}
	//卖货
	public synchronized void sale() {
		if (product<=0) {
			System.out.println("缺货");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}else {
			System.out.println(Thread.currentThread().getName()+ ":"+ --product);
			this.notifyAll();
		}
	}
}
2、由于解决方法一带来了虚假唤醒问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TDbFTjw9-1583231778390)(6F3DEE1D3F49413CAA5FB35C908AC035)]

  • 虚假唤醒问题产生原因图示:(问题发生在get和sale的else里面)
    (假设生产者productor容量为一)

消费者线程剩余循环次数:1,生产者剩余循环次数:2.

a、首先线程进入消费者线程,此时product=0,而消费者线程循环剩余次数为0,再进入消费者线程是会在wait()的位置进去并继续执行,消费者线程wait()并释放锁的资源。

b、进入生产者线程后满足else条件,productor线程开始生产,++product并notifyall(),生产者消费者同时抢锁。

c、假设被消费者抢到,进入消费者线程,继续之前的wait的位置,虽然此时product=1.但是由于wait的位置因此不能进入else。程序wait()并释放所有资源。此时消费者线程循环剩余次数为0次。

d、此时生产者线程循环次数还剩1次,因此一定进入生产者线程,此时product==1满足product>=1条件,进入条件并开始wait();

e、问题生产者开始wait()后没有任何线程再去唤醒它,程序开始陷入锁定,不能退出也不能继续执行。

f、假设此时被消费者线程抢到,那么直接进入上一次wait的地方,即此时消费者线程进入等待状态而循环次数为0. 不能够notiALL()其他的线程,也不能够执行自己的循环,整个程序陷入不能被唤醒的锁定状态。

3、 解决方法:去掉else;(保证唤醒一定能够被执行。)
  • 去掉else后,如果多个生产者多个消费者仍出现问题:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vayifgXU-1583231778392)(D2AE933A12934F6A8F7D670AC636F716)]

3、 产生问题原因:(虚假唤醒问题)

product==0,并被一个消费者抢到线程,消费者进入线程wait()。释放后,又被另一个消费者抢到线程,依旧wait()。再被生产者抢到线程执行一次product++,notifyAll(),又被消费者抢到了线程,此时进入–.notifyAll()后又被另一个消费者抢到线程,依旧–。就会出现负数的情况。而且如果被消费者多次抢到线程负数次数也会增加。

4、 虚假唤醒问题解决方式:JDK中特别提醒,在某一种情况中,虚假唤醒会总存在,因此wait()方法必须总是使用在循环中。最终解决方法把if变成while。
5、 本课涉及到的知识点:

1、虚假唤醒问题
2、生产者消费者问题
3、同步锁
4、等待唤醒机制

七、解决线程安全问题之同步锁-condition

1、Condition

Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个Lock 可能与多个Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的Object 版本中的不同。

2、condition中方法与同步锁方法对应:
  • await()->wait();

  • notify->siginal();

  • notifyAll()->signalAll();

3、Condition 实例实质上被绑定到一个锁上。要为特定Lock 实例获得Condition 实例,请使用其newCondition() 方法。
4、通过lock()获得线程通信的方式:lock.newCondition();

示例代码如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
 * 生产者和消费者案例
 */
public class TestProductorAndConsumerFORLock {
	public static void main(String[] args) {
		TTclerk clerk = new TTclerk();
		TTProductor TTproductor = new TTProductor(clerk);
		TTconsumer TTconsumer = new TTconsumer(clerk);
		new Thread(TTproductor,"proA").start();
		new Thread(TTconsumer,"conB").start();
		
		new Thread(TTproductor,"proC").start();
		new Thread(TTconsumer,"conD").start();
	}
}
//店员
class TTclerk{
	private int product = 0;
	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	//进货
	public void get() throws InterruptedException {
		lock.lock();
		//ifs
		try {
			while(product>=1) {
				System.out.println("产品已满");
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}/*
			else {
				System.out.println(Thread.currentThread().getName()+ ":"+ ++product);
				this.notifyAll();
			}*/
			System.out.println(Thread.currentThread().getName()+ ":"+ ++product);
			condition.signalAll();
		} finally {
			lock.unlock();
		}
	}
	//卖货
	public void sale() {
		lock.lock();
		try {
			//if
			while(product<=0) {
				System.out.println("缺货");
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}/*else {
				System.out.println(Thread.currentThread().getName()+ ":"+ --product);
				this.notifyAll();			
			}*/
			System.out.println(Thread.currentThread().getName()+ ":"+ --product);
			condition.signalAll();
		} finally {
			lock.unlock();
		}		
	}
}
//继承是因为生产者可能有多个
class TTProductor implements Runnable{
	private TTclerk clerk;
	

	public TTProductor(TTclerk clerk) {
		this.clerk = clerk;
	}
	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			try {
				clerk.get();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}		
	}
}
//消费者
class TTconsumer implements Runnable{
	private TTclerk clerk;
	
	
	public TTconsumer(TTclerk clerk2) {
		this.clerk = clerk2;
	}
	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			clerk.sale();
		}
	}
}

八、线程按序交替(线程通信condition)

1、编写一个程序,开启三个线程,这三个线程的ID分别为ABC,其中A打印1次,B打印1次,C打印1次,每个线程将自己的ID在屏幕上打印10遍,要求输出的结果必须按照顺序显示。如:ABCABCABC…依次递归
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestABCAlternate {

	public static void main(String[] args) {
		AlternateDemo alternateDemo = new AlternateDemo();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 1; i <= 20; i++) {
					alternateDemo.loopA(i);
				}		
			}
		},"A").start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 1; i <= 20; i++) {
					alternateDemo.loopB(i);
				}		
			}
		},"B").start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 1; i <= 20; i++) {
					alternateDemo.loopC(i);
					System.out.println("-------------------------------");
				}		
			}
		},"C").start();

	}
}
class AlternateDemo{
	private int number = 1;
	
	private Lock lock = new ReentrantLock();
	
	private Condition condition1 = lock.newCondition();
	
	private Condition condition2 = lock.newCondition();
	
	private Condition condition3 = lock.newCondition();
	
	public void loopA(int totalLoop) {
		lock.lock();
		
		try {
			//1、判断是否执行第一个线程
			if (number!=1) {
				condition1.await();
			}
			//2、打印
			for (int i = 1; i <= 5; i++) {
				System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
			}
			//3、唤醒
			number = 2;
			condition2.signal();//唤醒线程2
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	public void loopB(int totalLoop) {
		lock.lock();
		
		try {
			//1、判断是否执行第二个线程
			if (number!=2) {
				condition2.await();
			}
			//2、打印
			for (int i = 1; i <= 15; i++) {
				System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
			}
			//3、唤醒
			number = 3;
			condition3.signal();//唤醒线程3
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	public void loopC(int totalLoop) {
		lock.lock();
		
		try {
			//1、判断是否执行第三个线程
			if (number!=3) {
				condition3.await();
			}
			//2、打印
			for (int i = 1; i <= 20; i++) {
				System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
			}
			//3、唤醒
			number = 1;
			condition1.signal();//唤醒线程1
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jIn0GT0c-1583231778394)(97A131F7960C499183E8EAB30F82350B)]

2、加大难度:编写一个程序,开启三个线程,这三个线程的ID分别为ABC,其中A打印5次,B打印15次,C打印20次每个线程将自己的ID在屏幕上打印10遍,要求输出的结果必须按照顺序显示。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestABCAlternate {

	public static void main(String[] args) {
		AlternateDemo alternateDemo = new AlternateDemo();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 1; i <= 20; i++) {
					alternateDemo.loopA(i);
				}		
			}
		},"A").start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 1; i <= 20; i++) {
					alternateDemo.loopB(i);
				}		
			}
		},"B").start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 1; i <= 20; i++) {
					alternateDemo.loopC(i);
					System.out.println("-------------------------------");
				}		
			}
		},"C").start();

	}
}
class AlternateDemo{
	private int number = 1;
	
	private Lock lock = new ReentrantLock();
	
	private Condition condition1 = lock.newCondition();
	
	private Condition condition2 = lock.newCondition();
	
	private Condition condition3 = lock.newCondition();
	
	public void loopA(int totalLoop) {
		lock.lock();
		
		try {
			//1、判断是否执行第一个线程
			if (number!=1) {
				condition1.await();
			}
			//2、打印
			for (int i = 1; i <= 5; i++) {
				System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
			}
			//3、唤醒
			number = 2;
			condition2.signal();//唤醒线程2
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	public void loopB(int totalLoop) {
		lock.lock();
		
		try {
			//1、判断是否执行第二个线程
			if (number!=2) {
				condition2.await();
			}
			//2、打印
			for (int i = 1; i <= 15; i++) {
				System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
			}
			//3、唤醒
			number = 3;
			condition3.signal();//唤醒线程3
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	public void loopC(int totalLoop) {
		lock.lock();
		
		try {
			//1、判断是否执行第三个线程
			if (number!=3) {
				condition3.await();
			}
			//2、打印
			for (int i = 1; i <= 20; i++) {
				System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
			}
			//3、唤醒
			number = 1;
			condition1.signal();//唤醒线程1
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hkAwegdm-1583231778397)(F42C62CE2E214CA9AB77D1C7B927C8B7)]

九、读写锁ReadWriteLock

1、读写锁是一种乐观锁,之前写一次读一次,现在可以写一次读很多次。
2、什么情况下用读写锁:
  • 两个线程都在进行写入,此时需要保证线程安全(写写)
  • 一个线程读一个线程写(读写)读线程可以多个线程并发的持有(长时间没有写的情况下),写线程锁必须是独占的
  • 读读情况不需要互斥不需要用
  • readLock()
  • writeLock()
    代码示例:
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class TestReadWriteLock {
	public static void main(String[] args) {
		ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
		new Thread(new Runnable() {	
			@Override
			public void run() {
				readWriteLockDemo.set((int) (Math.random()*101));			
			}
		},"Write").start();
		for (int i = 1; i <= 100; i++) {
			new Thread(new Runnable() {	
				@Override
				public void run() {
					readWriteLockDemo.get();
				}
			}).start();
		}
	}
}
class ReadWriteLockDemo{
	private int number = 0;
	private ReadWriteLock lock = new ReentrantReadWriteLock();
	public void get() {
		lock.readLock().lock();
		try {
			System.out.println(Thread.currentThread().getName()+":"+number);
		} finally {
			lock.readLock().unlock();
		}
	}
	public void set(int number) {
		lock.writeLock().lock();
		try {
			System.out.println(Thread.currentThread().getName());
			this.number = number;
		} finally {
			lock.writeLock().unlock();
		}
	}
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FAQiFdGa-1583231778398)(2A04CF80CBC54002AADDE3134D4FE37C)]

  • 注意:
  • 如果读线程比写线程更开始运行则读出来的值是初始值0。后面比写线程慢的读出来的是写入的值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ytGgMF9V-1583231778399)(42AC670392F741B6B65AC88FDBF956A1)]

十、线程八锁

1、判断打印输出的是什么?one还是two
  • (1)两个同步方法,两个线程标准打印(synchronized关键字)
public class TestThread8Monitor {
	public static void main(String[] args) {
		Number number = new Number();
		new Thread(new Runnable() {
			@Override
			public void run() {
				number.getOne();
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				number.getTwo();
			}
		}).start();
	}
}
class Number{
	public synchronized void getOne() {
		System.out.println("one");
	}
	public synchronized void getTwo() {
		System.out.println("two");
	}
}

结果:one 和 two 都打印

  • (2)在getOne方法中加入3秒延迟
package testJUC;

public class TestThread8Monitor {

	public static void main(String[] args) {
		Number number = new Number();
		new Thread(new Runnable() {
			@Override
			public void run() {
				number.getOne();
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				number.getTwo();
			}
		}).start();
	}
}
class Number{
	public synchronized void getOne() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("one");
	}
	public synchronized void getTwo() {
		System.out.println("two");
	}
}

结果:程序运行三秒后依此打印出one和two

  • (3)新增一个普通方法getThree
public class TestThread8Monitor {
	public static void main(String[] args) {
		Number number = new Number();
		new Thread(new Runnable() {
			@Override
			public void run() {
				number.getOne();
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				number.getTwo();
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				number.getThree();
			}
		}).start();
	}
}
class Number{
	public synchronized void getOne() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("one");
	}
	public synchronized void getTwo() {
		System.out.println("two");
	}
	public void getThree() {
		System.out.println("Three");
	}
}

结果:
先打出three过三秒再打出one和two

  • (4)创建两个Number对象,number和number2,number调用getOne方法,number2调用getTwo方法
public class TestThread8Monitor {
	public static void main(String[] args) {
		Number number = new Number();
		Number number2 = new Number();
		new Thread(new Runnable() {
			@Override
			public void run() {
				number.getOne();
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				number2.getTwo();
			}
		}).start();
//		new Thread(new Runnable() {
//			
//			@Override
//			public void run() {
//				number.getThree();
//			}
//		}).start();
	}
}
class Number{
	public synchronized void getOne() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("one");
	}
	public synchronized void getTwo() {
		System.out.println("two");
	}
	public void getThree() {
		System.out.println("Three");
	}
}

结果:打印two三秒后打印出来one

  • (5)修改getOne为静态同步方法(static synchronized),使用一个number对象调用getOne()和getTwo()方法
public class TestThread8Monitor {
	public static void main(String[] args) {
		Number number = new Number();
		Number number2 = new Number();
		new Thread(new Runnable() {
			@Override
			public void run() {
				number.getOne();
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				number.getTwo();
			}
		}).start();
//		new Thread(new Runnable() {
//			
//			@Override
//			public void run() {
//				number.getThree();
//			}
//		}).start();
	}
}
class Number{
	public static synchronized void getOne() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("one");
	}
	public synchronized void getTwo() {
		System.out.println("two");
	}
	public void getThree() {
		System.out.println("Three");
	}
}

结果:打印two三秒后打印出来one

  • (6)两个静态方法
public class TestThread8Monitor {
	public static void main(String[] args) {
		Number number = new Number();
		Number number2 = new Number();
		new Thread(new Runnable() {
			@Override
			public void run() {
				number.getOne();
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				number.getTwo();
			}
		}).start();
//		new Thread(new Runnable() {
//			
//			@Override
//			public void run() {
//				number.getThree();
//			}
//		}).start();
	}
}
class Number{
	public static synchronized void getOne() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("one");
	}
	public static synchronized void getTwo() {
		System.out.println("two");
	}
	public void getThree() {
		System.out.println("Three");
	}
}

结果:三秒后打印one和two

  • (7)两个对象,number调用静态同步方法getOne(),number2调用非静态同步方法getTwo()
public class TestThread8Monitor {
	public static void main(String[] args) {
		Number number = new Number();
		Number number2 = new Number();
		new Thread(new Runnable() {
			@Override
			public void run() {
				number.getOne();
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				number2.getTwo();
			}
		}).start();
//		new Thread(new Runnable() {
//			
//			@Override
//			public void run() {
//				number.getThree();
//			}
//		}).start();
	}
}
class Number{
	public static synchronized void getOne() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("one");
	}
	public synchronized void getTwo() {
		System.out.println("two");
	}
	public void getThree() {
		System.out.println("Three");
	}
}

结果:打印two三秒后打印出来one

  • (8)两个都是静态同步方法,分别用两个对象调用
public class TestThread8Monitor {
	public static void main(String[] args) {
		Number number = new Number();
		Number number2 = new Number();
		new Thread(new Runnable() {
			@Override
			public void run() {
				number.getOne();
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				number2.getTwo();
			}
		}).start();
//		new Thread(new Runnable() {
//			
//			@Override
//			public void run() {
//				number.getThree();
//			}
//		}).start();
	}
}
class Number{
	public static synchronized void getOne() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("one");
	}
	public static synchronized void getTwo() {
		System.out.println("two");
	}
	public void getThree() {
		System.out.println("Three");
	}
}

结果:三秒后打印one和two

规律

  • 在某一时刻,只有一个对象持有锁;另外一个线程不能抢到。

线程八锁的关键:

  • 非静态方法的锁默认为this,静态方法的锁为对应的class(number.class)实例;
  • 某一时刻内,只能有一个线程持有锁,无论几个方法

十一、ThreadPool线程池

问题:每次new Thread().start()启动线程用完后销毁线程,当任务很多的话频繁的创建线程销毁线程很浪费资源。(数据库连接池)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lbPIOpRV-1583231778401)(782DA82B3DA44AC49148CBDDCFBE996F)]

数据库连接池

每次创建与数据库的连接再取消很麻烦,于是建立一个中间连接池。连接池中的连接与数据库一直保持联系,当需要用到数据传输时直接连接连接池中的连接就可以,不用的话放在连接池中。这样做能够减少资源的浪费并保持高效连接。(谁用谁拿)(线程池道理一样)

1、线程池:

提供了一个个线程队列,队列中保存着所有等待状态的线程,避免了创建与销毁额外开销,提高了响应速度。

2、线程池的体系结构
  • java.util.concurrent.Excetor(Interface):线程池核心接口,负责维护线程的使用和调度的根接口
    • ExcetorService(子接口):线程池的主要接口
      • ThreadPoolExcutor:线程池的实现类
      • ScheduleExcutorService(子接口):负责线程调度
        • ScheduledThreadPoolExcutor:实现类,继承了ThreadPoolExcutor(线程池的实现类),实现了ScheduleExcutorService(负责线程调度的接口),因此兼具线程池的功能和线程调度功能
3、工具类
  • (ExcetorService) newFixedThreadPool():创建固定大小的线程池
    • 返回值:(ExcetorService)类型的根接口
  • (ExcetorService)newCachedThreadPool() :缓存线程池,线程池的大小不固定,根据需求自动的更改数量
  • (ExcetorService)newSingleThreadExcutor():创建单个的线程池。线程池中只有一个线程。
  • (ScheduledThreadPoolExcutor) newScheduledThreadPool():创建固定大小的线程,可以延迟或定时的执行任务。
  • java工厂模式自动配备好了线程的内部配置,如有其他需要可以根据API文档再进行配置。
4、线程池实现Runnable接口
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool {
	public static void main(String[] args) {
		//1、创建线程池
		ExecutorService pool = Executors.newFixedThreadPool(5);
		TTTTTTThreadDemo tDemo = new TTTTTTThreadDemo();
		//2、为线程池中的线程分配任务
//		pool.submit(tDemo);
		for (int i = 0; i < 3; i++) {
			pool.submit(tDemo);
		}
		//3、关闭线程池
		pool.shutdown();//等待现有任务执行完成之后才会关闭,关闭之后不会再接受新的任务
		//pool.shutdownNow();//不管现有线程是否执行完都会立即关闭		
	}
}
class TTTTTTThreadDemo implements Runnable{
	private int i = 0;
	@Override
	public void run() {
		while (i<=100) {
			System.out.println(Thread.currentThread().getName()+":"+i++);
		}
	}
}
5、线程池实现Callable接口
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestThreadPool {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ExecutorService pool = Executors.newFixedThreadPool(5);
		List<Future<Integer>> list = new ArrayList<Future<Integer>>();
		for (int i = 0; i < 10; i++) {
			Future<Integer> future = pool.submit(new Callable<Integer>() {
				@Override
				public Integer call() throws Exception {
					int sum = 0;
					for (int i = 0; i <= 100; i++) {
						sum+=i; 
					}
					return sum;
				}
			});
			list.add(future);
		}
		for (Future<Integer> future : list) {
			System.out.println(future.get());
		}
		pool.shutdown();
	}
}

十二、线程池的调度

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class TestScheduledThreadPool {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);	
		for (int i = 0; i < 10; i++) {
			Future<Integer> future = pool.schedule(new Callable<Integer>() {
				@Override
				public Integer call() throws Exception {
					int number = new Random().nextInt(100);		
					System.out.println(Thread.currentThread().getName()+":"+number);
					return number;
				}
			}, 1, TimeUnit.SECONDS);
			System.out.println(future.get());
		}
		pool.shutdown();
	}
}

十三、FrokJoinPool分支合并框架-工作窃取

1、

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oq747ur5-1583231778402)(3A6081AF3FE8456B9378D1AA911936A6)]

并行求值:多个线程同时对一个一个小任务进行求值。

2、

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-foalJDzZ-1583231778403)(671F5FF40E584D6DBBF2E3A64F124502)]

工作窃取模式:

  • 把不同的的任务分到不同的线程中执行,线程池设计阻塞问题。不同线程的任务分配在不同的内核上进行完成。一旦某内核上的某一个线程阻塞,这个内核上的后续线程尽行不下去。未阻塞的内核处于忙碌状态,阻塞的线程处于不能继续进行工作的状态,造成CPU内核空闲,并没有更加合理利用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vGxUJHhV-1583231778404)(873BB497EA5B490792E97662DD6FE20D)]

  • 因此在jdk1.7以后出现了Frok/Join框架采用了工作窃取模式,把大任务拆成小任务,并把小任务放到对应的线程中,形成一个双端的队列。一旦某一个线程在获取线程任务时获取不到,此时,会随机从其他的内核的队伍的末尾偷一个线程继续执行任务。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YZqEfSHR-1583231778406)(63FF349868304D859E843032607622D8)]

3、手写frokjoin框架:
  • 建立类FrokJoinSumCaculate模拟FrokJoin工作模式计算,该类继承了RecursiveTask类。
    • RecursiveTask和RecursiveAction类功能相近,区别在于RecursiveTask有返回值,而RecursiveAction没有返回值。
  • RecursiveTask类继承了ForkJoinTask类(JDK1.7),ForkJoinTask类继承了Future类(JDK1.5)。
  • test2方法使用了java8新特性,并行流
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
import org.junit.Test;

public class TestForkJoinPool {
	
	public static void main(String[] args) {
		Instant start = Instant.now();
		
		ForkJoinPool pool = new ForkJoinPool();
		ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L, 100000000L);
		Long sum = pool.invoke(task);
		System.out.println(sum);
		
		Instant end = Instant.now();
		
		System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//166-1996-10590
	}
	
	@Test
	public void test1(){
		Instant start = Instant.now();
		
		long sum = 0L;
		
		for (long i = 0L; i <= 50000000000L; i++) {
			sum += i;
		}
		System.out.println(sum);
		
		Instant end = Instant.now();
		
		System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//35-3142-15704
	}
	//java8 新特性,并行流
	@Test
	public void test2(){
		Instant start = Instant.now();
		
		Long sum = LongStream.rangeClosed(0L, 50000000000L)
							 .parallel()//并行流
							 .reduce(0L, Long::sum);
		System.out.println(sum);
		Instant end = Instant.now();
		System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//1536-8118
	}
}

class ForkJoinSumCalculate extends RecursiveTask<Long>{

	private static final long serialVersionUID = -259195479995561737L;
	private long start;
	private long end;
	private static final long THURSHOLD = 10000L;  //临界值
	public ForkJoinSumCalculate(long start, long end) {
		this.start = start;
		this.end = end;
	}

	@Override
	protected Long compute() {
		long length = end - start;
		
		if(length <= THURSHOLD){
			long sum = 0L;
			
			for (long i = start; i <= end; i++) {
				sum += i;
			}
			return sum;
		}else{
			long middle = (start + end) / 2;
			
			ForkJoinSumCalculate left = new ForkJoinSumCalculate(start, middle); 
			left.fork(); //进行拆分,同时压入线程队列
			
			ForkJoinSumCalculate right = new ForkJoinSumCalculate(middle+1, end);
			right.fork(); //
			return left.join() + right.join();
		}
	}
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!