多线程
1. 基本概念
1.1 程序,进程,线程
- 程序:完成一定功能的静态代码,对比火车站售票厅
- 进程:正在执行的静态代码,称为进程,对比北京西站售票
- 线程:为了并发执行一些异步任务,比对售票的中多个窗口
- 单核cpu和多核cpu
- java程启动后,main线程,gc线程,异常处理线程
1.2 并行和并发
- 并行:多核cpu下,多线程同时执行
- 并发:如果是单核cpu,采用抢占时cpu调度模型,让cpu在多个线程之间切换执行
完成一个任务需要多少个线程
- io密集型 cpu核数*2
- cpu密集型 cpu核数
- 混合型 经验
1.3 线程使用的场景
- 网络连接tomcat,mysql,一个连接对一个一个线程, one connection one thread
- 文件操作,文件下载,后台启动一个线程异步执行长时间的任务
2 多线程实战
2.1 Thread
- 定义子类继承Thread类。
- 类中重写Thread类中的run方法。
- 创建Thread子类对象,即创建了线程对象。
- 调用线程对象start方法:启动线程,调用run方法。
启动一个线程,在线程中执行1-100的偶数打印工作
Mythread类:
//Thread类 适用于简单的线程场景,但是如果已经继承了其他类,就不能再继承Thread类了,所以有一定的限制。
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
if (i%2==0){
// System.out.println(getName()+":"+i);
System.out.println(Thread.currentThread().getName() + ":主线程");
}
}
}
}
Test1类:
/*
* 线程:
* 多线程:
* 1.继承Thread类
* 2.重写run方法
* 3.创建子类对象
* 4.调用start方法
* 5.调用start方法后,线程才会启动,执行run方法
* 6.一个线程只能执行一次start方法
*/
public class Test1 {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
/* myThread1.setName("线程1");*/ //设置线程名字
myThread1.start();
MyThread myThread2 = new MyThread();
/*myThread2.setName("线程2");*/
myThread2.start();
//获取线程名
System.out.println(Thread.currentThread().getName() + ":主线程");
}
}
- 如果子线程执行,进程不会停止
2.2 Runable
- 定义子类,实现Runnable接口。
- 类中重写Runnable接口中的run方法。
- 通过Thread类含参构造器创建线程对象。
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
- 调用Thread类的start方法:开启线程, 调用Runnable子类接口的run方法。
MyTask类:
public class MyTask implements Runnable{
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
if (i%2==0){
//System.out.println(getName()+":"+i);
System.out.println(Thread.currentThread().getName() + ": "+i);
//线程礼让 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
//若队列中没有同优先级的线程,忽略此方法
Thread.yield();
}
//sleep使线程阻塞 指定线程休眠的时间,单位毫秒,让出cpu时间片,其他线程可以抢占cpu时间片
// try {
// Thread.sleep(100);
// }
// catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
}
}
}
Test1类:
//Runnable接口的实现类
public class Test1 {
public static void main(String[] args) {
MyTask myTask = new MyTask();
Thread thread1 = new Thread(myTask,"线程1.。。。。。。。。");
thread1.setPriority(5);//设置线程优先级 1-10 1最低 10最高
thread1.start();
Thread thread2 = new Thread(myTask,"线程2");
thread2.setPriority(5);//设置线程优先级 1-10 1最低 10最高
thread2.start();
}
}
2.3 Thread常见方法
- 构造函数:
-
- Thread(): 创建新的Thread对象
- Thread(String threadname): 创建线程并指定线程实例名
- Thread(Runnable target): 指定创建线程的目标对象,它实现了Runnable接口中的run方法
- Thread(Runnable target, String name): 创建新的Thread对象
- void start(): 启动线程,并执行对象的run()方法
- run(): 线程在被调度时执行的操作
- String getName(): 返回线程的名称
- **void setName(String name)😗*设置该线程名称
- static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
- static void yield(): 线程让步
-
- 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
- 若队列中没有同优先级的线程,忽略此方法
- join() : 当某个程序执行流中调用其他线程的 join() 方法时, 调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
- static void sleep(long millis): (指定时间:毫秒) 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
- stop(): 强制线程生命期结束,不推荐使用
- boolean isAlive(): 返回boolean,判断线程是否还活着
2.4 sleep
指定线程休眠的时间,单位毫秒,让出cpu时间片,其他线程可以抢占cpu时间片。
//sleep使线程阻塞 指定线程休眠的时间,单位毫秒,让出cpu时间片,其他线程可以抢占cpu时间片
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
2.5 线程优先级
- 线程的优先级等级
-
- MAX_PRIORITY: 10
- MIN _PRIORITY: 1
- NORM_PRIORITY: 5
//Runable接口的实现类
public class Test1 {
public static void main(String[] args) {
MyTask myTask = new MyTask();
Thread thread1 = new Thread(myTask,"线程1.。。。。。。。。");
thread1.setPriority(5);//设置线程优先级 1-10 1最低 10最高
thread1.start();
Thread thread2 = new Thread(myTask,"线程2");
thread2.setPriority(5);//设置线程优先级 1-10 1最低 10最高
thread2.start();
}
}
2.6 守护线程
- 其他线程都执行结束,守护线程自动结束
- 守护启动子线程,也是守护线程
- 守护线程的语法
thread.(*setDaemon(true)*
设置守护线程
public class Test2 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//设置守护线程 一般用于后台线程 当其他非后台线程结束后,该子线程也会结束
myThread.setDaemon(true);
myThread.start();
JOptionPane.showMessageDialog(null,"是否向下执行?");//弹出对话框 阻塞
System.out.println("main线程终止");
}
static class MyThread extends Thread
{
@Override
public void run() {
while (true)
{
System.out.println("子线程正在执行");
}
}
}
}
运行结果:
main线程、子线程正在运行
点击确定后,主线程(main)结束,由于将子线程设置为了守护线程,子线程不再运行,程序进程结束:
2.7 yield(礼让线程)
让出cpu的时间分片,让出cpu的执行权
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法。
//Runnable接口的实现类 Runnable接口创建线程的好处是,可以避免Java单继承的限制
public class MyTask implements Runnable{
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
if (i%2==0){
//System.out.println(getName()+":"+i);
System.out.println(Thread.currentThread().getName() + ": "+i);
//线程礼让 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
//若队列中没有同优先级的线程,忽略此方法
Thread.yield();
}
}
}
}
总结
三种线程启动方式:
public class ThreadDemo {
//线程的三种启动方式
public static void main(String[] args) {
//1.方式1 继承Thread启动线程
Mythread mythread = new Mythread();
mythread.start();
//2.方式2 实现 Runable接口启动
Mytask mytask = new Mytask();
Thread thread = new Thread(mytask);
thread.start();
//3.方式3 匿名内部类启动 与方式2本质上相同只是简洁写法 但是可读性差
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Thread.currentThread().setName("匿名内部类启动线程");
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}).start();
}
//方式1 继承Thread类启动
static class Mythread extends Thread
{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Thread.currentThread().setName("继承Thread方式启动线程");
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
//方式2 实现Runable接口方式启动
static class Mytask implements Runnable
{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Thread.currentThread().setName("Runable方式启动线程");
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
}
Thread类和Runable类的区别
Thread类:
适用于简单的线程场景,但是如果已经继承了其他类,就不能再继承Thread类了,所以有一定的限制。
Runable类:
Runnable接口的实现类 Runnable接口创建线程的好处是,可以避免Java单继承的限制
2.8 线程合并join
有两个线程1,2如下
执行join()方法后
案例:
public class Test1 {
/**
* CountDownLatch:可以实现相同的效果
* @param args
*/
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " main orver");
}
}
2.9 线程退出
2.9.1 stop(不推荐使用)
不推荐,线程退出方式粗暴,不管线程正在执行的任务,直接退出,可能丢失数据。
public class Test1 {
public static void main(String[] args) {
Thread t1 = new Thread(new MyTask());
t1.start();
Scanner in = new Scanner(System.in);
System.out.println("输入1/0:0表示退出");
int i = in.nextInt(); ///主线程进入IO阻塞
if (i == 0) {
t1.stop();
}
System.out.println("main over");
}
static class MyTask implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
2.9.2 中断Interrupt
- interrupt():发送中断信号(true)
-
- 如果线程在阻塞状态,比如sleep(),join(),wait(),这时接收到中断信号会抛出一个异常InterruptException,同时中断信号清除(false)
- 只是发送信号,不会对线程产生影响
- **static interrupted():**得到中断信号(true),然后把中断信号设置成false
- **isInterrupted():**得到中断信号,不会清除中断信号
package com.example.java.day8.a_join_test;/**
* @User HASEE
* @Author WeiXu
* @Createtime 2023/7/27-27-9:42
* @PACKAGE_NAME com.example.java.day8.a_join_test
*/
import javax.swing.*;
/**
*@ClassName:Test2
*@author weixu
*@date 2023/7/27 9:42
*/
//线程退出
public class InterruptTest2 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + "正在进行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程休眠被打断");
e.printStackTrace();
// //由于t1线程处于阻塞状态此时中断信号会被清除,所以需要再次中断
// Thread.currentThread().interrupt();
}
if (Thread.currentThread().isInterrupted()) {
System.out.println("线程退出");
break;
}
}
}
});
t1.setDaemon(true);
t1.start();
JOptionPane.showMessageDialog(null,"子线程是否退出?");//弹出对话框 阻塞
t1.interrupt();//中断线程 如果该线程处于join() , join(long) , join(long, int) , sleep(long) ,或sleep(long, int) ,
// 这个类的方法,那么它的中断状态将被清除,并且将收到一个InterruptedException 会抛出异常
// t1.stop();//粗暴的线程退出 不建议使用 会导致数据不一致
JOptionPane.showMessageDialog(null,"是否接着执行main线程?");//弹出对话框 阻塞
System.out.println("主线程");
}
}
运行结果:
3. 线程原理
3.1 线程的调度与时间片
由于 CPU 的计算频率非常高,每秒计算数十亿次,于是,可以将 CPU 的时间从毫秒的维度进行分段,每一小段叫做一个 CPU 时间片。不同的操作系统、不同的处理器,线程的 CPU 时间片长度都不同。假定操作系统的线程一个时间片的时间长度为 20 毫秒(比如 Windows XP),在一个 2GHz 的 CPU 上,那么一个时间片可以进行计算的次数是: 20 亿/(1000/20) =4 千万次,也就是说,一个时间片内的计算量是非常巨大的。 目前操作系统中主流的线程调度方式大都是:基于 CPU 时间片方式进行线程调度。线程只有得到 CPU 时间片,才能执行指令,处于执行状态;没有得到时间片的线程,处于就绪状态,等待系统分配下一个 CPU 时间片。由于时间片非常短,在各个线程之间快速地切换,表现出来特征是很多个线程在“同时执行”或者“并发执行”。线程的调度模型,目前主要分为两种调度模型:分时调度模型、抢占式调度模型。
3.1.1 分时调度模型
系统平均分配 CPU 的时间片,所有线程轮流占用 CPU。分时调度模型在时间片调度的分配上,所有线程人人平等。下图就是一个分时调度的简单例子:三个线程,轮流得到 CPU 时间片;一个线程执行时,另外两个线程处于就绪状态。
3.1.2抢占式调度模型
系统按照线程优先级分配 CPU 时间片。优先级高的线程,优先分配 CPU 时间片;如果所有的就绪线程的优先级相同,那么会随机选择一个;优先级高的线程获取的 CPU 时间片相对多一些。 由于目前大部分操作系统都是使用抢占式调度模型进行线程调度。 Java 的线程管理和调度是委托给了操作系统完成的,与之相对应, Java 的线程调度也是使用抢占式调度模型。
3.2 线程状态
3.2.1 操作系统线程的状态
3.2.2 java的线程状态(六种状态)
Thread.State 是一个内部枚举类,定义了 6 个枚举常量,分别代表 Java 线程的 6 种状态,具体如下:
public static enum State {
NEW, //新建
RUNNABLE, //可执行:包含操作系统的就绪、运行两种状态
BLOCKED, //阻塞 -> 操作系统线程中的阻塞
WAITING, //等待 -> 操作系统线程中的阻塞
TIMED_WAITING, //计时等待 -> 操作系统线程中的阻塞
TERMINATED; //终止
}
在Thread.State 定义的 6 种状态中,有四种是比较常见的状态,它们是: NEW 状态、RUNNABLE状态、 TERMINATED 状态、 TIMED_WAITING 状态。
获取java的线程状态
public Thread.State getState(); //返回当前线程的执行状态,一个枚举类型值
1. NEW 状态 通过 new Thread(…)已经创建线程,但尚未调用 start()启动线程,该线程处于 NEW(新建)状态。虽然前面介绍了4种方式创建线程,但是其中的其他三种方式,本质上都是通过new Thread( )创建的线程,仅仅是创建了不同的 target 执行目标实例(如 Runnable 实例)。
2. RUNNABLE 状态 Java 把就绪(Ready)和执行(Running)两种状态合并为一种状态:可执行(RUNNABLE)状态(或者可运行状态)。调用了线程的 start()实例方法后,线程就处于就绪状态;此线程获取到 CPU 时间片后,开始执行 run( )方法中的业务代码,线程处于执行状态。
(1)就绪状态就绪状态仅仅表示线程具备运行资格,如果没有被操作系统的调度程序挑选中,线程就永远是就绪状态;当前线程进入就绪状态的条件,大致包括以下几种:
- 调用线程的 start()方法,此线程进入就绪状态。
- 当前线程的执行时间片用完。
- 线程睡眠(sleep)操作结束。
- 对其他线程合入(join)操作结束。
- 等待用户输入结束。
- 线程争抢到对象锁(Object Monitor)。
- 当前线程调用了 yield 方法出让 CPU 执行权限。
(2)执行状态
线程调度程序从就绪状态的线程中选择一个线程,作为当前线程时线程所处的状态。这也是线程进入执行状态的唯一方式。
3. BLOCKED 状态 处于阻塞(BLOCKED)状态的线程并不会占用 CPU 资源,以下情况会让线程进入阻塞状态:
(1)线程等待获取锁 等待获取一个锁,而该锁被其他线程持有,则该线程进入阻塞状态。当其他线程释放了该锁,并且线程调度器允许该线程持有该锁时,该线程退出阻塞状态。(2) IO 阻塞 线程发起了一个阻塞式 IO 操作后,如果不具备 IO 操作的条件,线程会进入阻塞状态。 IO 包括磁盘 IO、 网络 IO 等。 IO 阻塞的一个简单例子:线程等待用户输入内容后继续执行。
4. WAITING 状态
处于 WAITING(无限期等待)状态的线程不会被分配 CPU 时间片,需要被其他线程显式地唤醒,才会进入就绪状态。线程调用以下 3 种方法,会让自己进入无限等待状态:
- Object.wait() 方法,对应的唤醒方式为: Object.notify() / Object.notifyAll()。
- Thread.join() 方法,对应的唤醒方式为:被合入的线程执行完毕。
- LockSupport.park() 方法,对应的唤醒方式为: LockSupport.unpark(Thread)。
5. TIMED_WAITING 状态 处于 TIMED_WAITING(限时等待)状态的线程不会被分配 CPU 时间片,如果指定时间之内没有被唤醒,限时等待的线程会被系统自动唤醒,进入就绪状态。以下 3 个方法会让线程进入限时等待状态:
- Thread.sleep(time) 方法,对应的唤醒方式为: sleep 睡眠时间结束。
- Object.wait(time) 方 法 , 对 应 的 唤 醒 方 式 为 : 调 用 Object.notify() /Object.notifyAll()去主动唤醒,或者限时结束。
- LockSupport.parkNanos(time)/parkUntil(time) 方法,对应的唤醒方式为:线程调用配套的 LockSupport.unpark(Thread)方法结束,或者线程停止(park)时限结束。
进入 BLOCKED 状态、 WAITING 状态、 TIMED_WAITING 状态的线程都会让出 CPU 的使用权;另外,等待或者阻塞状态的线程被唤醒后,进入 Ready 状态,需要重新获取时间片才能接着运行。
6. TERMINATED 状态 线程结束任务之后,将会正常进入 TERMINATED(死亡)状态;或者说在线程执行过程中发生了异常(而没有被处理),也会导致线程进入死亡状态。
3.3 多线程自增i++和线程执行原理
3.3.1 多线程自增i++
4个线程自增一个堆(共享的)里的对象的值
i++的反编译结果:
四个线程执行时内存状态:
线程产生为没一个虚拟机栈分配1M内存
-Xss128K:指定每个线程的栈大小是128K
3.3.2 四线程i++实验
Plus类:
public class Plus {
private int amount = 0;
public Plus() {
}
public Plus(int amount) {
this.amount = amount;
}
void selfPlus(){
amount++;
}
public int getAmount(){
return amount;
}
}
PlusTask类:
public class PlusTask implements Runnable{
private Plus plus;
public PlusTask(Plus plus) {
this.plus = plus;
}
@Override
public void run() {
// synchronized (this)//加锁 保证线程安全
// {
for (int i = 0; i <100000000 ; i++)
{
plus.selfPlus();
}
// }
}
}
PlusDemo类:
public class PlusDemo {
public static void main(String[] args) throws InterruptedException {
Plus plus = new Plus();
PlusTask plusTask = new PlusTask(plus);
Thread thread1 = new Thread(plusTask);
Thread thread2 = new Thread(plusTask);
Thread thread3 = new Thread(plusTask);
Thread thread4 = new Thread(plusTask);
//四个线程并行执行 会出现线程安全问题 需要加锁 如i++的反编译结果为先取值:
// i = 1再加值:i+1 再赋值 i = 2但是在这个过程中四个线程可能同时取值为1
//都加值为2 再赋值为2 造成了线程安全问题
thread1.start();
thread2.start();
thread3.start();
thread4.start();
//先执行完子线程 再执行主线程
thread1.join();
thread2.join();
thread3.join();
thread4.join();
System.out.println("自加结果:"+plus.getAmount());
}
}
运行结果:
线程不安全,四个线程可能同时取值,运行结果不为400000000:
加锁之后运行结果为400000000:
4. 线程同步
4.1 多窗口买票
Ticket类:
package com.example.java.day9;/**
* @User HASEE
* @Author WeiXu
* @Createtime 2023/7/28-28-9:26
* @PACKAGE_NAME com.example.java.day9
*/
/**
*@ClassName:Ticket
*@author weixu
*@date 2023/7/28 9:26
*/
public class Ticket {
private int count ;
public Ticket() {
}
public Ticket(int count){
this.count = count;
}
public void setCount(int count) {
this.count = count;
}
//查询当前剩余票数
public int getCount(){
return count;
}
//出票
public int sellTicket(){
return this.count--;
}
}
WindowTask:
public class WindowTask implements Runnable {
private Ticket ticket;
public WindowTask() {
}
public WindowTask(Ticket ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while (true) {
if (ticket.getCount() <= 0) {
System.out.println("票卖完了");
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " 卖了第" + ticket.sellTicket() + "张票");
}
}
}
Test1类
public class Test1 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
ticket.setCount(100);
WindowTask windowTask = new WindowTask(ticket);
Thread thread1 = new Thread(windowTask);
thread1.setName("窗口1");
Thread thread2 = new Thread(windowTask);
thread2.setName("窗口2");
Thread thread3 = new Thread(windowTask);
thread3.setName("窗口3");
Thread thread4 = new Thread(windowTask);
thread4.setName("窗口4");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
运行结果:由于存在线程安全问题因此会出现超卖重卖问题
理想状态如下:
极端状态:
-
超卖问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
-
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
-
临界区代码块:多个线程同时访问的代码块
-
**共享数据:**临界区代码块中,多个线程共享访问的堆里面的数据
为了解决这个问题,我们需要通过加锁来保证线程安全。
4.2 synchronized内置锁
4.2.1.临界区资源(共同数据)
表示一种可以被多个线程使用的公共资源或共享数据,但是每一次只能有一个线程使用它。一旦临界区资源被占用,想使用该资源的其他线程必须等待。
4.2.2.临界区代码段(Critical Section)
是每个线程中访问临界资源的那段代码,多个线程必须互斥地对临界区资源进行访问。线程进入临界区代码段之前,必须在进入区申请资源,申请成功之后进行临界区代码段,执行完成之后释放资源。
临界区代码段(Critical Section)的进入和退出。
在 Hotspot 虚拟机中, Monitor 是由 C++类 ObjectMonitor 实现, ObjectMonitor 类定义在ObjectMonitor.hpp 文件中,其构造器代码大致如下:
/Monitor 结构体
ObjectMonitor::ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
//线程的重入次数
_recursions = 0;
_object = NULL;
//标识拥有该 monitor 的线程
_owner = NULL;
//等待线程组成的双向循环链表
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
//多线程竞争锁进入时的单向链表
cxq = NULL ;
FreeNext = NULL ;
//_owner 从该双向循环链表中唤醒线程节点
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
Monitor:
“Monitor”(监视器)是一种同步机制,用于控制多个线程之间的并发访问。Java中的每个对象都与一个内置的监视器相关联,也称为锁。通过使用监视器,可以确保在任何时候只有一个线程可以访问对象的同步代码块或方法,从而避免竞争条件和数据不一致性。
4.2.3 synchronized语法
1.代码块
synchronized (共享对象(plus,ticket)) {//临界区代码块
//对共对象的访问(plus)(ticket)
}
如多窗口售票:
synchronized (ticket) {
if (ticket.getCount() <= 0) {
System.out.println("票卖完了");
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " 卖了第" + ticket.sellTicket() + "张票");
}
自增1亿次案例:
synchronized (plus)//加锁 保证线程安全
{ CountDownLatch latch = new CountDownLatch(100000000);
for (int i = 0; i <100000000 ; i++)
{
latch.countDown();//-1
plus.selfPlus();
}
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
2.实例方法上锁
相当于synchronized(this)
public class SynDemo1 {
public static void main(String[] args) {
Car a = new Car();
Thread t1 = new Thread1(a);
Thread t2 = new Thread2(a);
t1.start();
t2.start();
}
}
class Thread1 extends Thread {
private Car a;
public Thread1(Car a) {
this.a = a;
}
public void run() {
a.fun1();
}
}
class Thread2 extends Thread {
private Car a;
public Thread2(Car a) {
this.a = a;
}
public void run() {
a.fun2();
}
}
class Car {
public synchronized void fun1() {
//synchronized (this) {
System.out.println("开始打蜡");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打蜡结束");
//}
}
public void fun2() {
synchronized (this) {
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始抛光");
System.out.println("抛光结束");
}
}
}
3.静态方法加锁
相当于syncronized(class)
public class SynDemo1 {
public static void main(String[] args) {
Car a = new Car();
Thread t1 = new Thread1(a);
Thread t2 = new Thread2(a);
t1.start();
t2.start();
}
}
class Thread1 extends Thread {
private Car a;
public Thread1(Car a) {
this.a = a;
}
public void run() {
a.fun1();
}
}
class Thread2 extends Thread {
private Car a;
public Thread2(Car a) {
this.a = a;
}
public void run() {
a.fun2();
}
}
class Car {
public synchronized static void fun1() {
System.out.println("开始打蜡");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打蜡结束");
}
public static void fun2() {
synchronized (Car.class) {
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始抛光");
System.out.println("抛光结束");
}
}
}
4.2.4 必须有相同的锁
当多个线程需要协调访问共享资源时,必须使用相同的锁来确保线程之间的同步。
public class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (Integer.class) {
System.out.println("1");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("2");
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (String.class) {
System.out.println("a");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("b");
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Test1 {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.start();
t2.start();
}
}
运行结果:当t1线程还在运行时,t2线程也在运行。
4.2.5 死锁(要避免产生死锁)
synchronized嵌套时,比如张三在A电话亭想去B电话亭,李四在B电话亭想去A电话亭,这是会发生死锁
a线程锁定一个资源,同时想获取b线程的资源,b线程锁定一个资源,同时想获取a线程的资源。
public class DeadLock {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (String.class) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (Integer.class) {
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (Integer.class) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (String.class) {
}
}
}
});
t1.start();
t2.start();
}
}
当t1获得了String锁,t2获得了Integer锁后程序发生死锁,t1无法获取到Integer锁导致t1无法执行完成,String锁无法释放出来,t2情况相同,进而导致程序卡死。
4.2.6 CountDownLatch(和join类似原理不同)
juc的公共锁
● 构造函数new CountDownLatch(perms 4)授权数量
● countDown():授权数量-1
● await():执行后线程进入阻塞,知道授权数量=0
案例:count++ 1亿次
Plus类:
class Plus {
private int count = 0;
public Plus() {
}
public Plus(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
void selfPlus() {
count++;
}
}
Plustask类:
class Plustask implements Runnable {
private Plus plus;
CountDownLatch countDownLatch;
public Plustask(Plus plus,CountDownLatch countDownLatch) {
this.plus = plus;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
synchronized (plus) {
for (int i = 0; i < 100000000; i++) {
plus.selfPlus();
}
countDownLatch.countDown();
}
}
}
CountDownLatchDemo类:
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
Plus plus = new Plus();
CountDownLatch countDownLatch = new CountDownLatch(4);
Plustask plustask = new Plustask(plus,countDownLatch);
Thread t1 = new Thread(plustask);
Thread t2 = new Thread(plustask);
Thread t3 = new Thread(plustask);
Thread t4 = new Thread(plustask);
t1.start();
t2.start();
t3.start();
t4.start();
countDownLatch.await();
System.out.println("总和为:"+plus.getCount());
}
}
运行结果: