JUC(java.util.concurrent)学习笔记
- 1.Java JUC简介
- 多线程的目的
- (1).提高效率。尽可能去利用cpu和系统资源。
- (2).注意:如果多线程使用不当的话,不仅不能提高效率,反而性能会更低,因为多线程的开销实际上比线程要大,因为多线程涉及线程之间的调度,以及cpu上下文切换,以及线程的创建,销毁,线程同步等问题。而单线程不涉及这些问题
- JUC包
- JDK1.5以后,Java为我们提供了java.util.concurrent包,这个包里面提供了大量应用于线程的一些工具类,供我们在不同需求上进行应用。
- 2.volatile关键字-内存可见性
- 3.原子变量-CAS算法(无锁算法)
- (1)引入问题:i++操作的原子性问题
public class TestAtomic {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
//创建10个线程调用i++方法
for(int i = 0; i < 10; i++){
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable{
private volatile int seriableNumber = 0;
@Override
public void run(){
try {
Thread.sleep(10000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + getSeriableNumber());
}
public int getSeriableNumber(){
return seriableNumber++;
}
}
- 运行结果:

- i= i++操作在计算机底层的实现操作: "读-改-写"
- 当有两个线程同时进行i++操作的时候,就容易出现进行了读、改两步操作而还没来得及写的时候,就会出现如上所示的原子性问题。
- (2).问题解决方案:原子变量
- (3)模拟CAS算法的实现
public class TestCompareAndSwap {
public static void main(String[] args) {
final CompareAndSwap cas = new CompareAndSwap();
//10个线程分别修改value变量的值,若修改成功则打印true,修改失败则打印false
for(int i = 0; i < 10; i++){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int expectedValue = cas.get();
boolean b = cas.compareAndSet(expectedValue, (int)(Math.random() * 101));
System.out.println(Thread.currentThread().getName() + "修改value的值:" + b);
}
}).start();
}
}
}
class CompareAndSwap{
private int value;
//获取内存值
public synchronized int get(){
return value;
}
//比较
//expectedValue预估值,newValue新值
public synchronized int compareAndSwap(int expectedValue, int newValue){
int oldValue = value;
//compare
if(oldValue == expectedValue){
this.value = newValue;
}
return oldValue;
}
//设置
public synchronized boolean compareAndSet(int expectedValue, int newValue){
return expectedValue == compareAndSwap(expectedValue, newValue);
}
}
运行结果: 
- 4.ConcurrentHashMap锁分段机制
- 5.CountDownLatch闭锁
- 6.实现Callable接口的方式来实现多线程
- 7.Lock同步锁
- (1)概述:用于解决多线程安全问题的3种解决方案
- ①同步代码块(synchronized隐式锁)
- ②同步方法(synchronized隐式锁)
- ③同步锁(jdk1.5后)(显示锁)(通过lock()方法上锁)(通过unlock()方法进行释放锁)
- (2)Lock同步锁解决多线程安全问题(多窗口卖票案例)
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,"1号窗口").start();
new Thread(ticket,"2号窗口").start();
new Thread(ticket,"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) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "完成售票,余票为" + --tick);
}
}finally{
lock.unlock();//释放锁
}
}
}
}
运行结果: 
- 没有出现负数票
-
- (3)生产者消费者案例
- 使用前言: 使用synchronized锁机制实现等待-唤醒机制(生产者消费者案例)
- ①不使用等待唤醒机制的问题 : 产品满时生产者仍不断生产产品。反过来,消费者发现没有产品了,还不断去消费。
public class TestProductorAndConsumer {
public static void main(String[] args) {
Cleck cleck = new Cleck();
Productor pro = new Productor(cleck);
Consumer cus = new Consumer(cleck);
new Thread(pro, "生产者A").start();
new Thread(cus, "消费者B").start();
}
}
//店员
class Cleck{
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 Cleck cleck;
public Productor(Cleck cleak){
this.cleck = cleak;
}
@Override
public void run(){
for(int i = 0; i < 20; i++){
cleck.get();
}
}
}
//消费者
class Consumer implements Runnable{
private Cleck cleck;
public Consumer(Cleck cleak){
this.cleck = cleak;
}
@Override
public void run(){
for(int i = 0; i < 20; i++){
cleck.sale();
}
}
}
运行结果: 
- 产品满时生产者仍不断生产产品
-

- 消费者发现没有产品了,还不断去消费
- ②解决方式:使用等待唤醒机制解决多消费或多生产的问题。(此时仍存在问题,因为我们以后都是在网络中进行,所以我们在下一步加上睡眠来模拟网络延迟)
public class TestProductorAndConsumer {
public static void main(String[] args) {
Cleck cleck = new Cleck();
Productor pro = new Productor(cleck);
Consumer cus = new Consumer(cleck);
new Thread(pro, "生产者A").start();
new Thread(cus, "消费者B").start();
}
}
//店员
class Cleck{
private int product = 0;
//加锁前,卖货和进货两个方法访问的是共享数据,两个方法都存在多线程安全问题
//进货
public synchronized void get(){
if(product >= 10){
System.out.println("产品已满!");
try {
this.wait();
} catch (InterruptedException e) {
}
}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();
}
}
}
//生产者
class Productor implements Runnable{
private Cleck cleck;
public Productor(Cleck cleak){
this.cleck = cleak;
}
@Override
public void run(){
for(int i = 0; i < 20; i++){
cleck.get();
}
}
}
//消费者
class Consumer implements Runnable{
private Cleck cleck;
public Consumer(Cleck cleak){
this.cleck = cleak;
}
@Override
public void run(){
for(int i = 0; i < 20; i++){
cleck.sale();
}
}
}
运行结果: 
- 此时不再出现产品满时生产者仍不断生产产品,或者消费者发现没货时还不断去消费的问题。但此程序是理想状态下的程序,而我们平时开发应用的时候,往往会有网络延迟,让我们接下来来使用sleep函数来模拟网络延迟,看看程序的执行结果如何。
-
- ③加上睡眠模拟网络延迟,并减小产品空位。此时的问题:程序不停止。(等待了但被唤醒不了),此时应该去掉else
public class TestProductorAndConsumer {
public static void main(String[] args) {
Cleck cleck = new Cleck();
Productor pro = new Productor(cleck);
Consumer cus = new Consumer(cleck);
new Thread(pro, "生产者A").start();
new Thread(cus, "消费者B").start();
}
}
//店员
class Cleck{
private int product = 0;
//加锁前,卖货和进货两个方法访问的是共享数据,两个方法都存在多线程安全问题
//进货
public synchronized void get(){
if(product >= 1){
System.out.println("产品已满!");
try {
this.wait();
} catch (InterruptedException e) {
}
}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();
}
}
}
//生产者
class Productor implements Runnable{
private Cleck cleck;
public Productor(Cleck cleak){
this.cleck = cleak;
}
@Override
public void run(){
for(int i = 0; i < 20; i++){
cleck.get();
}
}
}
//消费者
class Consumer implements Runnable{
private Cleck cleck;
public Consumer(Cleck cleak){
this.cleck = cleak;
}
@Override
public void run(){
for(int i = 0; i < 20; i++){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
cleck.sale();
}
}
}
运行结果: 
此时不再出现产品满时生产者仍不断生产产品,或者消费者发现没货时还不断去消费的问题。但是程序不停止,这是由else与多个线程执行造成了某个线程等待了但最后已经没有线程来发出通知唤醒它导致的。 -
- ④去掉else后的版本,程序可以停止了,其余的与上面③的运行结果一致,此处不再演示。
-
- 上述①-④是1生产者线程和1消费者线程,当我们换成2个消费者线程时,若两个消费者线程同时等待并同时被唤醒那么就会出现负数情况(虚假唤醒)。
public class TestProductorAndConsumer {
public static void main(String[] args) {
Cleck cleck = new Cleck();
Productor pro = new Productor(cleck);
Consumer cus = new Consumer(cleck);
new Thread(pro, "生产者A").start();
new Thread(pro, "生产者B").start();
new Thread(cus, "消费者C").start();
new Thread(cus, "消费者D").start();
}
}
//店员
class Cleck{
private int product = 0;
//加锁前,卖货和进货两个方法访问的是共享数据,两个方法都存在多线程安全问题
//进货
public synchronized void get(){
if(product >= 1){//为了避免虚假唤醒问题,应该总是使用在循环中
System.out.println("产品已满!");
try {
this.wait();
} catch (InterruptedException e) {
}
}
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();
}
}
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
}
//生产者
class Productor implements Runnable{
private Cleck cleck;
public Productor(Cleck cleak){
this.cleck = cleak;
}
@Override
public void run(){
for(int i = 0; i < 20; i++){
cleck.get();
}
}
}
//消费者
class Consumer implements Runnable{
private Cleck cleck;
public Consumer(Cleck cleak){
this.cleck = cleak;
}
@Override
public void run(){
for(int i = 0; i < 20; i++){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
cleck.sale();
}
}
}
- 解决方式,通过查阅api可知,解决虚假唤醒的方法就是把if改为while。
- ⑤改if为while之后的最终版本
public class TestProductorAndConsumer {
public static void main(String[] args) {
Cleck cleck = new Cleck();
Productor pro = new Productor(cleck);
Consumer cus = new Consumer(cleck);
new Thread(pro, "生产者A").start();
new Thread(pro, "生产者B").start();
new Thread(cus, "消费者C").start();
new Thread(cus, "消费者D").start();
}
}
//店员
class Cleck{
private int product = 0;
//加锁前,卖货和进货两个方法访问的是共享数据,两个方法都存在多线程安全问题
//进货
public synchronized void get(){
while(product >= 1){//为了避免虚假唤醒问题,应该总是使用在循环中
System.out.println("产品已满!");
try {
this.wait();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + ":" + ++product);
this.notifyAll();
}
//卖货
public synchronized void sale(){
while(product <= 0){
System.out.println("缺货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
}
//生产者
class Productor implements Runnable{
private Cleck cleck;
public Productor(Cleck cleak){
this.cleck = cleak;
}
@Override
public void run(){
for(int i = 0; i < 20; i++){
cleck.get();
}
}
}
//消费者
class Consumer implements Runnable{
private Cleck cleck;
public Consumer(Cleck cleak){
this.cleck = cleak;
}
@Override
public void run(){
for(int i = 0; i < 20; i++){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
cleck.sale();
}
}
}
运行结果: 
- 没有出现虚假唤醒现象。
- 使用Lock同步锁实现等待唤醒机制
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) {
Cleck1 cleck = new Cleck1();
Productor1 pro = new Productor1(cleck);
Consumer1 cus = new Consumer1(cleck);
new Thread(pro, "生产者A").start();
new Thread(pro, "生产者B").start();
new Thread(cus, "消费者C").start();
new Thread(cus, "消费者D").start();
}
}
//店员
class Cleck1{
private int product = 0;
//加锁前,卖货和进货两个方法访问的是共享数据,两个方法都存在多线程安全问题
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//进货
public void get(){
lock.lock();
try{
while(product >= 1){//为了避免虚假唤醒问题,应该总是使用在循环中
System.out.println("产品已满!");
try {
condition.await();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + ":" + ++product);
condition.signalAll();
}finally{
lock.unlock();
}
}
//卖货
public void sale(){
lock.lock();
try{
while(product <= 0){
System.out.println("缺货");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + --product);
// this.notifyAll();
condition.signalAll();
}finally{
lock.unlock();
}
}
}
//生产者
class Productor1 implements Runnable{
private Cleck1 cleck;
public Productor1(Cleck1 cleak){
this.cleck = cleak;
}
@Override
public void run(){
for(int i = 0; i < 20; i++){
cleck.get();
}
}
}
//消费者
class Consumer1 implements Runnable{
private Cleck1 cleck;
public Consumer1(Cleck1 cleak){
this.cleck = cleak;
}
@Override
public void run(){
for(int i = 0; i < 20; i++){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
cleck.sale();
}
}
}
运行结果: 
- 8.Condition控制线程通信
- 9.线程按序交替(案例)(互联网公司面试题)
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 编写一个程序,开启3 个线程,这三个线程的ID 分别为A、B、C,每个线程将自己的ID 在屏幕上打印10 遍,要求输出的结果必须按顺序显示。
如:ABCABCABC…… 依次递归
*/
public class TestABCAlternate {
public static void main(String[] args) {
AlternateDemo at = new AlternateDemo();
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i <= 20; i++){
at.loopA(i);
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i <= 20; i++){
at.loopB(i);
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i <= 20; i++){
at.loopC(i);
System.out.println("-----------------");
}
}
},"C").start();
}
}
class AlternateDemo{
private int num = 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(num != 1){
condition1.await();
}
//2.打印
for(int i = 1; i <= 1;i++){
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}
//3.唤醒
num = 2;
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void loopB(int totalLoop){
lock.lock();
try{
//1.判断
if(num != 2){
condition2.await();
}
//2.打印
for(int i = 1; i <= 1;i++){
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}
//3.唤醒
num = 3;
condition3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void loopC(int totalLoop){
lock.lock();
try{
//1.判断
if(num != 3){
condition3.await();
}
//2.打印
for(int i = 1; i <= 1;i++){
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}
//3.唤醒
num = 1;
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
运行结果: 
A、B、C交替打印了20次 -
- 10.ReadWriteLock读写锁
- (1)概述
- ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有writer,读取锁可以由多个reader 线程同时保持。写入锁是独占的。
- ReadWriteLock 读取操作通常不会改变共享资源,但执行写入操作时,必须独占方式来获取锁。对于读取操作占多数的数据结构。ReadWriteLock 能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性可以完全不需要考虑加锁操作。
- (2)实现 : 1个线程写,100个线程读
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReadWriteLock;
/**
* 读写锁
*
* 写写/读写 需要互斥
* 读读 不需要互斥
*
* 读锁可以让多个读线程并发持有
*
*/
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 = 0; i < 100; i++){
new Thread(new Runnable() {
@Override
public void run() {
readWriteLockDemo.get();
}
}).start();
}
}
}
class ReadWriteLockDemo{
private int num = 0;
private ReadWriteLock lock = new ReentrantReadWriteLock();
//读 允许多个线程并发读
public void get(){
lock.readLock().lock();
try{
System.out.println(Thread.currentThread().getName() + " : " + num);
}finally{
lock.readLock().unlock();
}
}
//写 一次只能有一个线程来读
public void set(int num){
lock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName());
this.num = num;
}finally{
lock.writeLock().unlock();
}
}
}
运行结果: 
- 11.线程八锁(工作中常用的八种情况)
- 题目:判断打印结果
- 不同锁下的打印结果
- (1)两个普通同步方法,两个线程,标准打印,打印结果是? ONE TWO
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");
}
}
运行结果: 
- (2)新增Thead.sleep()给getOne(),打印结果是? ONE TWO
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(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ONE");
}
public synchronized void getTwo(){
System.out.println("TWO");
}
}
运行结果: 
-
- (3)新增一个普通方法getThree(),并新增一个线程访问,打印结果是?Three ONE TWO
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");
}
}
运行结果: 
- (4)两个普通同步方法,两个Number对象,打印结果是? TWO ONE
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();
}
}
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");
}
}

- (5)修改getOne()为静态同步方法,同一个Number对象,打印结果是? TWO ONE
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 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");
}
}
- 运行结果:

- (6)修改getOne()和getTwo()都为静态方法,同一个Number对象,打印结果是? ONE TWO
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 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");
}
}
运行结果: 
- (7)修改getOne()为静态同步方法,getTwo()为普通同步方法,两个Number对象,打印结果是? TWO ONE
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();
}
}
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");
}
}
运行结果: 
- (8)两个静态同步方法,两个Number对象,打印结果是? TWO ONE
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();
}
}
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");
}
}
运行结果: 
- 结论:
- 同一时刻只有一个对象持有锁。
- 当一个对象持有锁的时候,另外一个对象不能拿到锁。
- 关键:
- 非静态方法的锁默认为this,静态方法的锁为Class实例
- 某一时刻内,只能有一个线程持有锁,无论有多少个方法。
- 12.线程池
- (1)为什么要使用线程池 ?
- 传统方式下,当我们需要线程的时候,我们就new一个线程,然后启动,再需要的时候就再new一个线程,当我们任务过多时,如果我们频繁地创建或销毁线程,那么会非常耗费资源。
- (2)线程池的作用及体系结构
- ①作用 : 线程池提供了一个线程队列,队列中保存着所有等待状态的线程,避免了创建与销毁线程的一些额外开销,提高了响应的速度。
- ②体系结构: java.util.concurrent.Executor : 负责线程的使用与调度的根接口。
- (核心)ExecutorService : 子接口 : 线程池的主要接口
- ThreadPoolExecutor 线程池实现类
- ScheduledExecutorService 子接口 负责线程的调度
- ScheduledThreadPoolExecutor 实现类 : 继承了ThreadPoolExecutor,实现了ScheduledExecutorService。具备的功能:线程池的功能以及调度的功能。
- (3)工具类:Executors
- ExecutorService newFixedThreadPool() : 创建固定大小的线程池
- ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动更改数量
- ExecutorService newSingleThreadExecutor() : 创建单个线程池,线程池中只有一个线程。
- ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延时或定时的执行任务。
- 使用线程池的好处: 避免了额外的创建和销毁的开销,也提高了响应速度。
- (4)线程池的使用案例:
- 为实现Runnable接口的线程分配任务
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool{
public static void main(String[] args) {
//1.创建线程池,线程池中包含5个线程
ExecutorService pool = Executors.newFixedThreadPool(5);
ThreadDemo2 tpd = new ThreadDemo2();
//2.为线程池中的线程分配任务
for(int i = 0; i < 10; i++){
pool.submit(tpd);
}
//3.关闭线程池
pool.shutdown();
}
}
class ThreadDemo2 implements Runnable{
private int i = 0;
@Override
public void run() {
while(i <= 100){
System.out.println(Thread.currentThread().getName() + " : " + i++);
}
}
}
运行结果: 
- 为实现Callable接口的线程分配任务
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class TestThreadPool{
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建线程池,线程池中包含5个线程
ExecutorService pool = Executors.newFixedThreadPool(5);
List<Future<Integer>> list = new ArrayList<>();
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);
}
pool.shutdown();
//计算1-100的值
for(Future<Integer> future : list){
System.out.println(future.get());
}
}
}
class ThreadDemo2 implements Runnable{
private int i = 0;
@Override
public void run() {
while(i <= 100){
System.out.println(Thread.currentThread().getName() + " : " + i++);
}
}
}
运行结果: 
- 13.线程调度
import java.util.Random;
import java.util.concurrent.*;
public class TestScheduledThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
for(int i = 0; i < 10; i++){
//第一个参数为为线程池分配的任务
//第二个参数为延迟的数值
//第三个参数为延迟的单位 此处为秒
Future<Integer> result = pool.schedule(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int num = new Random().nextInt(100);//产生一个100以内的随机数
System.out.println(Thread.currentThread().getName() + " : " + num );
return num;
}
},3 , TimeUnit.SECONDS);
System.out.println(result.get());
}
//平和方式关闭线程池
pool.shutdown();
}
}
运行结果:
- 14.ForkJoinPool 分支/合并框架 工作窃取 (JDK1.7以后出现的)
- (1)概述 : Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行join 汇总。
- (2)实现 :
import org.junit.Test;
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;
/**
* 分支合并框架
*/
public class TestForkJoinPool {
public static void main(String[] args) {
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinSumCaculate(0L, 50000000000L);
Long sum = pool.invoke(task);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为: " + Duration.between(start, end).toMillis());
}
@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()) ;
}
//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
}
}
/*
Recursive表示递归,以递归的方式不断把大任务拆分成小任务
*/
class ForkJoinSumCaculate extends RecursiveTask<Long>{
private long start;
private long end;
private static final long THURSHOLD = 10000L;
public ForkJoinSumCaculate(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;
ForkJoinSumCaculate left = new ForkJoinSumCaculate(start, middle);
left.fork();
ForkJoinSumCaculate right = new ForkJoinSumCaculate(middle + 1, end);
right.fork();
return left.join() + right.join();
}
}
}
-
- 采用“工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
- 相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能。