最近在学习多线程,记录一下
进程&线程
关系:一个进程里包含多个线程、一个进程里必定有一个线程,如果一个线程都没有则改进程就会被关闭。
区别:进程之间不会资源共享,线程之间会资源共享
生命周期
在程序开发中,将一个对象从被实例化完成(已经为这个对象分配好了内存空间),到这个对象使用结束,并销毁,将这样的过程称为对象的生命周期。
一个线程被实例化完成,到这个线程销毁,中间的过程被称为线程的生命周期。
线程的状态
新生态(New):一个线程对象被实例化完成,但还没有做任何操作。
就绪态(Ready):一个线程已经被开启,已经开始去争抢CPU时间片(谁抢到谁去执行)。
运行态(Run):一个线程抢到了CPU时间片,开始执行这个线程中的逻辑
阻塞态(Interrupt):一个线程在运行过程中,受到某些操作的影响,放弃了已经获取到的CPU时间片,并且不在去参与CPU时间片的争抢,此时线程处于挂起状态(暂停状态)
死亡态(Dead):一个线程对象需要被销毁
并发例子
public class ThreadCreate {
public static void main(String[] args) {
//1.继承Thread类,做一个线程子类(自定义的线程类)
MyThread mt = new MyThread();
//需要调用start方法,使线程启动
//start方法会开启一个新的线程,来执行run方法中的逻辑(并发)
//如果直接调用run方法,此时不会开辟新的线程,则线程mt不会进入就绪状态,run方法里的逻辑还是在当前调用run方法的线程里执行(不会并发)
mt.start();
//2.实现Runnable接口,做一个线程子类(自定义线程)
//直接new Runnable
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i=0; i<5; i++){
System.out.println("子线程2:"+i);
}
}
};
Thread thread = new Thread(runnable);
thread.start();
//使用Lambda表达式实现Runnable接口
Runnable lambdaRunnable = () -> {
for (int i=0; i<5; i++){
System.out.println("子线程3:"+i);
}
};
Thread lambdaThread = new Thread(lambdaRunnable);
lambdaThread.start();
System.out.println("主线程方法调用结束");
}
/**
* 自定义线程类
*/
class MyThread extends Thread{
@Override
public void run() {
for (int i=0; i<5; i++){
System.out.println("子线程1:"+i);
}
}
}
输出结果:
子线程1:0
子线程1:1
子线程1:2
子线程1:3
子线程1:4
子线程2:0
子线程2:1
子线程2:2
子线程2:3
子线程2:4
主线程方法调用结束
子线程3:0
子线程3:1
子线程3:2
子线程3:3
子线程3:4
实现线程方式的优缺点
继承Thread
优点:代码可读性高
缺点:Java单继承,如果需要变动类里的逻辑需要并发执行的,此时你需要把它做成一个线程类的话,那么这个类将不能在继承其它类了,所以继承可能会对原有的继承结构造成影响。
实现Runnable接口
优点:扩展性高,不会出现继承Thread实现的类缺点。
缺点:代码可读性差
线程的优先级(Priority)
设置线程的优先级,只是修改这个线程抢到CPU时间片的概率。
并不是优先级高的线程一定能抢到CPU时间片。
优先级的设置,是一个整数[0,10]的整数,默认值是5
设置优先级必须要放到这个线程开始执行(start)之前
public class ThreadPriority {
public static void main(String[] args) {
Runnable runnable = ()->{
for (int i=0; i<5; i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
};
//实例化两个线程
Thread thread = new Thread(runnable,"thread-1");
Thread thread1 = new Thread(runnable,"thread-2");
//设置优先级
thread.setPriority(10);
thread1.setPriority(1);
//启动线程
thread.start();
thread1.start();
}
}
输出:
thread-1:0
thread-2:0
thread-2:1
thread-2:2
thread-2:3
thread-2:4
thread-1:1
thread-1:2
thread-1:3
thread-1:4
线程的礼让(Yield)
线程礼让指的是当前运行状态的线程释放自己的CPU资源,由运行状态到就绪状态(Yield->Ready)重新抢CPU时间片。
假如有两个线程A,B在争抢CPU时间片,A线程抢到后选择礼让回到Ready状态,则B现在不是一定就能抢到CPU时间片,有可能还是A抢到CPU时间片
public class ThreadYield {
public static void main(String[] args) {
Runnable runnable = () -> {
for (int i=0; i<5; i++){
System.out.println(Thread.currentThread().getName()+":"+i);
if(i == 3){
//线程礼让
Thread.yield();
}
}
};
//实例化两个线程
Thread thread = new Thread(runnable,"thread-1");
Thread thread1 = new Thread(runnable,"thread-2");
//启动线程
thread.start();
thread1.start();
}
}
输出:
thread-1:0
thread-1:1
thread-1:2
thread-1:3
thread-2:0
thread-2:1
thread-2:2
thread-2:3
thread-1:4
thread-2:4
临界资源问题
某一个线程在对临界资源进行访问时,还没来得及完全修改临界资源的值,临界资源就被其他线程拿去访问,导致多个线程访问同一资源。直观表现为打印结果顺序混乱
模拟售票:有2个售票员卖10张
public class ThreadConfilct {
public static void main(String[] args) {
Runnable runnable = () -> {
while (TicketCenter.restCount > 0){
System.out.println(Thread.currentThread().getName()+ "卖了一张票,剩余:" + --TicketCenter.restCount + "张");
}
};
Thread t1 = new Thread(runnable,"thread-1");
Thread t2 = new Thread(runnable,"thread-2");
t1.start();
t2.start();
}
}
class TicketCenter{
//剩余票的数量
public static Integer restCount = 10;
}
输出:
thread-2卖了一张票,剩余:9张
thread-2卖了一张票,剩余:8张
thread-2卖了一张票,剩余:7张
thread-2卖了一张票,剩余:6张
thread-2卖了一张票,剩余:5张
thread-2卖了一张票,剩余:4张
thread-1卖了一张票,剩余:9张
thread-2卖了一张票,剩余:3张
thread-1卖了一张票,剩余:2张
thread-2卖了一张票,剩余:1张
thread-1卖了一张票,剩余:0张
临界资源问题解决
使用同步锁(synchronized)
synchronized概念:临界资源加锁,多个线程访问一个临界资源时,一定有一个先后顺序。
例如:A线程访问到临界资源时,在外边上一把锁,此时后面的线程访问临界资源时会发现临界资源外边有锁,就会进入等待。
一直等待到A线程操作临界资源结束解锁。后面线程同理。
public class ThreadConfilct {
public static void main(String[] args) {
Runnable runnable = () -> {
while (TicketCenter.restCount > 0){
synchronized (ThreadConfilct.class){
if (TicketCenter.restCount <= 0){
return;
}
System.out.println(Thread.currentThread().getName()+ "卖了一张票,剩余:" + --TicketCenter.restCount + "张");
}
}
};
Thread t1 = new Thread(runnable,"thread-1");
Thread t2 = new Thread(runnable,"thread-2");
t1.start();
t2.start()
}
}
class TicketCenter{
//剩余票的数量
public static Integer restCount = 10;
}
synchronized的使用
同步代码段
语法:synchronized(“对象锁”,“类锁”){};
对象锁:任意对象都可以当做锁使用
例子:synchronized(""){};
类锁:当前类.class
例子:synchronized(Object.class){};
注意:“需要保证多个线程看到的锁都是同一把锁”
Runnable runnable = () -> {
while (TicketCenter.restCount > 0){
//同步代码段
//使用类锁
synchronized (ThreadConfilct.class){
if (TicketCenter.restCount <= 0){
return;
}
System.out.println(Thread.currentThread().getName()+ "卖了一张票,剩余:" + --TicketCenter.restCount + "张");
}
}
};
同步方法
使用关键字synchronized修饰的方法叫做同步方法。
如果某一个方法里的逻辑都需要同步,那么可以做成同步方法。
public class ThreadConfilct {
public static void main(String[] args) {
Runnable runnable = () -> {
while (TicketCenter.restCount > 0){
soldTicket();
}
};
Thread t1 = new Thread(runnable,"thread-1");
Thread t2 = new Thread(runnable,"thread-2");
t1.start();
t2.start();
}
/**
* 同步方法
* 静态方法:同步锁就是"类锁" 当前类.class
* 非静态方法:同步锁就是"this"
*/
private synchronized static void soldTicket(){
if (TicketCenter.restCount <= 0){
return;
}
System.out.println(Thread.currentThread().getName()+ "卖了一张票,剩余:" + --TicketCenter.restCount + "张");
}
}
ReentrantLock对象锁
//实例化对象锁
ReentrantLock reentrantLock = new ReentrantLock();
//对临界资源加锁
reentrantLock.lock();
//代码段........
//对临界资源解锁
reentrantLock.unlock();
//实例化锁对象
ReentrantLock reentrantLock = new ReentrantLock();
Runnable runnable = () -> {
while (TicketCenter.restCount > 0){
//对临界资源加锁
reentrantLock.lock();
if (TicketCenter.restCount<=0){
return;
}
System.out.println(Thread.currentThread().getName()+ "卖了一张票,剩余:" + --TicketCenter.restCount + "张");
//对临界资源解锁
reentrantLock.unlock();
}
};
Thread t1 = new Thread(runnable,"thread-1");
Thread t2 = new Thread(runnable,"thread-2");
t1.start();
t2.start();
死锁
多个线程彼此持有对方所需要的锁对象,而不释放自己的锁。
例如:A,B两个线程,A线程持有了A锁,需要等待B锁,此时B锁被B线程所持有,而B线程在等待A锁,这样一来就是A线程持有A锁等待B线程释放自己需要的锁标记,
B线程持有B锁等待A线程释放自己需要的锁标记。从而导致A,B同时被锁住,它们都在等待对方释放自己所需要的一个锁标记。
public static void main(String[] args) {
//死锁:多个线程彼此持有对方所需要的锁对象,而不释放自己的锁
Runnable runnable1 = () -> {
synchronized ("A"){
System.out.println("A线程持有了A锁,等待B锁释放");
synchronized ("B"){
System.out.println("A线同时持有了A锁和B锁");
}
}
};
Runnable runnable2 = () -> {
synchronized ("B"){
System.out.println("B线程持有了B锁,等待A锁释放");
synchronized ("A"){
System.out.println("B锁同时持有了A锁和B锁");
}
}
};
Thread t1 = new Thread(runnable1);
Thread t2 = new Thread(runnable2);
t1.start();
t2.start();
}
输出:
A线程持有了A锁,等待B锁释放
B线程持有了B锁,等待A锁释放
解决死锁办法:wait、notify、notifyAll
wait():等待,该方法是Object类中的一个方法,当前的线程释放自己的锁标记,并且让出CPU资源,使当前的线程进入等待队列中。
notify():通知,该方法是Object类中的一个方法,唤醒等待队列中的一个线程,使这个线程进入锁池。(“CPU随机唤醒一个线程”)。
notifyAll():通知,该方法是Object类中的一个方法,唤醒等待队列中的所有线程,使这些线程进入锁池。
public static void main(String[] args) {
//死锁:多个线程彼此持有对方所需要的锁对象,而不释放自己的锁
Runnable runnable1 = () -> {
synchronized ("A"){
System.out.println("A线程持有了A锁,等待B锁释放");
try {
"A".wait();
} catch (InterruptedException e) {
System.out.println("A锁释放进入等待队列异常");
e.printStackTrace();
}
synchronized ("B"){
System.out.println("A线同时持有了A锁和B锁");
}
}
};
Runnable runnable2 = () -> {
synchronized ("B"){
System.out.println("B线程持有了B锁,等待A锁释放");
synchronized ("A"){
System.out.println("B锁同时持有了A锁和B锁");
"A".notify();
//"A".notifyAll();
}
}
};
Thread t1 = new Thread(runnable1);
Thread t2 = new Thread(runnable2);
t1.start();
t2.start();
}
输出:
A线程持有了A锁,等待B锁释放
B线程持有了B锁,等待A锁释放
B锁同时持有了A锁和B锁
A线同时持有了A锁和B锁