多线程是提升程序性能非常重要的一种方式
使用多线程可以让程序充分利用CPU资源
优点
- 系统资源得到更合理的利用
- 程序设计更加简洁
- 程序响应更快、运行效率更高
缺点 - 需要更多的内存空间来支持多线程
- 多线程并发访问的情况可能会影响数据的准确性
- 数据被多线程共享,可能会出现死锁
进程和线程
什么是进程:就是计算正在运行的一个独立的应用程序。
进程是一个动态的概念,当我们启动某个应用的时候,进程就产生了,当我们关闭应用的时候,进程就结束了,进程的生命周期就是我们使用该软件的整个过程
什么是线程?
线程是组成进程的基本单位,可以完成特定的功能,一个进程是一个或多个线程组成的
应用程序时静态的,进程和线程时动态的,有创建有销毁,存在是暂时的,不是永久的。
进程和线程的区别
进程在运行时拥有独立的内存空间,每个进程所占用的内存空间是独立的,线程是共享内存空间的,但是每个线程的执行都是独立,单独的线程是无法执行的,由进程来控制多个线程的执行。
多线程
多线程是指一个进程中,多个线程同时执行,
系统会为每个线程分配CPU资源,在某个具体的时间段内CPU资源会被一个线程占用,在不同的时间段内由不同线程来占用CPU资源,所以多个线程是在交替执行
- 注意
不能通过run方法来调用线程的任务,因run方法调用相当于普通对象的执行,并不会去抢占CPU资源
只有通过start方法才能开启线程,从而去抢占CPU资源,当某个线程抢占到CPU资源后,会自动调用run方法
线程和任务
线程是去抢占CPU资源
任务是具体执行业务逻辑
线程内部会包含一个任务
线程启动
当抢到资源后
任务开始执行
- 继承Thread类和实现Runnable接口区别
继承Thread方式,直接在类中重写Run方法,使用的时候直接实例化, 因Thread内部存在Runnable
实现Runnanble接口方法,在实现类中重写Run方法,使用时需先创建Thread对象
并将MyRunnable注入到Thread中
Thread.start
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
线程状态(5种)
在特定的情况下,线程可以在不同的状态之间切换
- 创建状态:实例化一个新的线程对象,还未启动
- 就绪状态:创建好的线程对象调用Start方法完成启动,进入线程池等待抢占CPU资源
- 运行状态:线程对象获取了CPU资源,在一定时间内执行任务
- 阻塞状态:正在运行的线程暂停执行任务,释放所占用的CPU资源,并在解除阻塞状态之后也不能直接回到运行状态,而是重新回到就绪状态,等待获取CPU资源
- 终止状态:线程执行完成之后或因异常导致线程终止
-
线程调度
- 线程休眠
让当前线程暂停执行,从运行状态进入阻塞状态,将CPU资源让给其他线程的调度方式,通过sleep()来实现。
sleep(long millis) 单位毫秒
sleep是静态本地方法,可以通过类调用,也可也通过对象调用,方法定义抛出InterruptedException,InterruptedException继承Exception,外部调用时必须手动处理异常。
线程合并
合并是指将指定的某个线程加入到当前线程种,合并为一个线程,由两个线程交替执行变成一个线程中的两个线程顺序执行。
通过join方法来实现合并
线程A和线程B,线程A执行到某个时间点的时候调用线程B的join方法,则表示从当前时间点开始CPU资源被线程B独占,线程A进入阻塞状态,知道线程B执行完成,线程甲进入就绪状态,等待获取CPU资源进入运行状态
join方法重载,join()表示B线程执行完成之后才能执行其他线程
join(long millis)表示B线程执行millis毫秒之后,无论是否执行完毕,其他线程都可以和它争夺CPU资源
线程礼让
线程礼让是指某特定时间点,让线程暂停抢占CPU资源行为,运行状态/就绪状态----》阻塞状态,将CPU资源让给其他线程来使用
若线程A和线程B在交替执行,谋时间点线程A做出了礼让,则在这个时间节点线程B拥有CPU资源执行业务逻辑,但不代表线程A一直暂停执行
线程A只是在特定的时间节点礼让,过了时间节点,线程A再次进入就绪状态,和线程B争夺CPU资源
yield
public class YieldThread1 extends Thread{
public void run(){
for (int i = 0; i < 10; i++) {
if( i == 5){
yield();
}
System.out.println(Thread.currentThread().getName()+"-----"+i);
}
}
}
public class YieldThread2 extends Thread {
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
public class Test {
public static void main(String[] args) {
YieldThread1 thread1 = new YieldThread1();
thread1.setName("线程1");
YieldThread2 thread2 = new YieldThread2();
thread2.setName("线程2");
thread1.start();
thread2.start();
}
}
线程中断
- 线程执行完毕自动停止
- 线程执行过程中遇到错误抛出异常并停止
- 线程执行过程中根据需求手动停止
public void stop()
public void interrupt()
public boolean isInterrupted()
interrupt是一个实例方法,当一个线程对象调用该方法时,表示中断当前线程对象
每个线程对象都是通过一个标志位来判断当前是否为中断状态
isInterrupted就是用来获取当前线程对象的标志位: true表示清除了标志位,当前线程已经终端;false表示没有清除标志位,当前对象没有中断
当一个线程对象处于不同的状态时,中断机制也不同的。
public class Test {
public static void main(String[] args) {
Thread thread = new Thread();
//getState 当前线程的状态
System.out.println(thread.getState());
thread.interrupt();
System.out.println(thread.isInterrupted());
}
}
NEW当前线程为创建状态,false表示当前线程并未中断,因为当前线程没有启动,就不存在中断,故不需要清除标志位
public class Test2 {
public static void main(String[] args) {
// MyRunnable runnable = new MyRunnable();
// Thread thread = new Thread(runnable);
// thread.start();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i+"----main-");
}
}
});
thread.start();
System.out.println(thread.getState());
thread.interrupt();
System.out.println(thread.isInterrupted());
System.out.println(thread.getState());
}
}
线程同步
多个线程同时操作同一个共享数据时,可能会导致数据不准确的问题
通过synchronized关键字修饰方法实现线程同步,每个java对象都有一个内置锁,内置锁会保护使用synchronized关键字的方法,要调用该方法就必须先获得锁,否则处于阻塞状态。
非线程同步
public class Account implements Runnable {
//为了获取数据一致 所以用静态修饰
private static int num;
@Override
public void run() {
try {
Thread.currentThread().sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num++;
System.out.println(Thread.currentThread().getName()+"是当前第"+num+"位访客");
}
}
线程同步
public class Account implements Runnable {
//为了获取数据一致 所以用静态修饰
private static int num;
@Override
public synchronized void run() {
try {
Thread.currentThread().sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num++;
System.out.println(Thread.currentThread().getName()+"是当前第"+num+"位访客");
}
}
public class Test {
public static void main(String[] args) {
Account account = new Account();
Thread t1 = new Thread(account,"李四");
Thread t2 = new Thread(account,"张三");
t1.start();
t2.start();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(account,"线程"+i);
thread.start();
}
}
}
synchronized修饰非静态方法
public class SynchronizedTest2 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
SynchronizedTest2 synchronizedTest2 = new SynchronizedTest2();
synchronizedTest2.test();
}
});
thread.start();
}
}
public synchronized void test(){
System.out.println("start...");
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
给实例方法(非静态方法)添加synchronized关键字并不能实现线程同步。
synchronized修饰静态方法
public class SynchronizedTest {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
SynchronizedTest.test();
}
});
thread.start();
}
}
public synchronized static void test(){
System.out.println("start....");
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end....");
}
}
线程同步本质是锁定多个线程所共享的资源
synchronized可以修饰代码块,会为代码块加上内置锁,从而实现同步。
package com.xmm.test;
/**
* @Description TODO
* @Author Xm
* @Date 2022/7/16 15:18
*/
public class SynchronizedTest3 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
SynchronizedTest3.test();
}
});
thread.start();
}
}
public static void test(){
synchronized (SynchronizedTest3.class){ System.out.println("...start");
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");}
}
}
如何判断线程同步或不同
找到关键点:锁定的资源在内存中是一份还是多份 一份需要排队是同步 多份 不是线程同步
无论锁定方法还是锁定对象,锁定类,只需要分析这个方法、对象、类在内存中有几份即可
对象一般是多份
类一定是一份
方法看是静态还是非静态
静态一定是一份
非静态一般是多份
线程安全的单例模式
单例模式是一种常见的设计模式
核心思想是一个类只有一个实例对象
public class SingletonDemo {
private static SingletonDemo singletonDemo;
private SingletonDemo() {
System.out.println("创建了SingletonDemo..");
}
public synchronized static SingletonDemo getInstance() {
//判断对象是否已经被创建
if (singletonDemo == null) {
singletonDemo = new SingletonDemo();
}
return singletonDemo;
}
}
public class Test2 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
SingletonDemo singletonDemo = SingletonDemo.getInstance();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
SingletonDemo singletonDemo = SingletonDemo.getInstance();
}
}).start();
}
}
双重检测 synchronized修饰代码块
- 线程同步是为了实现线程安全、如果只创建一个对象,那么线程就是安全的
- 如果synchronized锁定的是多个线程共享的数据(同一个对象),那么线程就是安全的。
public class SingletonDemo {
private volatile static SingletonDemo singletonDemo;
private SingletonDemo() {
System.out.println("创建了SingletonDemo..");
}
public static SingletonDemo getInstance() {
//判断对象是否已经被创建
if(singletonDemo == null){
synchronized(SingletonDemo.class){
if (singletonDemo == null) {
singletonDemo = new SingletonDemo();
}
}
return singletonDemo;
}
}
volatile的作用是可以使内存中的数据对线程可见
主内存对线程是不可见的,添加volatile关键字之后,主内存对线程可见
synchronized关键字实现线程同步,让在访问同一个资源的多个线程排队去完成业务,避免出现数据错乱的情况。
死锁
- 前提:一个线程完成业务需要同时访问两个资源
- 死锁: 多个线程同时完成业务,出现争抢资源的情况
资源类 是供线程调用的
public class DeadLockRunnable implements Runnable {
//编号
public int num;
//资源
private static Chopsticks chopsticks1 = new Chopsticks();
private static Chopsticks chopsticks2 = new Chopsticks();
/**、
* num = 1 拿到 chopsticks1 , 等待 chopsticks2
* num = 2 拿到 chopsticks2 , 等待 chopsticks1
*/
@Override
public void run() {
if (num == 1) {
System.out.println(Thread.currentThread().getName() + "拿到了chopsticks1,等待获取chopsticks2");
synchronized (chopsticks1) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (chopsticks2) {
System.out.println(Thread.currentThread().getName() + "用餐完毕");
}
}
}
if (num == 2) {
System.out.println(Thread.currentThread().getName() + "拿到chopsticks2,等待获取chopsticks1");
synchronized (chopsticks2) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (chopsticks1) {
System.out.println(Thread.currentThread().getName() + "用餐完毕");
}
}
}
}
}
public class DeadLockTest {
public static void main(String[] args) {
DeadLockRunnable deadLockRunnable1 = new DeadLockRunnable();
deadLockRunnable1.num = 1;
DeadLockRunnable deadLockRunnable2 = new DeadLockRunnable();
deadLockRunnable2.num = 2;
new Thread(deadLockRunnable1,"张三").start();
new Thread(deadLockRunnable2,"李四").start();
}
}
如何破解死锁?
不让多线程并发访问
public class DeadLockTest {
public static void main(String[] args) {
DeadLockRunnable deadLockRunnable1 = new DeadLockRunnable();
deadLockRunnable1.num = 1;
DeadLockRunnable deadLockRunnable2 = new DeadLockRunnable();
deadLockRunnable2.num = 2;
new Thread(deadLockRunnable1,"张三").start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(deadLockRunnable2,"李四").start();
}
}
Lambda表达式简化代码开发
public static void main(String[] args) {
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println("==========Runnable");
}
}).start();
}
Lock
JUC
java.util.concurrent
Lock是一个接口,用来实现线程同步,功能与synchronized一样
Lock使用频率最高的实现类是ReentrantLock(重入锁),可也重复上锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
Account account = new Account();
new Thread(account,"A").start();
new Thread(account,"B").start();
}
}
class Account implements Runnable{
private static int num;
private Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
num++;
System.out.println(Thread.currentThread().getName()+"当前的第"+num+"位访客");
lock.unlock();
}
}
实现资源和Runnable接口的解耦合操作
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test1 {
public static void main(String[] args) {
Acount acount = new Acount();
new Thread(()->{
acount.count();
},"A").start();
new Thread(()->{
acount.count();
},"B").start();
}
}
class Acount{
private int num ;
private Lock lock = new ReentrantLock();
public void count(){
lock.lock();
num++;
System.out.println(Thread.currentThread().getName()+"第几位"+num+"访客");
lock.unlock();
}
}
重入锁
JUC
Java并发编程工具包,Java官方提供的一套专门用来处理并发编程的工具集合(接口+类)
并发:单核CPU,多个线程”同时“运行,实际是交替执行,只不过速度太快,看起来同时执行
并行:多核CPU,真正的多个线程同时运行。
重入锁是JUC使用频率非常高的一个类
ReentrantLock
ReentrantLock就是对Synchronized的升级,目的也是为了实现线程同步
- ReentrantLock是一个类,Synchronized是一个关键字
- ReentranLock是JDK实现,Synchronized是JVM实现
- synchronized可以自动释放锁,ReentrantLock需要手动释放。
公平锁和非公平锁的区别
公平锁:线程同步时,多个线程排队,依次执行
非公平锁:线程同步时,可以插队
Tips
class Nums{
private static Integer num = 0;
private static Integer id = 0;
public void count(){
synchronized (num){
num++;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"是当前第"+num+"几位访客");
}
}
}
如果锁定num则不能同步,锁定id可以同步
分析如下
synchronized必须锁定唯一的元素才可以实现同步
num的值每次都在变,所以num所指的引用一直在变,所以不是唯一的元素,肯定无法实现同步。
id的值永远不变,所以是唯一的元素,可以实现同步。
Reentranlock
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class Test2 {
public static void main(String[] args) {
Account2 account= new Account2();
new Thread(()->{
account.count();
},"A").start();
new Thread(()->{
account.count();
},"B").start();
}
}
class Account2{
private static int num;
private ReentrantLock reentrantLock = new ReentrantLock();
public void count(){
reentrantLock.lock();
num++;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"是当前的第"+num+"位访客");
reentrantLock.unlock();
}
}
- Lock上锁和解锁都需要开发者手动完成
- 可以重复上锁
ReentrantLock除了可以重入之外,还有一个可以中断的特点,可中断是指某个线程在等待获取锁的过程中可以主动终止线程。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class Test4 {
public static void main(String[] args) {
StopLock stopLock = new StopLock();
Thread t1 = new Thread(()->{
stopLock.service();
},"A");
Thread t2 = new Thread(()->{
stopLock.service();
},"B");
t1.start();
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
t2.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class StopLock{
private ReentrantLock reentrantLock = new ReentrantLock();
public void service(){
try{
reentrantLock.lockInterruptibly();
System.out.println(Thread.currentThread().getName()+" get lock");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}catch (InterruptedException e1){
e1.printStackTrace();
}finally {
reentrantLock.unlock();
}
}
}
生产者消费者模式
在一个生产环境中,生产者和消费者在同一时间段内共享同一块缓冲区,生产者负责向缓冲区添加数据,消费者负责取出数据。
- 容器类
public class Container {
public Humburger[] array = new Humburger[6];
public int index = 0;
/**
* 向容器内添加汉堡
*/
public synchronized void push(Humburger humburger){
while(index == array.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}//
this.notify();
array[index] = humburger;
index++;
System.out.println("生产了一个汉堡"+humburger);
}
/**
* 从容器中取汉堡
*/
public synchronized Humburger pop(){
while(index == 0){
//当前线程暂停
//正在访问当前资源的线程暂停
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//唤醒之前暂停的线程
this.notify();
index--;
System.out.println("消费了一个汉堡"+array[index]);
return array[index];
}
}
- 生产者
public class Producer {
private Container container = null;
public Producer(Container container){
this.container = container;
}
public void product(){
for (int i = 0; i < 30; i++) {
Humburger humburger = new Humburger(i);
this.container.push(humburger);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 消费者
//消费者
public class Consumer {
private Container container;
public Consumer(Container container){
this.container = container;
}
public void consum(){
for (int i = 0; i < 30; i++) {
this.container.pop();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 测试类
public class Test {
public static void main(String[] args) {
Container container = new Container();
Producer producer = new Producer(container);
Consumer consumer = new Consumer(container);
new Thread(()->{
producer.product();
}).start();
new Thread(()->{
consumer.consum();
}).start();
}
}
汉堡类
public class Humburger {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Humburger{" +
"id=" + id +
'}';
}
public Humburger(int id) {
this.id = id;
}
}
多线程并发卖票
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class Ticket {
//剩余球票
private int surpluCount = 15;
//已售出球票
private int outCount = 0;
private ReentrantLock reentrantLock = new ReentrantLock();
public void sale (){
while(surpluCount > 0 ){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票卖完了
if (surpluCount == 0){
return;
}
reentrantLock.lock();
surpluCount--;
outCount++;
if (surpluCount == 0){
System.out.println(Thread.currentThread().getName()+"售出"+outCount+"张票,球票已售罄");
}else {
System.out.println(Thread.currentThread().getName()+"售出"+outCount+"张票,还剩 "+surpluCount+"张");
}
reentrantLock.unlock();
}
}
}
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
ticket.sale();
},"A").start();
new Thread(()->{
ticket.sale();
},"B").start();
new Thread(()->{
ticket.sale();
},"C").start();
}
}