Java多线程学习笔记

最近在学习多线程,记录一下

进程&线程

关系:一个进程里包含多个线程、一个进程里必定有一个线程,如果一个线程都没有则改进程就会被关闭。
区别:进程之间不会资源共享,线程之间会资源共享

生命周期

在程序开发中,将一个对象从被实例化完成(已经为这个对象分配好了内存空间),到这个对象使用结束,并销毁,将这样的过程称为对象的生命周期。
一个线程被实例化完成,到这个线程销毁,中间的过程被称为线程的生命周期。

线程的状态

新生态(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个售票员卖10public 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锁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值