Java 多线程

进程:即正在执行的应用程序

线程:程序功能执行的最小单位(代码运行的通道)

二者关系:一个进程中包含了多个线程(至少一个)

多线程并发: 一个时间段之内,有多个线程执行。

多线程并行:(多cpu情况下) 同一时刻有多个线程同时执行。

Thread的常用方法:

  1. sleep(); 里面放入long值,使线程休眠指定毫秒数,使线程堵塞

  2. join(); 等待该线程终止 使指定线程暂时处于独占作用,直到执行完毕,使线程死亡,使其他线程堵塞

  3. yield(); 线程礼让方法,使线程大概率平均调用 不是绝对的,因为cpu是随机调用线程

  4. setPriority(); 更改线程优先级方法 (1~10)数字越大优先级越大,但是设置优先级只是将线程首先被调用的几率无限制的增大,并不是绝对优先100%,即使是优先级为1的线程也有可能首先被调用

  5. getPriority(); 查看线程优先级

  6. interrupt(); 线程中断方法,但是使用此方法不是像想像中的直接停止线程,而是相当于给这儿线程做了个标记,提示可以停止这个线程,后续使用别的方法进行线程的停止

  7. isInterrupted(); 使用此方法可以测试这个线程是否被中断

写在重写的run方法里 其他的写在Test类中

通过继承Thread类创建多线程:

  1. 创建类,在类中继承Thread类

  2. 在类中重写run方法

  3. 创建线程类对象

  4. 使用Strat()方法开启线程

创建线程类,在类中重写run方法
public class Tread1 extends Thread{

    @Override
    public void run() {

        for (int i = 0; i < 100; i++) {

            System.out.println(getName()+":"+i);

        }
    }
}
class Thread2 extends Thread{


    @Override
    public void run() {

        for (int i = 100; i > 0; i--) {

            System.out.println(getName()+";"+i);

        }

    }
}
创建线程对象,开启线程:
public static void main(String[] args) {

    //创建线程对象
    Tread1 td1 = new Tread1();
    td1.setName("海绵宝宝");
    Tread1 td2 = new Tread1();
    td2.setName("派大星");
    Thread2 td3 = new Thread2();
    td3.setName("章鱼哥");

    //开启多线程
    td1.start();
    td2.start();
    td3.start();
}
线程的执行过程:

线程的分类:
  1. 主线程:main执行时,会主动开启一条主线程通道

  2. 用户线程:其他手动创建的线程,如果未设置守护状态,都为用户线程

  3. 守护线程:如果线程创建后,设置为守护线程,则该线程会在所有用户线程结束后,自动结束

手动创建的线程都为用户线程

守护线程的创建:

使用setDaemon(); 方法创建守护线程,里面填入boolean类型的参数,填入true即为定义该线程为守护线程

  1. 创建守护线程时,先在重写的run方法中创建一个死循环

  2. 再使用setDaemon(); 把死循环所在的类的对象设置为守护线程

守护线程多用于:后台程序的运行,因为只要用户线程有没结束的,守护线程就会一直执行下去

代码示例:

public class ThreadDemo extends Thread{

    @Override
    public void run() {

        while (true){

            System.out.println(getName()+":正在执行:");

        }
    }
}
ThreadDemo td = new ThreadDemo();
td.setName("孙悟空");
//设置守护线程
td.setDaemon(true);

此时孙悟空就被设置成了守护线程,只有程序中的用户线程全部结束后,该守护线程猜自动结束

使用Runnable接口创建线程

  1. 创建类实现Runnable接口

  2. 重写run方法

  3. 创建该类的对象

  4. 创建Thread对象

  5. 开启线程

使用 Runnable 接口创建线程的好处: 方便进行多个线程间的数据共享

实现Runnable接口的类,无法直接开启线程,需要转为Thread对象,进行线程开启。

创建方法:
public class RunnableDemo implements Runnable{

    public int num=0;

    @Override
    public void run() {

        for (;num < 100; num++) {

            System.out.println(Thread.currentThread().getName()+":"+num);

        }

    }
}
public static void main(String[] args) {

    RunnableDemo runnableDemo = new RunnableDemo();
    //把对象转成Thread类对象进行线程开启
    Thread thread1 = new Thread(runnableDemo);
    thread1.setName("线程1");

    Thread thread2 = new Thread(runnableDemo);
    thread2.setName("线程2");

    thread1.start();
    thread2.start();
}

线程的运行状态:

新建状态---->就绪状态(start开启之后,cpu调用之前)----->运行状态(cpu调用)--->阻塞状态(执行过程中分配时间未使用完之前被打断)------>死亡状态(线程中的功能执行完毕)。

实现Runnable接口比继承Thread类所具有的优势

从上面的运行结果可以看出,两者的区别。

实现Runnable接口的,对于三个线程来说共享的是ThreadTest1对象的资源。

继承Thread类,三个线程都是独立的运行,线程间不共享资源。

所以可以总结出以下区别:

  1. Runnable接口的话,可以避免单继承的局限性,具有较强的健壮性。

  2. Runnable可以实现资源的共享,同时处理同一资源。

  3. Thread类的线程间都是独立运行的,资源不共享。

  4. 继承Thread类不再被其他类继承(java不存在多继承)

线程锁:

多线程进行数据共享时线程不安全,数据无法进行统一,会有重复数据产生

锁:synchronized (){}代码块
  1. 可以将代码放入代码块中,保护线程安全

  2. 可以直接将synchronized写在方法的返回值前,与synchronized {}代码块有相同的作用,且不需要再创建对象

  3. 可以直接在()里面写入当前类.class,指定当前类为锁的对象

1、使用synchronized (){}代码块保护线程安全实现买票操作:
public class LockRunnableDemo implements Runnable{

        public int num = 1000;
        public Object obj = new Object();
    /**
     * 使用锁保护线程安全实现买票操作
     */
    @Override
    public void run() {
    
        while (true){
            synchronized (obj){
                //循环卖票
                if (num<1){  //票卖完
                    return;
                }
                System.out.println(Thread.currentThread().getName()+"剩余票量:"+num+"张");
                num--;
            }
        }
    }
}
public static void main(String[] args) {

    LockRunnableDemo ticket = new LockRunnableDemo();

    Thread thread1 = new Thread(ticket);
    thread1.setName("1号窗口");

    Thread thread2 = new Thread(ticket);
    thread2.setName("2号窗口");

    Thread thread3 = new Thread(ticket);
    thread3.setName("3号窗口");

    //开启线程
    thread1.start();
    thread2.start();
    thread3.start();
}

2、使用synchronized直接写在返回值前 实现保护线程安全

直接将synchronized写在方法的返回值前,与synchronized {}代码块有相同的作用,且不需要再创建对象

public synchronized void test(){  // 使用 this

    while (true) {

            if (num < 1) {
                return;
            }

            System.out.println(Thread.currentThread().getName() + "报:剩余" + num + "张票,开始售卖一张");
            num--;

        }

}
3、使用类的反射对象作为锁的对象 实现保护线程安全

直接在()里面写入当前类.class,指定当前类为锁的对象

public class ThreadDemo2 implements Runnable{
    public int  num = 100;
    @Override
    public void run() {
        test();
    }
    public void test(){  // 使用 this
        while (true) {
            synchronized (ThreadDemo2.class) {
                if (num < 1) {
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "报:剩余" + num + "张票,开始售卖一张");
                num--;
            }
        }
    }
}

Lock锁:

使用Lock锁

java.util.concurrent.locks.Lock接口

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

把需要锁维护线程安全的代码

通过 Thread对象.lock()方法添加锁

通过 Thread对象.unlock()释放锁

lock使用步骤:

ReentrantLock是lock的一个实现类 通过此类的无参构造方法就可以创建lock

  1. 在成员位置创建一个ReentrantLock对象

  2. 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁

  3. 在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

创建线程的类代码示例:

public class ThreadDemo3 implements Runnable{
    public Lock lock;
    public int  num = 100;
    @Override
    public void run() {
        test();
    }
    public void test(){  // 使用 this
        while (true) {

            lock.lock();
                if (num < 1) {
                    return;
                }

                System.out.println(Thread.currentThread().getName() + "报:剩余" + num + "张票,开始售卖一张");
                num--;

            lock.unlock();

        }
    }
}

Main方法中代码示例:

public static void main(String[] args) {

    ThreadDemo3 t = new ThreadDemo3();
    //lock锁
    //创建一个ReentrantLock对象
    t.lock = new ReentrantLock();

    Thread thread = new Thread(t);
    thread.setName("白晶晶3");

    Thread thread1 = new Thread(t);
    thread1.setName("朱丝丝3");

    thread.start();
    thread1.start();
}

在多个线程对同一个对象使用时,lock锁时需要创建同一个锁的对象

   使用lock锁的优势:

  可以在不同的线程类中使用数据共享时 可以使用同一个lock锁 保护线程的安全

  注意 此时使用的数据为引用数据类型

   (想要多个线程中使用同一个数据,必须使用引用数据类型,通过引用地址,指向同一个数据对象)

例如:

Demo4线程类:

public class ThreadDemo4 extends Thread{

    public Lock lock;
    public NumberDemo nd;

    @Override
    public void run() {

       while (true){
       
           lock.lock();
           if(nd.num<1){
               lock.unlock();
               return;
           }

           System.out.println(getName()+":数字为:"+nd.num);
           nd.num--;
           
           lock.unlock();
       }
    }
}

Demo5线程类:

public class ThreadDemo5 extends Thread{

    public Lock lock;
    public NumberDemo nd;
    @Override
    public void run() {
   
       while (true){
       
           lock.lock();
           if(nd.num<1){
               lock.unlock();
               return;
           }
           System.out.println(getName()+":数字为:"+nd.num);
           nd.num-=2;
           lock.unlock();
       }
    }
}

Main方法:

public static void main(String[] args) {
        NumberDemo numberDemo = new NumberDemo();
        numberDemo.num=500;

        Lock lock = new ReentrantLock();

        ThreadDemo4 t = new ThreadDemo4();
        t.nd = numberDemo;
        t.lock=lock;
/*
        ThreadDemo4 t2 = new ThreadDemo4();
        t2.nd = numberDemo;
        t2.lock=lock;

        ThreadDemo4 t3 = new ThreadDemo4();
        t3.nd = numberDemo;
        t3.lock=lock;*/

        ThreadDemo5 t5 = new ThreadDemo5();
        t5.nd = numberDemo;
        t5.lock=lock;

        t.start();
       /* t2.start();
        t3.start();*/
        t5.start();
    }

死锁:

当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他同步。这时会引发一种现象:程序出现无限等待,这种现象我们称为死锁

简单理解:指两个线程或多个线程相互持有对方所需要的资源,导致线程都处于等待状态,无法往下执行,这就是死锁!

产生的原因:

  1. 多个线程中都使用了多个相同的对象作为锁

  2. 多个线程中都使用了锁中套锁的形式,且锁的使用顺序不同

代码示例:

public class SiSuoDemo1 extends Thread{
    public Object obj1;
    public Object obj2;
    @Override
    public void run() {
        synchronized (obj1){
            System.out.println(getName()+":获取锁1");
            
            synchronized (obj2){
                System.out.println(getName()+":获取锁2");
            }
            System.out.println(getName()+"获取锁");
        }
    }
}
public class SiSuoDemo2 extends Thread{
    public Object obj1;
    public Object obj2;
    @Override
    public void run() {
        synchronized (obj2){
            System.out.println(getName()+":获取锁2");

            synchronized (obj1){
                System.out.println(getName()+":获取锁1");
            }
            System.out.println(getName()+"获取锁");
        }
    }
}
public static void main(String[] args) {
    //创建对象作为锁
    Object o = new Object();
    Object o1 = new Object();
    //创建线程类对象
    SiSuoDemo1 s1 = new SiSuoDemo1();
    s1.obj1=o;
    s1.obj2=o1;
    
    SiSuoDemo2 s2 = new SiSuoDemo2();
    s2.obj1=o;
    s2.obj2=o1;

    s1.start();
    s2.start();

}

解决方式:

死锁的出现主要是因为同步中嵌套同步了,我们只需要保证不让它们进行嵌套即可解决死锁的出现

  1. 避免锁中套锁进行锁的使用 (最关键的避免死锁的方式)

  2. 如果套锁,需要保证多个线程中的锁有所不同,或者使用锁的顺序保持一致

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值