Java 多线程同步
程序员文章站
2022-05-04 18:17:49
...
文章目录
1、概述
1.1 场景
- 在处理任务时,多线程同步是十分常见的,故十分有必要做一个详细的了解。
- 部分问题,待后续整理完后,继续补充
1.2 思维导图
2、实现多线程的两种方式
扩充:在 java 中,每次程序运行至少启动 2 个线程。一个是 main 线程,一个是垃圾收集线程。因为每当使用 java 命令执行一个类的时候,实际上都会启动一个 JVM,每一个 JVM 其实在就是在操作系统中启动了一个进程。
2.1 继承 Thread
package threadDemo;
public class ThreadDemo {
public static void main(String[] args) {
new ThreadDemo01().start();
System.out.println(Thread.currentThread().getName() + "主线程");
}
}
class ThreadDemo01 extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "子线程");
}
}
输出结果:
main主线程
Thread-0子线程
2.2 实现 Runnable(推荐)
- 避免 java 类
单继承
的局限性 - 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
package threadDemo;
public class ThreadDemo {
public static void main(String[] args) {
new Thread(new ThreadDemo02()).start();
System.out.println(Thread.currentThread().getName() + "主线程");
}
}
class ThreadDemo02 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "子线程");
}
}
输出结果:
main主线程
Thread-0子线程
3、线程安全问题
3.1 线程不安全问题
- 案例:有 100 张票,3 个窗口同时出售。(经典的生产者、消费者模型)
- 实质:多线程
同时写
一份资源(如;票)
package threadDemo;
public class Ticket implements Runnable {
private int ticket = 100; // 票数
@Override
public void run() {
// 模拟一直在售票
while (true) {
if (ticket > 0) {
try {
// 模拟出票操作
Thread.sleep(100); // 0.1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + "正在卖: " + ticket--);
}
}
}
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();
}
}
输出结果:
窗口3正在卖: 5
窗口2正在卖: 5
窗口1正在卖: 4
窗口2正在卖: 3
窗口3正在卖: 2
窗口1正在卖: 1
窗口2正在卖: 0
窗口3正在卖: -1
发现程序出现了 2 个问题:
- 相同的票数,比如 5 这张票被卖了两回。
- 不存在的票,比如 0 票与 -1 票,是不存在的。
3.2 线程同步机制
- 窗口1 线程进入操作的时候,窗口2 和 窗口3 线程只能在外等着
- 窗口1 操作结束,窗口1 和 窗口2 和 窗口3 才有机会进入代码去执行
- 也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
3.2.1 同步代码块
语法:
synchronized(同步锁){ // 锁对象可以是任意类型
需要同步操作的代码
}
package threadDemo;
public class Ticket implements Runnable {
private int ticket = 100; // 票数
Object lock = new Object();
@Override
public void run() {
// 模拟一直在售票
while (true) {
synchronized (lock) { // 同步代码块
if (ticket > 0) {
try {
// 模拟出票操作
Thread.sleep(100); // 0.1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + "正在卖: " + ticket--);
}
}
}
}
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();
}
}
这样就解决了数据同步不安全的问题(方案一)。
3.2.2 同步方法
语法:
public synchronized void method(){
可能会产生线程安全问题的代码
}
package threadDemo;
public class Ticket implements Runnable {
private int ticket = 100; // 票数
@Override
public void run() {
// 模拟一直在售票
while (true) {
sellTicket();
}
}
// 同步方法
private synchronized void sellTicket() {
if (ticket > 0) {
try {
// 模拟出票操作
Thread.sleep(100); // 0.1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + "正在卖: " + ticket--);
}
}
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();
}
}
这样就解决了数据同步不安全的问题(方案二)。
3.2.3 lock 锁(推荐)
-
java.util.concurrent.locks.Lock
机制提供了比synchronized代码块
和synchronized方法
更广泛的锁定操作,同步代码块/同步方法具有的功能Lock
都有,除此之外更强大,更体现面向对象。 -
public void lock()
: 加同步锁。 -
public void unlock()
: 释放同步锁。
package threadDemo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Ticket implements Runnable {
private int ticket = 100; // 票数
Lock lock = new ReentrantLock();
@Override
public void run() {
// 模拟一直在售票
while (true) {
lock.lock();
if (ticket > 0) {
try {
// 模拟出票操作
Thread.sleep(100); // 0.1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + "正在卖: " + ticket--);
}
lock.unlock();
}
}
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();
}
}
4、线程状态
5、线程池
- Java 里面线程池的*接口是
java.util.concurrent.Executor
,但是严格意义上讲Executor 并不是一个线程池,而只是一个执行线程的工具。 - 真正的线程池接口是
java.util.concurrent.ExecutorService
。
package threadDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2); // 包含2个线程对象
MyRunnable myRunnable = new MyRunnable();
executorService.submit(myRunnable);
executorService.submit(myRunnable);
executorService.submit(myRunnable);
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我要一个教练");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了: " + Thread.currentThread().getName());
System.out.println("教我游泳,交完后,教练回到了游泳池");
}
}
上一篇: Python爬取国家统计局行政区划信息
下一篇: Quick_Python_0