多线程
一、进程与线程
1.1、进程:
进程:是正在运行的程序
-
是系统进行资源分配和调用的独立单位
-
每个进程都有它自己的内存空间和系统资源
1.2、线程:
在一个进程内部,可以执行一个任务,也可以执行多个任务
线程:是进程中的单个执行顺序控制流,是一条执行路径
-
单线程:一个进程中如果只有一条执行路径,则称为单线程程序
-
多线程:一个进程中如果有多条执行路径,则称为多线程程序
二、多线程的实现方式
1.1、方式1:继承Thread类
-
定义一个类MyThread类
-
在MyThread类中重写run()方法
-
创建MyThread类对象
-
启动线程
两个问题:
-
为什么要重写run()方法?
因为run方法是用来封装被线程执行的方法
-
run方法和start方法的区别?
run:封装线程执行的代码,直接调用,相当于普通方法的调用
start:启动线程;然后由JVM调用此线程的run方法
1.2、Thread类中获取和设置线程名称的方法
void setName(String name):设置线程名称 String getName():返回线程名称
三、线程调度
线程有两种调度模型:
-
分时调度模型
-
抢占式调度模型
Java是抢占式
四、设置和获取线程名称
设置线程名称:myThread1.setName("线程1");
获取线程名称:myThread1.getName();
//static Thread currentThread() :返回对当前正在执行的线程对象的引用 System.out.println(Thread.currentThread().getName());
五、线程优先级
最小:1
最大:10
默认值:5
方法:
//获取线程的优先级 System.out.println(myThread1.getPriority()); //5 System.out.println(myThread2.getPriority()); //5
//设置线程优先级:max:10;min:5 myThread2.setPriority(10); myThread1.setPriority(1);
六、线程控制
//static void sleep(long millis):使当前正在执行的线程暂停指定的毫秒数
//void join():等待这个线程死亡
//void setDaemon(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机退出
七、线程的生命周期
八、多线程实现方式
8.1、方式2:实现Runnable接口
-
定义一个MyRunnable类实现接口
-
在MyRunnable中重新run方法
-
创建MyRunnable类的对象
-
创建Thread类的对象,把MyRunnable对象作为构造方法的参数
-
启动线程
8.2、多线程的实现方式有两种:
-
继承Thread类
-
实现Runnable接口
8.3、相比继承Thread类,实现Runnable接口的好处:
-
避免了Java单继承的局限性
-
适合多个相同程序的代码去处理同一资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
九、同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
-
格式:
synchronized (任意对象){
//多条语句操作共享数据的代码
// t1进来后,就会把这段代码给锁起来
if (ticket > 0){
//通过sleep方法来模拟出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
ticket--;
}
}
-
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成一把锁
-
弊端:当线程很多时,因为每个线程都会判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
十、同步方法
同步方法:就是把synchronized关键字加到方法上
-
格式:
private synchronized void sellTicket() {
}
-
同步方法的锁对象是this
同步静态方法:就是把synchronized关键字加到静态方法上
-
格式
private static synchronized void sellTicket() {
}
同步静态方法的锁对象是
-
类名.class
十一、线程安全的类
public class ThreadDemo {
public static void main(String[] args) {
//线程安全
StringBuffer sb = new StringBuffer();
//线程不安全
StringBuilder sb2 = new StringBuilder();
//以下两个被Collections替代了
//线程安全
Vector<String> v = new Vector<>();
//线程不安全
ArrayList<String> strings = new ArrayList<>();
//线程安全
Hashtable<String, String> ht = new Hashtable<>();
//线程不安全
HashMap<String, String> hm = new HashMap<>();
//static <T> list<T> synchronizedList(list<T> list):返回由指定列表支持的同步(线程安全的)列表
List<String> strings1 = Collections.synchronizedList(new ArrayList<String>());
}
}
十二、Lock锁
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法:
-
void lock();获得锁
-
void unlock();释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法
-
ReentrantLock():创建一个ReentrantLock的实例
十二、死锁
在Java多线程程序中,死锁是一种非常常见的问题。死锁是指两个或多个线程互相持有彼此需要的资源并且互相等待,从而导致程序无法继续执行下去的一种情况。
例如,如果线程 A 持有锁 1,并尝试去获得锁 2,而线程 B 此时持有锁 2,尝试去获得锁 1,那么这两个线程就会进入死锁状态。因为线程 A 需要锁 2 才能释放锁 1,而线程 B 需要锁 1 才能释放锁 2。
为了避免死锁,可以使用以下方法:
-
避免共享资源:尽量避免线程之间共享同一个资源,或使用同步机制来避免冲突。
-
加锁的顺序一致性:尽量让所有线程按照相同的顺序获取锁,例如所有线程都先获取锁1,再获取锁2。
-
超时机制:在获取资源的时候,设置超时机制,如果超过一定时间还没有获取到资源,就放弃。这样可以避免线程无限等待。
-
用 tryLock():使用可重入锁(ReentrantLock)的 tryLock() 方法,如果未能获取到锁则返回 false,避免长时间阻塞。
-
避免嵌套锁:尽可能避免使用嵌套锁,如果必须使用,则一定要确保加锁的顺序一致。