Java Thread
一、进程和线程
进程: 一个运行的程序
线程: 进程中的一个执行任务
一个进程中至少有一个线程,也可以运行多个线程
并行: 同一时刻多任务交替执行(单核CPU)
并发: 同一时刻多任务同时执行(多核CPU)
二、原始创建线程方式
使用jconsole
命令观察线程状态
1.继承Thread类,重写run()方法
class Test01 extends Thread{
int count = 0;
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "开始!");
while(true)
{
System.out.println("线程" + Thread.currentThread().getName() + "正在运行 " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count == 10) {
System.out.println("线程" + Thread.currentThread().getName() + "结束!");
break;
}
}
}
}
public static void main(String[] args) {
/*
* public synchronized void start() {
* start0(); --->真正创建线程函数
* }
*
* private native void start0(); 本地方法 由JVM调用
*/
System.out.println("线程" + Thread.currentThread().getName() + "开始!");
Test01 test = new Test01();
//test.run();//!!!不能直接调用run() --->没有创建新的线程,由主线程完成,整个程序串行执行
test.start();
for (int i = 0; i < 10; i++) {
System.out.println("线程" + Thread.currentThread().getName() + "正在运行 " + (i + 1));
}
System.out.println("线程" + Thread.currentThread().getName() + "结束!");
}
/*
某次运行结果:
线程main开始!
线程main正在运行 1
线程main正在运行 2
线程main正在运行 3
线程main正在运行 4
线程main正在运行 5
线程main正在运行 6
线程Thread-0开始!
线程Thread-0正在运行 1
线程main正在运行 7
线程main正在运行 8
线程main正在运行 9
线程main正在运行 10
线程main结束!
线程Thread-0正在运行 2
线程Thread-0正在运行 3
线程Thread-0正在运行 4
线程Thread-0正在运行 5
线程Thread-0正在运行 6
线程Thread-0正在运行 7
线程Thread-0正在运行 8
线程Thread-0正在运行 9
线程Thread-0正在运行 10
线程Thread-0结束!
*/
注意: Java不能真正创建线程,底层由C++创建
2.实现Runnable接口,放入Thread对象中
class Test02 implements Runnable{
int count = 0;
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "开始!");
while(true)
{
System.out.println("线程" + Thread.currentThread().getName() + "正在运行 " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count == 10) {
System.out.println("线程" + Thread.currentThread().getName() + "结束!");
break;
}
}
}
}
public static void main(String[] args) {
System.out.println("线程" + Thread.currentThread().getName() + "开始!");
Test02 test = new Test02();
//test.start(); //test类没有start()
//使用代理模式 ---> thread类帮你调用start()创建一个线程
Thread thread = new Thread(test);
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("线程" + Thread.currentThread().getName() + "正在运行 " + (i + 1));
}
System.out.println("线程" + Thread.currentThread().getName() + "结束!");
}
/*
某次运行结果:
线程main开始!
线程main正在运行 1
线程main正在运行 2
线程main正在运行 3
线程main正在运行 4
线程main正在运行 5
线程main正在运行 6
线程main正在运行 7
线程main正在运行 8
线程main正在运行 9
线程main正在运行 10
线程Thread-0开始!
线程main结束!
线程Thread-0正在运行 1
线程Thread-0正在运行 2
线程Thread-0正在运行 3
线程Thread-0正在运行 4
线程Thread-0正在运行 5
线程Thread-0正在运行 6
线程Thread-0正在运行 7
线程Thread-0正在运行 8
线程Thread-0正在运行 9
线程Thread-0正在运行 10
线程Thread-0结束!
*/
三、线程操作
1.通知线程
class Test03 implements Runnable{
private int count;
private boolean loop = true;
public void setLoop(boolean loop) {
this.loop = loop;
}
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "开始!");
while(loop)
{
System.out.println("线程" + Thread.currentThread().getName() + "正在运行 " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Test03 test03 = new Test03();
Thread thread = new Thread(test03, "3");
thread.start();
//3s后通知线程3退出
Thread.sleep(3000);
System.out.println("通知线程3退出!");
test03.setLoop(false);
}
/*
线程3开始!
线程3正在运行 1
线程3正在运行 2
线程3正在运行 3
通知线程3退出!
*/
2.中断线程休眠
使用Thread.interrupt()
方法中断线程休眠
class Test04 implements Runnable{
private int count;
private int second = 20;
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "开始!");
for (int i = 0; i < 5; i++) {
System.out.println("线程" + Thread.currentThread().getName() + " " + (++count));
}
try {
while (second > 0) {
//休眠20s
System.out.println("线程" + Thread.currentThread().getName() + "休眠" + (second--) + "s");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("线程" + Thread.currentThread().getName() + "中断休眠");
}
for (int i = 0; i < 5; i++) {
System.out.println("线程" + Thread.currentThread().getName() + " " + (++count));
}
System.out.println("线程" + Thread.currentThread().getName() + "结束!");
}
}
public static void main(String[] args) throws InterruptedException {
Test04 test04 = new Test04();
Thread thread = new Thread(test04, "4");
thread.start();
//3s后中断线程4休眠
Thread.sleep(3000);
thread.interrupt();
}
/*
线程4开始!
线程4 1
线程4 2
线程4 3
线程4 4
线程4 5
线程4休眠20s
线程4休眠19s
线程4休眠18s
线程4休眠17s
线程4中断休眠
线程4 6
线程4 7
线程4 8
线程4 9
线程4 10
线程4结束!
*/
3.线程插队
class Test05 implements Runnable{
int count = 0;
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "开始!");
while(true)
{
System.out.println("线程" + Thread.currentThread().getName() + "正在运行 " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count == 10) {
System.out.println("线程" + Thread.currentThread().getName() + "结束!");
break;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Test05 test05 = new Test05();
Thread A = new Thread(test05, "A");
A.start();
for (int i = 1; i <= 10; i++) {
System.out.println("线程" + Thread.currentThread().getName() + "正在运行 " + i);
//让A线程执行完毕后,再执行Main线程
if(i == 5)
{
//A.join(); //插队 一定成功
Thread.yield(); //礼让A线程执行,不一定成功
}
}
}
/*
join():
线程main正在运行 1
线程A开始!
线程main正在运行 2
线程main正在运行 3
线程main正在运行 4
线程main正在运行 5
线程A正在运行 1
线程A正在运行 2
线程A正在运行 3
线程A正在运行 4
线程A正在运行 5
线程A正在运行 6
线程A正在运行 7
线程A正在运行 8
线程A正在运行 9
线程A正在运行 10
线程A结束!
线程main正在运行 6
线程main正在运行 7
线程main正在运行 8
线程main正在运行 9
线程main正在运行 10
-----------------
yield():
线程main正在运行 1
线程A开始!
线程main正在运行 2
线程A正在运行 1
线程main正在运行 3
线程main正在运行 4
线程main正在运行 5
线程main正在运行 6
线程main正在运行 7
线程main正在运行 8
线程main正在运行 9
线程main正在运行 10
线程A正在运行 2
线程A正在运行 3
线程A正在运行 4
线程A正在运行 5
线程A正在运行 6
线程A正在运行 7
线程A正在运行 8
线程A正在运行 9
线程A正在运行 10
线程A结束!
*/
4.守护线程
用户线程: 工作线程
守护线程: 为工作线程服务,当所有工作线程结束,守护线程自动结束,生命周期和进程一样,例如垃圾回收机制 gc
class Test06 implements Runnable{
private int count;
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "开始!");
//线程无限循环执行
while(true)
{
System.out.println("线程" + Thread.currentThread().getName() + "正在运行 " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Test06 test06 = new Test06();
Thread thread = new Thread(test06, "Daemon");
//将Daemon线程设置为守护线程
thread.setDaemon(true);
thread.start();
System.out.println("线程" + Thread.currentThread().getName() + "开始!");
for (int i = 1; i < 10; i++) {
System.out.println("线程" + Thread.currentThread().getName() + "正在运行 " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程" + Thread.currentThread().getName() + "结束!");
}
/*
线程main开始!
线程Daemon开始!
线程main正在运行 1
线程Daemon正在运行 1
线程main正在运行 2
线程Daemon正在运行 2
线程main正在运行 3
线程Daemon正在运行 3
线程main正在运行 4
线程Daemon正在运行 4
线程Daemon正在运行 5
线程main正在运行 5
线程main正在运行 6
线程Daemon正在运行 6
线程main正在运行 7
线程Daemon正在运行 7
线程main正在运行 8
线程Daemon正在运行 8
线程main正在运行 9
线程Daemon正在运行 9
线程main结束!
线程Daemon正在运行 10
*/
四、线程六大状态
public enum State {
//创建
NEW,
//可运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
五、线程同步
同步: 在同一时刻,对于同一数据最多只用一个线程访问
1.Synchronized
1.同步代码块
//对象锁
synchronized(对象) {
//同步代码块
}
2.同步方法
private synchronized void f(){}
执行顺序问题:
1.同一个对象调用两个普通同步方法执行顺序
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
new Thread(()->{
threadTest.f1();
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
threadTest.f2();
}).start();
}
class ThreadTest{
public synchronized void f1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("f1()执行...");
}
public synchronized void f2(){
System.out.println("f2()执行...");
}
}
/*
Synchronized修饰普通方法时,锁的对象是方法的调用者(本例中为threadTest)
本例中f1()、f2()方法被同一个对象调用,谁先调用,谁先执行
延时3s
f1()执行...
延时1s
f2()执行...
*/
2.两个不同对象调用两个static同步方法执行顺序
public static void main(String[] args) {
ThreadTest threadTest1 = new ThreadTest();
ThreadTest threadTest2 = new ThreadTest();
new Thread(()->{
threadTest1.f1();
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
threadTest2.f2();
}).start();
}
}
class ThreadTest{
public static synchronized void f1(){
try {
TimeUnit.SECONDS.sleep(3 );
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("f1()执行...");
}
public static synchronized void f2(){
System.out.println("f2()执行...");
}
}
/*
Synchronized修饰static方法时,锁的对象是该类的class对象(本例中为ThreadTest.class)
本例中f1()、f2()方法被不同对象调用,但是由于锁的是同一个类class,谁先调用,谁先执行
延时3s
f1()执行...
延时1s
f2()执行...
*/
3.同一对象调用一个static同步方法和一个普通同步方法执行顺序
public static void main(String[] args) {
ThreadTest threadTest1 = new ThreadTest();
new Thread(()->{
threadTest1.f1();
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
threadTest1.f2();
}).start();
}
class ThreadTest{
public static synchronized void f1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("f1()执行...");
}
public synchronized void f2(){
System.out.println("f2()执行...");
}
}
/*
两个方法锁的对象不同,延时后f2()先执行
f2()执行...
f1()执行...
*/
卖票:
class Sell implements Runnable{
private static int total = 50;
private Boolean loop = true;
@Override
public void run() {
System.out.println("窗口" + Thread.currentThread().getName() +"开始售票!");
while(loop)
{
sellTicket();
}
}
/**
* synchronized加在方法上,变成同步方法,默认锁为 ---> this对象互斥锁
*/
private synchronized void sellTicket() {
// synchronized(this) { //synchronized加在代码块上,变成同步代码块,同一时刻只能有一个线程运行代码块
if (total <= 0) {
System.out.println("票已售完!!!");
loop = false;
return;
}
System.out.println("窗口" + Thread.currentThread().getName() + "卖掉一张票,剩余票数: " + (--total));
try {
//买完一张票休息一秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
}
}
public static void main(String[] args) {
Sell sell = new Sell();
Thread A = new Thread(sell, "A");
Thread B = new Thread(sell, "B");
Thread C = new Thread(sell, "C");
A.start();
B.start();
C.start();
}
/*
窗口B开始售票!
窗口C开始售票!
窗口A开始售票!
窗口B卖掉一张票,剩余票数: 49
窗口B卖掉一张票,剩余票数: 48
窗口B卖掉一张票,剩余票数: 47
窗口B卖掉一张票,剩余票数: 46
窗口B卖掉一张票,剩余票数: 45
窗口B卖掉一张票,剩余票数: 44
窗口B卖掉一张票,剩余票数: 43
窗口B卖掉一张票,剩余票数: 42
窗口B卖掉一张票,剩余票数: 41
窗口B卖掉一张票,剩余票数: 40
窗口B卖掉一张票,剩余票数: 39
窗口B卖掉一张票,剩余票数: 38
窗口B卖掉一张票,剩余票数: 37
窗口B卖掉一张票,剩余票数: 36
窗口B卖掉一张票,剩余票数: 35
窗口B卖掉一张票,剩余票数: 34
窗口B卖掉一张票,剩余票数: 33
窗口B卖掉一张票,剩余票数: 32
窗口B卖掉一张票,剩余票数: 31
窗口B卖掉一张票,剩余票数: 30
窗口B卖掉一张票,剩余票数: 29
窗口B卖掉一张票,剩余票数: 28
窗口B卖掉一张票,剩余票数: 27
窗口A卖掉一张票,剩余票数: 26
窗口A卖掉一张票,剩余票数: 25
窗口A卖掉一张票,剩余票数: 24
窗口A卖掉一张票,剩余票数: 23
窗口A卖掉一张票,剩余票数: 22
窗口A卖掉一张票,剩余票数: 21
窗口A卖掉一张票,剩余票数: 20
窗口A卖掉一张票,剩余票数: 19
窗口A卖掉一张票,剩余票数: 18
窗口A卖掉一张票,剩余票数: 17
窗口A卖掉一张票,剩余票数: 16
窗口A卖掉一张票,剩余票数: 15
窗口A卖掉一张票,剩余票数: 14
窗口A卖掉一张票,剩余票数: 13
窗口A卖掉一张票,剩余票数: 12
窗口A卖掉一张票,剩余票数: 11
窗口A卖掉一张票,剩余票数: 10
窗口A卖掉一张票,剩余票数: 9
窗口A卖掉一张票,剩余票数: 8
窗口A卖掉一张票,剩余票数: 7
窗口A卖掉一张票,剩余票数: 6
窗口A卖掉一张票,剩余票数: 5
窗口A卖掉一张票,剩余票数: 4
窗口A卖掉一张票,剩余票数: 3
窗口A卖掉一张票,剩余票数: 2
窗口C卖掉一张票,剩余票数: 1
窗口C卖掉一张票,剩余票数: 0
票已售完!!!
票已售完!!!
票已售完!!!
*/
2.Lock
卖票:
class Sell1 {
private int total;
public Sell1(int total) {
this.total = total;
}
private Lock lock = new ReentrantLock(true);
public void sellTicket() {
//上锁
lock.lock();
try {
if (total > 0) {
System.out.println("窗口" + Thread.currentThread().getName() + "卖掉一张票,剩余票数: " + (--total));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//解锁
lock.unlock();
}
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入总票数:");
int count = scanner.nextInt();
Sell1 sell = new Sell1(count);
new Thread(()->{
for (int i = 0; i < count; i++) {
sell.sellTicket();
}
}, "A").start();
new Thread(()->{
for (int i = 0; i < count; i++) {
sell.sellTicket();
}
}, "B").start();
new Thread(()->{
for (int i = 0; i < count; i++) {
sell.sellTicket();
}
}, "C").start();
}
/*
请输入总票数:
20
窗口A卖掉一张票,剩余票数: 19
窗口A卖掉一张票,剩余票数: 18
窗口A卖掉一张票,剩余票数: 17
窗口A卖掉一张票,剩余票数: 16
窗口B卖掉一张票,剩余票数: 15
窗口A卖掉一张票,剩余票数: 14
窗口B卖掉一张票,剩余票数: 13
窗口A卖掉一张票,剩余票数: 12
窗口C卖掉一张票,剩余票数: 11
窗口B卖掉一张票,剩余票数: 10
窗口A卖掉一张票,剩余票数: 9
窗口C卖掉一张票,剩余票数: 8
窗口B卖掉一张票,剩余票数: 7
窗口A卖掉一张票,剩余票数: 6
窗口C卖掉一张票,剩余票数: 5
窗口B卖掉一张票,剩余票数: 4
窗口A卖掉一张票,剩余票数: 3
窗口C卖掉一张票,剩余票数: 2
窗口B卖掉一张票,剩余票数: 1
窗口A卖掉一张票,剩余票数: 0
*/
Synchronized
和Lock
区别:
Synchronized
是Java关键字,Lock是一个Java类Synchronized
自动释放锁,Lock
需要手动释放锁Synchronized
适合锁少量的同步代码,Lock
适合锁大量的同步代码
3.生产者消费者问题:
Synchronized
实现:
class Resource{
private int count = 0;
public synchronized void product() throws InterruptedException {
if(count > 0)
{
this.wait();
}
count++;
System.out.println("生产者" + Thread.currentThread().getName() + "生产了一个产品,count = " + count);
this.notifyAll();
}
public synchronized void consume() throws InterruptedException {
if(count < 1)
{
this.wait();
}
count--;
System.out.println("消费者" + Thread.currentThread().getName() + "消费了一个产品,count = " + count);
this.notifyAll();
}
}
public static void main(String[] args) {
Resource resource = new Resource();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
resource.product();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
resource.consume();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
/*
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
*/
多个生产者消费者线程出现虚假唤醒问题:
Resource resource = new Resource();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
resource.product();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
resource.consume();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
resource.product();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"C").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
resource.consume();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"D").start();
}
/*
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者A生产了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者C生产了一个产品,count = 1
生产者A生产了一个产品,count = 2
生产者C生产了一个产品,count = 3
消费者B消费了一个产品,count = 2
消费者B消费了一个产品,count = 1
消费者B消费了一个产品,count = 0
生产者C生产了一个产品,count = 1
生产者A生产了一个产品,count = 2
生产者C生产了一个产品,count = 3
消费者B消费了一个产品,count = 2
消费者D消费了一个产品,count = 1
消费者D消费了一个产品,count = 0
生产者C生产了一个产品,count = 1
生产者A生产了一个产品,count = 2
生产者C生产了一个产品,count = 3
消费者D消费了一个产品,count = 2
消费者D消费了一个产品,count = 1
生产者C生产了一个产品,count = 2
生产者A生产了一个产品,count = 3
生产者C生产了一个产品,count = 4
消费者D消费了一个产品,count = 3
消费者D消费了一个产品,count = 2
消费者D消费了一个产品,count = 1
消费者D消费了一个产品,count = 0
生产者C生产了一个产品,count = 1
消费者D消费了一个产品,count = 0
生产者C生产了一个产品,count = 1
消费者D消费了一个产品,count = 0
*/
this.wait()
方法应放在while循环中判断
class Resource{
private int count = 0;
public synchronized void product() throws InterruptedException {
while(count > 0)
{
this.wait();
}
count++;
System.out.println("生产者" + Thread.currentThread().getName() + "生产了一个产品,count = " + count);
this.notifyAll();
}
public synchronized void consume() throws InterruptedException {
while(count < 1)
{
this.wait();
}
count--;
System.out.println("消费者" + Thread.currentThread().getName() + "消费了一个产品,count = " + count);
this.notifyAll();
}
}
Lock
实现:
class Resource1{
private int count = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void product() throws InterruptedException {
lock.lock();
try {
while(count > 0)
{
condition.await();
}
count++;
System.out.println("生产者" + Thread.currentThread().getName() + "生产了一个产品,count = " + count);
condition.signalAll();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
} finally {
lock.unlock();
}
}
public void consume() throws InterruptedException {
lock.lock();
try {
while(count < 1)
{
condition.await();
}
count--;
System.out.println("消费者" + Thread.currentThread().getName() + "消费了一个产品,count = " + count);
condition.signalAll();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
Resource1 resource = new Resource1();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
resource.product();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
resource.consume();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
resource.product();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"C").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
resource.consume();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"D").start();
}
六、集合类并发问题
1.List
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
/*
ConcurrentModificationException 集合类并发不安全(并发修改异常)
*/
解决方法:
- 使用
Collections.synchronizedList()
,将List方法上添加Sychronized
关键字变成同步方法
List<String> list = Collections.synchronizedList(new ArrayList<>());
2.使用CopyOnWriteArrayList
,当 List 需要被修改的时候,并不直接修改原有数组对象,而是对原有数据进行一次拷贝,将修改的内容写入副本中。写完之后,再将修改完的副本替换成原来的数组,这样就可以保证写操作不会影响读操作了。
List<String> list = new CopyOnWriteArrayList<>();
//源码
public boolean add(E e) {
final ReentrantLock lock = this.lock;
//上锁
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//拷贝原数组,增加一个元素
Object[] newElements = Arrays.copyOf(elements, len + 1);
//添加新元素
newElements[len] = e;
//将副本替换成原来的数组
setArray(newElements);
return true;
} finally {
//解锁
lock.unlock();
}
}
2.Set
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
/*
ConcurrentModificationException 集合类并发不安全(并发修改异常)
*/
解决方法:
1.使用Collections.synchronizedSet()
,将Set方法上添加Sychronized
关键字变成同步方法
Set<String> set = Collections.synchronizedSet(new HashSet<>());
2.使用CopyOnWriteArraySet
Set<String> set = new CopyOnWriteArraySet<>();
3.Map
public static void main(String[] args) {
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 20; i++) {
final String key = String.valueOf(i);
new Thread(()->{
map.put(key, UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
/*
ConcurrentModificationException 集合类并发不安全(并发修改异常)
*/
解决方法:
1.使用Collections.synchronizedMap()
,将Map方法上添加Sychronized
关键字变成同步方法
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
2.使用ConcurrentHashMap
:
Map<String, String> map = new ConcurrentHashMap<>();
七、Callable
//Callable接口
// V --> 返回值
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
//Runnabel子类 --> RunnableFuture实现类 ---> FutureTask(也是一个Runnable)
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
//通过Thread启动
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<String>(() -> {
return "1111";
});
new Thread(futureTask).start();
//获取返回值(可能会阻塞)
String str = futureTask.get();
System.out.println(str);
}
Callable
和Runnabel
区别:
- Callable执行call()方法,Runnable执行run()方法
- Callable执行后有返回值(可通过
FutureTask
获取),而Runnable没有返回值 - Callable可以抛出异常,Runnable不行
八、ThreadLocal
用于线程隔离,当前线程只能访问线程内部的数据,无法访问其他线程的数据
public static void main(String[] args) {
Vector<String> list = new Vector<>();
ThreadLocal<String> tl = new ThreadLocal<>();
new Thread(() -> {
list.add("A");
System.out.println(Thread.currentThread().getName() + "--->" + list);
//tl.set("A");
//System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
//tl.remove();
}, "A").start();
new Thread(() -> {
list.add("B");
System.out.println(Thread.currentThread().getName() + "--->" + list);
//tl.set("B");
//System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
//tl.remove();
}, "B").start();
}
/*
A--->[A]
B--->[A, B]
--------------------
ThreadLocal:
A--->A
B--->B
*/
ThreadLocal的set方法:
ThreadLocal的get方法:
ThreadLocal的remove方法:
ThreadLocalMap是Thread类的一个成员变量,每个线程都有自己的threadLocalMap存储相关数据
四种引用类型:
1.强引用
一般我们使用的对象都是强引用,即一个引用指向在堆中真正的对象,如果要回收对象只需将引用置为null,
对象没有引用则会被视为垃圾
//重写finalize方法,确认对象被视为垃圾回收
class A{
@Override
protected void finalize() throws Throwable {
System.out.println("A对象被销毁...");
}
}
public static void main(String[] args) throws IOException, InterruptedException {
A a = new A();
a = null;
System.gc();
System.out.println(a);
//等待1s垃圾回收
TimeUnit.SECONDS.sleep(1);
}
/*
null
A对象被销毁...
*/
2.软引用
垃圾回收器不会回收软引用,当堆内存不够时清理软引用,可用作缓存
public static void main(String[] args) throws InterruptedException {
//设置JVM参数 -Xmx20M 堆内存最大容量为20M
//10M
SoftReference<byte[]> reference = new SoftReference<>(new byte[1024 * 1024 * 10]);
System.out.println(reference.get());
System.gc();
TimeUnit.SECONDS.sleep(1);
//垃圾回收器不会回收软引用
System.out.println(reference.get());
//在创建一个字节数组10M,当内存不够时会清理软引用存放其他内容
byte[] bytes = new byte[1024 * 1024 * 10];
System.out.println(reference.get());
}
/*
[B@1b6d3586
[B@1b6d3586
null
*/
3.虚引用
虚引用用来通知垃圾回收器,将虚引用对象放入队列进行特殊处理,NIO中使用
public static void main(String[] args) {
//设置JVM参数 -Xmx20M 堆内存最大容量为20M
LinkedList<Object> list = new LinkedList<>();
ReferenceQueue<A> queue = new ReferenceQueue<>();
PhantomReference<A> reference = new PhantomReference<>(new A(), queue);
//获取不到虚引用指向的对象
System.out.println(reference.get());
//工作线程用于将堆内存充满
new Thread(() -> {
while(true)
{
list.add(new byte[1024 * 1024]);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(reference.get());
}
}, "工作线程").start();
//测试线程测试回收器处理虚引用
new Thread(() -> {
while(true)
{
Reference<? extends A> poll = queue.poll();
if(poll != null)
{
System.out.println("回收虚引用对象...");
}
}
}, "测试线程").start();
}
/*
null
null
null
null
null
null
null
null
null
null
null
null
A对象被销毁...
null
null
null
null
null
null
回收虚引用对象...
null
Exception in thread "工作线程" java.lang.OutOfMemoryError: Java heap space
*/
4.弱引用
弱引用指向的对象会被垃圾回收器直接回收,在ThreadLocal中使用
public static void main(String[] args) throws InterruptedException {
WeakReference<A> reference = new WeakReference<>(new A());
System.out.println(reference.get());
System.gc();
TimeUnit.SECONDS.sleep(1);
//垃圾回收器直接回收弱引用指向的对象
System.out.println(reference.get());
}
/*
com.qingsong.thread.threadlocal.reference.A@1b6d3586
A对象被销毁...
null
*/
ThreadLocalMap中Entry存储的key值使用弱引用防止内存溢出
内存溢出:没有足够的内存使用
内存泄漏:分配的内存无法释放造成内存浪费,最终造成内存溢出
-
如果key使用强引用在ThreadLocal使用完后,ThreadLocalReference引用被回收,但是由于key是强引用指向ThreadLocal,在线程没有结束并且没有删除该Entry的条件下将导致ThreadLocal无法释放造成内存泄漏
-
如果key使用弱引用在ThreadLocal使用完后,ThreadLocalReference被回收,key指向null,但是value是强引用,在线程没有结束并且没有删除该Entry的条件下会导致value无法被访问还是会造成内存泄漏
在使用完ThreadLocal应该调用remove方法删除Entry
九、常用辅助类
1.CountDownLatch
CountDownLatch
: 倒计时计数器,允许一个或多个线程等待直到其他线程执行操作完成。
public static void main(String[] args) {
//倒数次数
int count = 5;
CountDownLatch countDownLatch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "执行...");
//每当一个线程执行完毕,计数器减一
countDownLatch.countDown();
}).start();
}
//当所有线程执行完后输出结束语句,已经执行完的线程等待计数器归零被唤醒
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束!");
}
/*
Thread-0执行...
Thread-3执行...
Thread-2执行...
Thread-1执行...
Thread-4执行...
结束!
*/
2.CyclicBarrier
CyclicBarrier
: 加法计数器,允许一组线程全部等待彼此达到共同屏障。
public static void main(String[] args) {
int count = 5;
//CyclicBarrier(int parties, Runnable barrierAction)
//加法计数器,直到计数器到达count后执行Runnable线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(count, () -> {
System.out.println("结束!");
});
for (int i = 1; i <= count; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "执行...");
//等待计数器到达5
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
/*
1执行...
5执行...
3执行...
4执行...
2执行...
结束!
*/
3.Semaphore
Semaphore: 信号量,信号量维持一组许可证
public static void main(String[] args) {
//两个许可证
Semaphore semaphore = new Semaphore(2);
//10个线程每次执行2个(2个线程拿到许可证,执行完后释放)
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
try {
//线程被阻塞直到获取许可证
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "执行...");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放许可证
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
/*
1执行...
2执行...
----------
4执行...
3执行...
----------
5执行...
6执行...
----------
7执行...
8执行...
----------
9执行...
10执行...
*/
4.ReadWriteLock
ReadWriteLock
: 读写锁,读读共享,读写互斥,写写互斥
class MyStorage{
//读写锁
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "正在读取...");
System.out.println(Thread.currentThread().getName() + "读取成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
public void write(){
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "正在写入...");
System.out.println(Thread.currentThread().getName() + "写入成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
}
public static void main(String[] args) {
MyStorage myStorage = new MyStorage();
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
myStorage.read();
}, "read" + i).start();
}
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
myStorage.write();
}, "write" + i).start();
}
}
/*
read2正在读取...
read5正在读取...
read5读取成功!
read3正在读取...
read3读取成功!
read1正在读取...
read4正在读取...
read4读取成功!
read1读取成功!
read2读取成功!
write4正在写入...
write4写入成功!
write1正在写入...
write1写入成功!
write2正在写入...
write2写入成功!
write3正在写入...
write3写入成功!
write5正在写入...
write5写入成功!
*/
十、线程相关队列
1.BlockingQueue
BlockingQueue
: 阻塞队列
方式 | 抛出异常 | 返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加元素 | add | offer | put | offer(…) |
移除元素 | remove | poll | take | poll(…) |
查看队首元素 | element | peek | - | - |
public static void main(String[] args) throws InterruptedException {
//队列容量为3
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
//add ---> 队列满时抛出异常
//System.out.println(blockingQueue.add("A"));
//System.out.println(blockingQueue.add("B"));
//System.out.println(blockingQueue.add("C"));
//System.out.println(blockingQueue.add("D"));
//查看队首元素 队列为空时抛出异常
//System.out.println(blockingQueue.element());
//remove ---> 队列空时抛出异常
//System.out.println(blockingQueue.remove());
//System.out.println(blockingQueue.remove());
//System.out.println(blockingQueue.remove());
//System.out.println(blockingQueue.remove());
//---------------------------------------
//offer ---> 队列满时返回false,加入元素失败
//System.out.println(blockingQueue.offer("A"));
//System.out.println(blockingQueue.offer("B"));
//System.out.println(blockingQueue.offer("C"));
//System.out.println(blockingQueue.offer("D"));
//查看队首元素, 队列为空时返回null
//System.out.println(blockingQueue.peek());
//poll ---> 队列空时返回null
//System.out.println(blockingQueue.poll());
//System.out.println(blockingQueue.poll());
//System.out.println(blockingQueue.poll());
//System.out.println(blockingQueue.poll());
//---------------------------------------
//blockingQueue.put("A");
//blockingQueue.put("B");
//blockingQueue.put("C");
//队列满时,一直等待
//blockingQueue.put("D");
//System.out.println(blockingQueue.take());
//System.out.println(blockingQueue.take());
//System.out.println(blockingQueue.take());
//队列为空时,一直等待
//System.out.println(blockingQueue.take());
//---------------------------------------
blockingQueue.offer("A", 1, TimeUnit.SECONDS);
blockingQueue.offer("B", 1, TimeUnit.SECONDS);
blockingQueue.offer("C", 1, TimeUnit.SECONDS);
//队列满时,等待1s,超时退出
//blockingQueue.offer("D", 1, TimeUnit.SECONDS);
System.out.println(blockingQueue.poll(1, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(1, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(1, TimeUnit.SECONDS));
//队列空时,等待1s,超时返回null
System.out.println(blockingQueue.poll(1, TimeUnit.SECONDS));
}
2.SynchronousQueue
SynchronousQueue
: 同步队列,只能存放一个元素
public static void main(String[] args) {
SynchronousQueue<String> queue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "添加元素" + 1);
queue.put("1");
System.out.println(Thread.currentThread().getName() + "添加元素" + 2);
queue.put("2");
System.out.println(Thread.currentThread().getName() + "添加元素" + 3);
queue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
String value = null;
try {
TimeUnit.SECONDS.sleep(1);
value = queue.take();
System.out.println(Thread.currentThread().getName() + "取出元素" + value);
value = queue.take();
System.out.println(Thread.currentThread().getName() + "取出元素" + value);
value = queue.take();
System.out.println(Thread.currentThread().getName() + "取出元素" + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
/*
Thread-0添加元素1
Thread-1取出元素1
Thread-0添加元素2
Thread-1取出元素2
Thread-0添加元素3
Thread-1取出元素3
*/
十一、线程池
1.通过Executors创建线程池(不推荐)
public static void main(String[] args) {
//创建单一线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行...");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
/*
pool-1-thread-1执行...
pool-1-thread-1执行...
pool-1-thread-1执行...
pool-1-thread-1执行...
pool-1-thread-1执行...
pool-1-thread-1执行...
pool-1-thread-1执行...
pool-1-thread-1执行...
pool-1-thread-1执行...
pool-1-thread-1执行...
*/
public static void main(String[] args) {
//创建固定大小线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
try {
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行...");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
/*
pool-2-thread-1执行...
pool-2-thread-5执行...
pool-2-thread-1执行...
pool-2-thread-4执行...
pool-2-thread-4执行...
pool-2-thread-4执行...
pool-2-thread-2执行...
pool-2-thread-3执行...
pool-2-thread-1执行...
pool-2-thread-5执行...
*/
public static void main(String[] args) {
//创建大小可变的线程池(根据CPU情况)
ExecutorService executorService = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行...");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
/*
pool-3-thread-1执行...
pool-3-thread-5执行...
pool-3-thread-4执行...
pool-3-thread-6执行...
pool-3-thread-7执行...
pool-3-thread-3执行...
pool-3-thread-2执行...
pool-3-thread-9执行...
pool-3-thread-8执行...
pool-3-thread-10执行...
*/
源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
导致接受的任务堆积在阻塞队列中,最后造成OOM
2.使用ThreadPoolExecutor
(推荐)
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大线程池大小
long keepAliveTime, //多余空闲线程的存活时间(当前线程池数量超过corePoolSize,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁到只剩下核心线程为止)
TimeUnit unit, //keepAliveTime的单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂(使用默认的即可)
RejectedExecutionHandler handler) //拒绝策略
RejectedExecutionHandler(四种拒绝策略)
:
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
5,
3,
TimeUnit.SECONDS,
//阻塞队列容量为3
new LinkedBlockingQueue<>(3),
//使用默认的线程工厂
Executors.defaultThreadFactory(),
//多余的线程不处理,抛出异常(默认)
//new ThreadPoolExecutor.AbortPolicy());
//哪里来的去哪里,交给原来的线程代理
//new ThreadPoolExecutor.CallerRunsPolicy());
//多余的线程不处理,直接丢弃,不会抛出异常
//new ThreadPoolExecutor.DiscardPolicy());
//丢弃队列中等待最久的线程,将当前线程加入队列尝试再次运行
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
for (int i = 1; i <= 9; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行...");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
/*
AbortPolicy:
pool-1-thread-1执行...
pool-1-thread-5执行...
pool-1-thread-4执行...
pool-1-thread-3执行...
pool-1-thread-2执行...
pool-1-thread-1执行...
pool-1-thread-4执行...
pool-1-thread-5执行...
RejectedExecutionException
---------------------------
CallerRunsPolicy:
pool-1-thread-1执行...
main执行...
pool-1-thread-3执行...
pool-1-thread-2执行...
pool-1-thread-5执行...
pool-1-thread-3执行...
pool-1-thread-4执行...
pool-1-thread-1执行...
pool-1-thread-2执行...
---------------------------
DiscardPolicy:
pool-1-thread-1执行...
pool-1-thread-2执行...
pool-1-thread-1执行...
pool-1-thread-5执行...
pool-1-thread-4执行...
pool-1-thread-3执行...
pool-1-thread-1执行...
pool-1-thread-2执行...
---------------------------
DiscardOldestPolicy:
pool-1-thread-1执行...
pool-1-thread-5执行...
pool-1-thread-4执行...
pool-1-thread-4执行...
pool-1-thread-3执行...
pool-1-thread-2执行...
pool-1-thread-5执行...
pool-1-thread-1执行...
*/
设置线程池最大线程数
//CPU密集型
//设置线程池最大线程数为 计算机CPU核数 + 1
Runtime.getRuntime().availableProcessors() + 1
//IO密集型
//设置线程池最大线程数为 CPU核数的两倍
2*Runtime.getRuntime().availableProcessors()
十二、函数式接口(lambda表达式)
1.Function
//T ---> 参数类型 R ---> 返回值
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
...
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Function<String, String> function = (str) -> {
System.out.println("请输入一个字符串:");
return scanner.nextLine();
};
System.out.println("你输入的是:" + function.apply("1"));
}
/*
请输入一个字符串:
dssad
你输入的是:dssad
*/
2.Predicate
// T ---> 参数类型 返回值为boolean
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
...
}
public static void main(String[] args) {
Predicate<String> predicate = (str) -> {
return str.isEmpty();
};
System.out.println(predicate.test(""));
}
//true
3.Consumer
//T ---> 参数类型 没有返回值
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
...
}
public static void main(String[] args) {
Consumer<String> consumer = (str) -> {
System.out.println("str = " + str);
};
consumer.accept("asdasxax");
}
//str = asdasxax
4.Supplier
//T ---> 返回值类型 没有参数
@FunctionalInterface
public interface Supplier<T> {
T get();
}
public static void main(String[] args) {
Supplier<String> supplier = () -> {return "asd";};
System.out.println(supplier.get());
}
//asd
十三、Forkjoin
并行执行任务的框架,把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果
工作窃取:所有空闲线程尝试去执行其他线程未执行的子任务
计算1 + ··· + 1000000000:
//普通方式
public class Normal {
private long start;
private long end;
public Normal(long start, long end) {
this.start = start;
this.end = end;
}
public long getStart() {
return start;
}
public long getEnd() {
return end;
}
public long compute(){
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
}
}
//ForkJoin方式:
//1.继承ForkJoinTask的子类:
//RecursiveAction: 用于没有返回结果的任务。
//RecursiveTask: 用于有返回结果的任务。
public class ForkJoin_ extends RecursiveTask<Long> {
private long start;
private long end;
private long temp = 10_000L;
public ForkJoin_(long start, long end) {
this.start = start;
this.end = end;
}
public long getStart() {
return start;
}
public long getEnd() {
return end;
}
@Override
protected Long compute() {
long sum = 0;
if ((end - start) < temp) {
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
long middle = (start + end) / 2;
//按平均值划分成小任务
ForkJoin_ task1 = new ForkJoin_(start, middle);
//计算小任务
task1.fork();
//按平均值划分成小任务
ForkJoin_ task2 = new ForkJoin_(middle + 1, end);
//计算小任务
task2.fork();
//返回计算结果
sum = task1.join() + task2.join();
return sum;
}
}
}
//测试
public static void main(String[] args) {
long start = 0L;
long end = 1_000_000_000L;
Normal normal = new Normal(start, end);
long t1 = System.currentTimeMillis();
System.out.println(normal.compute());
long t2 = System.currentTimeMillis();
System.out.println("normal耗时:" + (t2 - t1));
System.out.println("=======================================");
t1 = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoin_(start, end);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
try {
System.out.println(submit.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
t2 = System.currentTimeMillis();
System.out.println("forkJoin耗时:" + (t2 - t1));
System.out.println("=======================================");
t1 = System.currentTimeMillis();
System.out.println(LongStream.rangeClosed(start, end).parallel().reduce(0, Long::sum));
t2 = System.currentTimeMillis();
System.out.println("stream耗时:" + (t2 - t1));
}
/*
500000000500000000
normal耗时:570ms
=======================================
500000000500000000
forkJoin耗时:206ms
=======================================
500000000500000000
stream耗时:165ms
*/
十四、异步回调
1.CompletableFuture.runAsync(Runnable)
:
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有返回值的异步回调(程序不会阻塞而是继续向下执行)
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
System.out.println("--------------");
//阻塞等待异步结果
completableFuture.get();
}
/*
--------------
ForkJoinPool.commonPool-worker-1
*/
2.CompletableFuture.supplyAsync(Supplier)
:
public static void main(String[] args) throws ExecutionException, InterruptedException {
//有返回值的异步回调
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//制造异常异步执行失败
//int i = 1 / 0;
System.out.println(Thread.currentThread().getName());
return "success";
});
//whenComplete(BiConsumer) 执行成功回调
//exceptionally(Function) 执行失败回调
System.out.println(completableFuture.whenComplete((t, u) -> {
//返回值
System.out.println("t = " + t);
//错误信息
System.out.println("u = " + u);
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return "fail";
}).get());
}
/*
执行成功:
ForkJoinPool.commonPool-worker-1
t = success
u = null
success
------------------------
执行失败:
t = null
u = java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
fail
*/
十五、JMM
JMM
:Java内存模型
规定:
- 线程解锁前,必须把共享变量立刻刷新到主存
- 线程加锁前,必须将主存中的最新值读取到工作内存
- 加锁和解锁是同一把锁
(三组操作成对出现)
public class Volatile_ {
//输出sum = 1 但是线程不会退出 sum不可见
//private static int num = 0;
private volatile static int num = 0; //volatile保证可见性
public static void main(String[] args) {
new Thread(() -> {
while(num == 0)
{
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println("num = " + num);
}
}
十六、Volatile
Volatile
:轻量级同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
public class Volatile01_ {
private volatile static int num = 0;
//volatile不保证原子性
//最后输出num可能小于20000,线程同时操作
public /*synchronized*/ static void add(){
num++;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}, String.valueOf(i)).start();
}
while(Thread.activeCount() > 2) //main gc 线程
{
Thread.yield();
}
System.out.println("num = " + num);
}
}
通过反编译发现:
使用原子类AtomicInteger
:
public class Volatile01_ {
private volatile static AtomicInteger num = new AtomicInteger();
public /*synchronized*/ static void add(){
num.getAndIncrement(); //num++
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}, String.valueOf(i)).start();
}
while(Thread.activeCount() > 2) //main gc 线程
{
Thread.yield();
}
System.out.println("num = " + num);
}
}
//20000
指令重排: 是对指令的优化,程序编写的顺序和实际上执行的顺序不一致。程序有万分之一的概率会产生指令重排。在多线程执行相同代码的前提下,产生指令重排,就会导致多个线程获取到了不同的结果。因此在多线程下要禁止指令重排。
x,y,a,b默认值为0
预期结果: x = 0,y = 0
线程1 | 线程2 |
---|---|
x = a | y = b |
b = 1 | a = 2 |
如果发生指令重排,结果变成: x = 2,y = 1
线程1 | 线程2 |
---|---|
b = 1 | a = 2 |
x = a | y = b |
使用volatile可以禁止指令重排(通过在操作前后添加内存屏障)
Java屏障类型:
1.LoadLoad Barriers
两个线程同时从主存中加载数据到工作内存(Load)操作互斥
2.StoreStore Barriers
两个线程同时将工作内存中的数据刷新到主存(Store)操作互斥
3.LoadStore Barriers
当一个线程从主存中加载数据到工作内存(Load),同时另一个线程将工作内存中的数据刷新到主存(Store)时,要先保证先Load成功,Store才开始
4.StoreLoad Barriers
(全能屏障)
当一个线程将工作内存中的数据刷新到主存(Store),同时另一个线程从主存中加载数据到工作内存(Load)时,要先保证Store已经成功,并且数据所有人可见,Load才开始
十七、CAS
CAS(compareAndSet)
: CPU的并发原语
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(1);
System.out.println(atomicInteger.compareAndSet(1, 2));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(1, 2));
System.out.println(atomicInteger.get());
}
/*
true
2
false
2
*/
源码分析:
//源码 如果和预期结果(expect)一样则进行更新(update)
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//atomicInteger中字段 Unsafe类对象(包含一系列native方法,底层使用C/C++) ---> 对内存进行操作
private static final Unsafe unsafe = Unsafe.getUnsafe();
//atomicInteger中方法 原子性加一操作
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
-----------------------------------------------------------------------------------
//Unsafe中方法
//var1 = this , var2 = valueOffset , var4 = 1
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
//自旋锁 直到内存中该值加一为止
do {
//获取当前对象在内存中的值
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//Unsafe中方法
public native int getIntVolatile(Object var1, long var2);
//Unsafe中方法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
CAS
无法解决ABA
问题
ABA
问题: 一个线程修改共享数据后又将该数据改回原值,另一个线程无法发现共享数据被修改过
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(1);
new Thread(() -> {
System.out.println("A:" + atomicInteger.compareAndSet(1, 2));
System.out.println("A:" + atomicInteger.get());
System.out.println("A:" + atomicInteger.compareAndSet(2, 1));
System.out.println("A:" + atomicInteger.get());
},"A").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//无法发现线程A修改过数据(ABA问题)
System.out.println("B:" + atomicInteger.compareAndSet(1, 2));
System.out.println("B:" + atomicInteger.get());
},"B").start();
}
/*
A:true
A:2
A:true
A:1
B:true
B:2
*/
解决方法:
使用**AtomicStampedReference
**每次修改更新stamp(版本号/时间戳)
public static void main(String[] args) {
AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(1, 1);
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A:stamp = " + reference.getStamp());
System.out.println("A:" + reference.compareAndSet(1, 2, reference.getStamp(), reference.getStamp() + 1));
System.out.println("A:stamp = " + reference.getStamp());
System.out.println("A:" + reference.compareAndSet(2, 1, reference.getStamp(), reference.getStamp() + 1));
System.out.println("A:stamp = " + reference.getStamp());
}, "A").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B:stamp = " + reference.getStamp());
System.out.println("B:" + reference.compareAndSet(1, 3, reference.getStamp(), reference.getStamp() + 1));
System.out.println("B:stamp = " + reference.getStamp());
}, "B").start();
}
/*
A:stamp = 1
A:true
A:stamp = 2
A:true
A:stamp = 3
B:stamp = 3
B:true
B:stamp = 4
*/
十八、AQS
1.LockSupport
线程的阻塞和唤醒
阻塞 | 唤醒 | 所属类 |
---|---|---|
wait | notify | Object |
await | signal | Condition |
pack | unpack | LockSupport |
public class LockSupport_ {
private static final Object o = new Object();
private static final ReentrantLock lock = new ReentrantLock();
private static final Condition condition = lock.newCondition();
public static void main(String[] args) {
test1();
//test2();
//test3();
}
/**
* Object类wait、notify
*/
public static void test1() {
new Thread(() -> {
/*
休眠3s先执行notify唤醒后再执行wait阻塞
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
synchronized (o) {
System.out.println("线程" + Thread.currentThread().getName() + "开启...");
try {
System.out.println("线程" + Thread.currentThread().getName() + "被阻塞...");
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "被唤醒...");
}
}, "A").start();
new Thread(() -> {
synchronized (o) {
System.out.println("线程" + Thread.currentThread().getName() + "通知...");
o.notify();
}
}, "B").start();
}
/**
* Condition类await、signal
*/
public static void test2() {
new Thread(() -> {
/*
休眠3s先执行signal唤醒后再执行await阻塞
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
lock.lock();
try {
System.out.println("线程" + Thread.currentThread().getName() + "开启...");
System.out.println("线程" + Thread.currentThread().getName() + "被阻塞...");
condition.await();
System.out.println("线程" + Thread.currentThread().getName() + "被唤醒...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "A").start();
new Thread(() -> {
lock.lock();
try {
System.out.println("线程" + Thread.currentThread().getName() + "通知...");
condition.signal();
} finally {
lock.unlock();
}
}, "B").start();
}
/**
* LockSupport类pack、unpack
*/
public static void test3() {
Thread a = new Thread(() -> {
/*
休眠3s先执行unpark唤醒后再执行park阻塞
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
System.out.println("线程" + Thread.currentThread().getName() + "开启...");
System.out.println("线程" + Thread.currentThread().getName() + "被阻塞...");
LockSupport.park();
System.out.println("线程" + Thread.currentThread().getName() + "被唤醒...");
}, "A");
a.start();
new Thread(() -> {
System.out.println("线程" + Thread.currentThread().getName() + "通知...");
LockSupport.unpark(a);
}, "B").start();
}
}
Object类wait、notify:
wait、notify方法必须在同步代码块或同步方法中使用
先执行notify后执行wait无法唤醒线程
Condition类await、signal:
await、signal方法必须在lock、unlock之间
先执行signal后执行await无法唤醒线程
LockSupport类pack、unpack方法(不需要在同步域中):
先执行unpack再执行pack可以唤醒线程,该类与使用它的每个线程关联一个许可证
每个线程最多只有一个许可证
public static void main(String[] args) {
Thread a = new Thread(() -> {
System.out.println("线程" + Thread.currentThread().getName() + "被阻塞...");
LockSupport.park();
LockSupport.park();
System.out.println("线程" + Thread.currentThread().getName() + "被唤醒...");
}, "A");
a.start();
new Thread(() -> {
System.out.println("线程" + Thread.currentThread().getName() + "通知...");
//使用同一个许可证唤醒两次线程
LockSupport.unpark(a);
LockSupport.unpark(a);
}, "B").start();
}
无法唤醒线程:
2.AbstractQueuedSynchronizer
一个依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关的同步器的框架
等待队列节点类(内部类):
等待队列是CLH(Craig、Landin 和 Hagersten)
锁定队列的变体,分为独占模式和共享模式,waitStatus是状
态字段表示线程的状态
值 | 说明 |
---|---|
CANCELLED(1) | 当前线程由于超时或中断而被取消 |
SIGNAL(-1) | 当前线程的后继线程被唤醒 |
CONDITION(-2) | 当前线程位于条件队列中 |
PROPAGATE(-3) | 共享模式下 acquireShared 应该无条件传播 |
通过ReentrantLock
的非公平锁分析AbstractQueuedSynchronizer
原理
上锁:
如果锁未被占有,则被当前线程持有并设置为拥有者
如果锁被占有,则执行acquire(1)方法:
解锁:
总结:
十九、锁
1.公平锁、非公平锁
公平锁: 先来后到、不能插队
非公平锁: 可以插队
Synchronized
非公平锁、Lock默认非公平锁
Lock lock = new ReentrantLock(); //非公平锁
Lock lock = new ReentrantLock(true); //公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2.可重入锁
所有的锁都是可重入锁,获取外部锁的同时获取内部锁,直到执行完毕释放锁
(例如有打开大门的钥匙,即可打开房间内部的卧室的门)
Synchronized:
class Draw {
public synchronized void getPen(){
System.out.println(Thread.currentThread().getName() + "拿到笔...");
//获取了getPen()方法的锁后或自动获取draw()方法的锁,直到执行完draw()方法后释放锁
draw();
}
public synchronized void draw(){
System.out.println(Thread.currentThread().getName() + "画画...");
}
}
//等待一个线程画完后,另一个线程才能画画
Draw draw = new Draw();
new Thread(() -> {
draw.getPen();
}, "A").start();
new Thread(() -> {
draw.getPen();
}, "B").start();
/*
A拿到笔...
A画画...
B拿到笔...
B画画...
*/
Lock:
class Paint {
private ReentrantLock lock = new ReentrantLock();
public void getPen(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "拿到笔...");
//获取了getPen()方法的锁后或自动获取draw()方法的锁,直到执行完draw()方法后释放锁
draw();
} finally {
lock.unlock();
}
}
public void draw(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "画画...");
} finally {
lock.unlock();
}
}
}
//等待一个线程画完后,另一个线程才能画画
Paint paint = new Paint();
new Thread(() -> {
paint.getPen();
}, "A").start();
new Thread(() -> {
paint.getPen();
}, "B").start();
/*
A拿到笔...
A画画...
B拿到笔...
B画画...
*/
3.自旋锁
使用CAS
操作自定义自旋锁:
public class SpinLock {
private AtomicReference<Thread> atomicReference = new AtomicReference(null);
//加锁
public void lock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + " lock");
while(!atomicReference.compareAndSet(null, thread)) {
}
}
//解锁
public void unlock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + " unlock");
while(!atomicReference.compareAndSet(thread, null)) {
}
}
}
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
new Thread(() -> {
spinLock.lock();
try {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "执行");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
spinLock.unlock();
}
}, "A").start();
new Thread(() -> {
spinLock.lock();
try {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "执行");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
spinLock.unlock();
}
}, "B").start();
}
/*
A lock
A执行
A执行
A执行
A执行
B lock
A执行
A unlock
B执行
B执行
B执行
B执行
B执行
B unlock
*/
4.死锁
两个线程试图获取对方的锁,相互等待造成死锁
class MyThread implements Runnable{
private static Object lock1 = new Object();
private static Object lock2 = new Object();
private boolean flag;
public MyThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
//互相等待获取对方的锁
if(flag) {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "获取lock1...");
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "获取lock2...");
}
}
}
else
{
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "获取lock2...");
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "获取lock1...");
}
}
}
}
}
public static void main(String[] args) {
new Thread(new MyThread(true), "A").start();
new Thread(new MyThread(false), "B").start();
}
/*
A获取lock1...
B获取lock2...
......
*/
排查方式:
1.在终端中输入jsp -l
命令,查看程序对应的进程号
2.使用jstack 进程号
查看堆栈信息找到死锁
最后显示死锁信息:
5.锁升级
jdk1.6以前,synchronized还是一个重量级锁,效率较低,jdk1.6以后,JVM为提高锁的获取和释放效率对synchronized进行优化,从此以后锁如下四种状态:
无锁:没有给资源上锁,修改资源操作在循环中进行,线程会不断尝试修改资源,如果没有冲突则修改成功,否则就会继续循环尝试直到修改成功
偏向锁:初次执行到同步块时会升级会偏向锁,当同步代码被同一个线程访问时(不存在竞争),那么该线程会自动获得锁,然后通过CAS操作在对象头的Mark Word中存储线程id
轻量级锁:当锁为偏向锁并产生竞争时会升级为轻量级锁,通过CAS操作抢锁,没有抢到的线程会进行自旋操作。长时间自旋空耗CPU,执行不了任何有效任务称为忙等
重量级锁:忙等是有限度的,当自旋10次任未获取到锁时会升级为重量级锁,后续线程抢不到锁后会将自己阻塞(而不是忙等),等待被唤醒
synchronized使用的锁是存放在对象头之中的,在Hotspot对象头主要包括
Mark Word:标识字段,默认存储对象的hashcode,分代年龄和标志位信息,这些信息会随着锁标识位的变化而变化
Klass Point:类型指针,对象指向它的类原数据指针,JVM通过这个指针来确定是哪个类的实例
在32位的机器中:
无锁和偏向锁的锁标识位(占2bit)都是01,通过是否是偏向锁字段(占1bit)进行区分
轻量级锁和重量级锁都会开辟30bit空间存放指向锁的指针,锁标志位00为轻量级锁,10为重量级锁