注:本系列教程只适用于JUC入门
什么是JUC
JUC 就是JavaJDK中的(java.util.concurrent)包的名字的缩写,在jdk1.5开始提供,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中Collection 实现等。
回顾初级多线程
我们在学习Java的初级阶段的时候我们,我们一定学过线程,我们回忆一下,创建线程的两大方法
- 继承Thread类,重写run方法
- 实现Runnable接口
还有一点我们需要注意,重写run方法并不能开启线程,开启线程需要调用
Thread.start()
方法
我们既然回顾,那就再多写一点,我们来看一道经典的电影院窗口买票的例子
继承Thread类实现
首先:我们来通过继承Thread方法来实现多线程
代码如下:
public class OldThread {
public static void main(String[] args) {
Ticket ticket1 = new Ticket("1号窗");
Ticket ticket2 = new Ticket("2号窗");
Ticket ticket3 = new Ticket("3号窗");
ticket1.start();
ticket2.start();
ticket3.start();
}
}
class Ticket extends Thread {
public Ticket() {
}
public Ticket(String name) {
super(name);
}
private static int totalTicket = 100;
@Override
public void run() {
while (true){
if (totalTicket > 0) {
System.out.println(getName() + "卖出第" + (totalTicket--) + "张票,还剩下" + totalTicket + "张票");
} else {
break;
}
}
}
}
运行截图:
问题分析:
我们明显能看出99张票买了2次,这在我们实际生活中是不被允许的,所以我们需要在run方法上加上synchronized,通过加锁来实现同步,让我们的票同一时间只能有一个被线程操作。
public class OldThread {
public static void main(String[] args) {
Ticket ticket1 = new Ticket("1号窗");
Ticket ticket2 = new Ticket("2号窗");
Ticket ticket3 = new Ticket("3号窗");
ticket1.start();
ticket2.start();
ticket3.start();
}
}
class Ticket extends Thread {
public Ticket() {
}
public Ticket(String name) {
super(name);
}
private static int totalTicket = 100;
@Override
public synchronized void run() {
while (true){
if (totalTicket > 0) {
System.out.println(getName() + "卖出第" + (totalTicket--) + "张票,还剩下" + totalTicket + "张票");
} else {
break;
}
}
}
}
实现Runnable接口
实现Runnable接口来创建开启线程有两种方法,第一,通过在操作类上实现Runnable接口,第二,通过匿名内部类方法实现Runnable接口
public class OldRunnable {
public static void main(String[] args) {
Ticket myRunnable = new Ticket();
new Thread(myRunnable, "1号窗").start();
new Thread(myRunnable, "2号窗").start();
new Thread(myRunnable, "3号窗").start();
}
}
class Ticket implements Runnable {
private int totalTicket = 100;
@Override
public void run() {
while (true){
if (totalTicket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + (totalTicket--) + "张票,还剩下" + totalTicket + "张票");
} else {
break;
}
}
}
}
匿名内部类实现多线程
public class AnonymousRunnable {
public static void main(String[] args) {
new Thread(new Runnable() {
private int ticket = 30;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + (ticket--) + "张票,还剩下" + ticket + "张票");
} else {
break;
}
}
}
},"窗口1").start();
}
}
多线程的状态
public enum State {
//创建
NEW,
//就绪,可运行
RUNNABLE,
//阻塞 wait,sleep
BLOCKED,
//等待,一直等待,(俗称:不见不散)
WAITING,
//在一定时间内等待,时间一到,就继续运行(俗称:过期不候)
TIMED_WAITING,
//终结
TERMINATED;
}
wait和sleep的区别
wait,放开手里的锁阻塞
sleep,不放开锁阻塞
wait和sleep的主要区别是放不放手里的锁
线程的生命周期图
Lock锁
我们在java.util.concurrent.locks
包下我们发现了Lock接口
我们发现官方文档直接说,Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。也就是说Lock
的锁机制,比synchronized
的更加好。
那他的使用方法是什么呢?
因为我们知道Lock是一个接口,那么我们看看该接口是否有他自己的实现类,实现类如下
共有三个实现类,但是我们一般使用的是第一个实现类。官方给的使用方法为:
Lock l = ...; //我们一般写为 Lock l = new ReentrantLock();
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
所以我们就明白了我们的加锁机制
之前代码:
class Ticket{
private int number = 30;
public void saleTicket(){
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖第" + (number--) + "张票,还剩" + number + "张票");
}
}
}
/**
* 多线程编程的口诀:线程 操作 资源类
*/
public class MyLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
}
},"C").start();
}
}
然后我们就可以将我么们的代码改写
class Ticket{
private int number = 30;
private Lock l = new ReentrantLock();
public void saleTicket(){
l.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖第" + (number--) + "张票,还剩" + number + "张票");
}
}finally {
l.unlock();
}
}
}
/**
* 多线程编程的口诀:线程 操作 资源类
*/
public class MyLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
}
},"C").start();
}
}
其实匿名内部类我们也可以通过lambda表达式来进行实现
lambda表达式口诀拷贝小括号,写死右箭头,落地大括号
改写后
class Ticket{
private int number = 30;
private Lock l = new ReentrantLock();
public void saleTicket(){
l.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖第" + (number--) + "张票,还剩" + number + "张票");
}
}finally {
l.unlock();
}
}
}
/**
* 多线程编程的口诀:线程 操作 资源类
*/
public class MyLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() -> { for (int i = 0; i < 40; i++) { ticket.saleTicket(); } },"A").start();
new Thread(()-> {for (int i = 0; i < 40; i++) {ticket.saleTicket(); }},"B").start();
new Thread(()->{for (int i = 0; i < 40; i++) {ticket.saleTicket(); }},"C").start();
}
}
我们发现代码无比简洁,更有条理性,更加容易阅读,提高了观赏性。