多线程&JUC

多线程

多线程的两个概念

并发:在同一个cpu内多个线程同时发生

并行:在多个cpu内多个线程同时进行

多线程的三种实现方式

1.继承Thread

public class MyThread extends Thread{


    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}

测试代码

    @Test
    public void test01(){
        MyThread myThread1 = new MyThread();
        myThread1.setName("线程1");


        MyThread myThread2 = new MyThread();
        myThread2.setName("线程2");

        myThread1.start();
        myThread2.start();
    }

2.实现Runnable接口

public class MyRun implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

测试代码

    @Test
    public void test02(){
        MyRun myRun = new MyRun();

        Thread th1 = new Thread(myRun);
        Thread th2 = new Thread(myRun);

        th1.setName("线程1");
        th2.setName("线程2");

        th1.start();
        th2.start();
    }

3.利用Callable接口和Future接口

//Callable中的泛型指定线程要返回的结果
public class MyCall implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Integer sum=0;
        for (int i = 1; i <= 100; i++) {
            sum+=i;
        }
        return sum;
    }
}

 测试代码

    @Test
    public void test03() throws ExecutionException, InterruptedException {
        //创建MyCallable对象
        MyCall myCall1 = new MyCall();

        //创建FutureTask(Future接口的实现类)对象,用于接收线程执行完成后返回的结果
        FutureTask<Integer> futureTask1 = new FutureTask<Integer>(myCall1);

        //创建线程,开启线程
        Thread th1 = new Thread(futureTask1);
        th1.start();
        //获取返回值
        System.out.println("result:"+futureTask1.get());
    }

 总结

 实现Callable接口可以返回线程执行的结果

多线程中常用的成员方法 

线程的优先级

线程的优先级默认是5,最大是10,最小是1。(10表示优先级最高)

    @Test
    public void test04(){
        MyThread myThread1 = new MyThread("线程1");
        MyThread myThread2 = new MyThread("线程2");
        System.out.println(myThread1.getPriority()+"-----"+myThread2.getPriority());//默认优先级

        //设置优先级
        myThread1.setPriority(1);
        myThread2.setPriority(10);

        myThread1.start();
        myThread2.start();
    }

守护线程

测试代码及结果

应用场景

礼让线程

线程1和线程2每进入一次循环打印输出都都进行礼让,使得打印结果相较之前更加均匀了

插队线程

结果为线程1执行完毕后main线程才能执行并打印输出

线程的生命周期

同步代码块

 引用同步代码块之前

public class Ticket extends Thread{
    private static Integer num=0;//所有类创建的对象都共享100张票

    public Ticket() {
    }

    public Ticket(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (num<100){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"售出第"+(++num)+"张票");
        }
    }

    public static void main(String[] args) {
        Ticket t1 = new Ticket("窗口1");
        Ticket t2 = new Ticket("窗口2");
        Ticket t3 = new Ticket("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
 测试结果

出现重复票票数超过100张出票顺序错误的情况,这些都是由于线程抢占CPU执行权导致出票流程没有一气呵成。

引入同步代码块之后

首先(错误示范)

    @Override
    public void run() {
        synchronized (lock)  //把需要一气呵成的代码包裹起来
        {
            while (num<100){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"售出第"+(++num)+"张票");
            }
        }
    }

注意,

synchronized同步代码块不能写在while循环的外面,否则第一个抢占到CPU的线程将会把while循环执行完,会出现如下结果(一个窗口自己把票卖完了),运行结果如下

修改后
    @Override
    public void run() {
            while (num<100){
                synchronized (lock)  //把需要一气呵成的代码包裹起来
                {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"售出第"+(++num)+"张票");
                }
        }
    }

此外,在循环内部需要重新检查票数是否超过,否则在num=99时,三个线程都同时进入while循环,他们最终都会执行一次同步代码块,会出现如下结果

最终代码及测试结果
package com.example.testThread;

public class Ticket extends Thread{
    private static Integer num=0;//所有类创建的对象都共享100张票

    private static Object lock=new Object();//这个lock必须是唯一的

    public Ticket() {
    }

    public Ticket(String name) {
        super(name);
    }

    @Override
    public void run() {
            while (num<100){
                //把需要一气呵成的代码包裹起来
                //此处也可以用 Ticket.class  因为这个类的字节码被加载到内存中也是唯一的
                synchronized (Ticket.class)
                {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(num<100)  //重新判断一次
                {
                    System.out.println(Thread.currentThread().getName()+"售出第"+(++num)+"张票");
                }
                else {
                    break;
                }
                }
        }
    }

    public static void main(String[] args) {
        Ticket t1 = new Ticket("窗口1");
        Ticket t2 = new Ticket("窗口2");
        Ticket t3 = new Ticket("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

同步方法

案例(用同步方法实现上述买票案例)

只需要将用synchronized包裹住的代码块封装成一个方法.

先用synchronized确认要包裹的共享代码块。
package com.example.testThread;

public class MyTicketRunnable implements Runnable{
    //因为继承了Runnable接口,多个线程只需要调用同一个MyTicketRunnable即可完成买票流程,这里的num也不需要用static修饰了
    int num=0;

    @Override
    public void run() {
        while (num<100){
            synchronized (MyTicketRunnable.class){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(num<100){
                    System.out.println(Thread.currentThread().getName()+"正在卖第"+(++num)+"张票");
                }
                else{
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        MyTicketRunnable mtr = new MyTicketRunnable();
        Thread t1 = new Thread(mtr,"线程1");
        Thread t2 = new Thread(mtr,"线程2");
        Thread t3 = new Thread(mtr,"线程3");

        t1.start();
        t2.start();
        t3.start();
    }
}
封装成一个synchronized方法

(1.选中,ctrl+alt+m  生成方法,手动添加synchronized   

2.再删掉原来包裹它的synchronized)

package com.example.testThread;

public class MyTicketRunnable implements Runnable{
    //因为继承了Runnable接口,多个线程只需要调用同一个MyTicketRunnable即可完成买票流程,这里的num也不需要用static修饰了
    int num=0;

    @Override
    public void run() {
        while (num<100){
                if (synMethod()) break;
        }
    }

    //这里创建的是MyTicketRunnable的实例化对象mtr(动态方法),因此这里的锁是mtr(this)
    private synchronized boolean synMethod() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(num<100){
            System.out.println(Thread.currentThread().getName()+"正在卖第"+(++num)+"张票");
        }
        else{
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        MyTicketRunnable mtr = new MyTicketRunnable();
        Thread t1 = new Thread(mtr,"线程1");
        Thread t2 = new Thread(mtr,"线程2");
        Thread t3 = new Thread(mtr,"线程3");

        t1.start();
        t2.start();
        t3.start();
    }
}

 lock锁

案例

将同步代码块的案例(Ticket)中的run方法中的共享代码块用锁包裹

    static Lock lock=new ReentrantLock();//创建静态锁对象
    @Override
    public void run() {
        //线程1卖完第100张票后回到这里,判断num=100直接结束了
            while (num<100){
                //线程3会卡死在这里
                lock.lock();
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(num<100)  //重新判断一次
                {
                    System.out.println(Thread.currentThread().getName()+"售出第"+(++num)+"张票");
                }
                else {
                    break;//num=100时,线程2直接跳出去了,没有unlock
                }
                lock.unlock();
        }
    }

 执行结果

能够正常售票,但是程序没有停止。

这是因为执行到num=100时(第100张票已经被线程1卖出),假设是线程2抢到了cpu的执行权,它进行判断num=100直接break,没有unlock,这时候线程3就被卡住了,该程序永远不会结束。

正确的做法

    @Override
    public void run() {
            while (num<100){
                lock.lock();
                try {
                    Thread.sleep(10);
                    if(num<100)  //重新判断一次
                    {
                        System.out.println(Thread.currentThread().getName()+"售出第"+(++num)+"张票");
                    }
                    else {
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();//无论如何都要释放锁
                }
        }
    }

无论如何都要释放锁

死锁

死锁的定义

  • 死锁是指在执行过程中,两个或两个以上的进程(或线程)由于竞争资源或彼此通信而阻塞,导致无法继续执行的情况。

  • 如果没有外部干预,这些进程将无法向前推进。

  • 这种状态被称为系统死锁或死锁产生。

  • 这些相互等待的进程被称为死锁进程。

产生死锁的4个条件

1、互斥条件:一个共享资源同一时刻只允许有一个线程使用,如果其他线程要使用,要等它使用完成后释放。
2、请求并持有条件:一个线程已经持有了至少一个资源,又请求了新的资源,而新的资源已经被其他线程占用。
3、不可剥夺条件:线程获取到的资源在使用完之前不会被其他线程所抢占,只能由自己释放。
4、环路等待:必然存在一个环形链,T0等T1,T1等T0

*******************************************************************

案例

可能发生死锁 

续多线程.-CSDN博客

  • 21
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值