java多线程总结

并发:交替执行。一个人吃两个馒头

并行:同时执行。两个人吃两个馒头

所有应用程序都需要进到内存中执行。

线程是一个进程的执行单元。

线程是一条应用程序到CPU的执行路径。

线程调度:

  • 分时调度:平均分配时间
  • 抢占式调度:优先级高的先使用CPU,相同优先级的话,随机选择一个。【Java使用的是抢占式调度。】

Java主线程:执行main方法的线程

创建多线程的第一种方法:创建Thread类的子类

实现步骤:

  1. 创建一个Thread类的子类
  2. 重写run方法,设置线程任务
  3. 创建对象
  4. 调用Thread类的start方法,开启新线程,执行run方法
  • 例子
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Run-->" + i);
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();  // 创建Thread的子类
        myThread.start();  // 启动新线程
        // 主线程
        for (int i = 0; i < 5; i++) {
            System.out.println("Main-->" + i);
        }
    }
}

  • 执行结果
Main-->0
Main-->1
Main-->2
Run-->0
Run-->1
Run-->2
Run-->3
Run-->4
Main-->3
Main-->4

Thread的常用方法:

  • String getName()
    【返回该线程的名称。 】
  • static Thread currentThread()
    【返回对当前正在执行的线程对象的引用。】
  • void setName(String name)
    【改变线程名称,使之与参数 name 相同。 】
  • static void sleep(long millis)
    【在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。】

创建线程的第二种方式:

实现步骤:

  1. 创建一个Runnable接口的实现类
  2. 重写run方法
  3. 创建实现类对象
  4. 创建Thread对象,构造方法中传递Runnable接口的实现类对象
  5. 调用start方法。

其实就是利用了Thread的一个构造方法:

  • Thread(Runnable target)

这两种方式,一种先创建Thread的字类,重写,对象实例化,start方法;一种先实现Runnable接口,创建实现类对象,构造Thread类对象,start方法。用第二种方法。

好处在于:避免单继承的局限性;解耦。

匿名内部类实现线程的创建:

new Thread() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }.start();
        //-------------------------------------------------------------
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        new Thread(runnable).start();
        //----------------------------------------------------------
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();

运行结果:

Thread-0
Thread-1
Thread-2

线程安全问题:

  • 单线程不会出现线程安全问题
  • 多线程没有访问共享数据,不会产生问题
  • 多线程访问了共享的数据,会产生线程安全问题
public class RunnableImpl implements Runnable {
    private int ticket = 10;

    @Override
    public void run() {
        while (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票");
            ticket--;
        }
    }

    public static void main(String[] args) {
        RunnableImpl runnable = new RunnableImpl();
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        Thread t3 = new Thread(runnable);
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

Thread-2-->正在卖10张票
Thread-2-->正在卖9张票
Thread-2-->正在卖8张票
Thread-1-->正在卖10张票
Thread-1-->正在卖6张票
Thread-1-->正在卖5张票
Thread-0-->正在卖10张票
Thread-0-->正在卖3张票
Thread-0-->正在卖2张票
Thread-0-->正在卖1张票
Thread-1-->正在卖4张票
Thread-2-->正在卖7张票

用线程同步机制解决线程安全问题:

分为三种方法:同步代码块;同步方法;锁机制

1. 同步代码块

  • 格式:
        synchronized (锁对象) {
        	可能会出现线程安全问题的代码(访问了共享数据的代码)
        }
  • 锁对象可以使用任意的对象。
  • 但必须保证多个线程使用的锁对象是同一个
  • 锁对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行。
  • 同步中的线程,没有执行完毕不会释放锁
  • 同步外的线程没有锁进不去同步
  • 保证了只有一个线程在同步中执行共享数据
  • 保证了安全,代价是降低一些效率

举例子:

public class RunnableImpl implements Runnable {
    private int ticket = 10;
    Object o = new Object();

    @Override
    public void run() {
        synchronized (o) {
            while (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票");
                ticket--;
            }
        }
    }

    public static void main(String[] args) {
        RunnableImpl runnable = new RunnableImpl();
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        Thread t3 = new Thread(runnable);
        t1.start();
        t2.start();
        t3.start();
    }
}

2. 同步方法:

  • 格式:
权限修饰符 synchronized 返回值类型 方法名(参数列表){
	可能会出现线程问题的代码(访问了共享数据的代码)
}
  • 原理:

同步方法也会把方法内部的代码锁住,只让一个线程执行;
同步方法的锁对象是Runnable实现类的实例化对象。

  • 例子:
public class RunnableImpl implements Runnable {
    private int ticket = 10;

    @Override
    public void run() {
        sellTicket();
    }

    public synchronized void sellTicket() {
        while (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票");
            ticket--;
        }
    }

    public static void main(String[] args) {
        RunnableImpl runnable = new RunnableImpl();
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        Thread t3 = new Thread(runnable);
        t1.start();
        t2.start();
        t3.start();
    }
}

3. 锁机制,Lock锁

在这里插入图片描述
在这里插入图片描述

使用步骤:

  1. 在成员变量位置创建一个ReentrantLock对象
  2. 在可能出现线程安全问题前的代码前调用Lock接口中的lock()方法,获取锁
  3. 在代码后调用Lock接口中的unlock()方法,释放锁。
import java.util.concurrent.locks.ReentrantLock;

public class RunnableImpl implements Runnable {
    private int ticket = 10;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        lock.lock();
        while (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票");
            ticket--;
        }
        lock.unlock();
    }


    public static void main(String[] args) {
        RunnableImpl runnable = new RunnableImpl();
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        Thread t3 = new Thread(runnable);
        t1.start();
        t2.start();
        t3.start();
    }
}

线程状态:

线程状态。线程可以处于下列状态之一:

  • NEW
    至今尚未启动的线程处于这种状态。
  • RUNNABLE
    正在 Java 虚拟机中执行的线程处于这种状态。
  • BLOCKED
    受阻塞并等待某个监视器锁的线程处于这种状态。
  • WAITING
    无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
  • TIMED_WAITING
    等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
  • TERMINATED
    已退出的线程处于这种状态。

在给定时间点上,一个线程只能处于一种状态。这些状态是虚拟机状态,它们并没有反映所有操作系统线程状态。


Object类中的几个方法:
在这里插入图片描述

  • void wait(long timeout) 方法
    【在其他线程调用此对象的notify()方法或者notifyAll()方法,或者超过指定的时间量前。当前线程都是等待状态】
  • wait和sleep的区别:

在这里插入图片描述

  • 带参数的wait方法,即使没有notify唤醒,在参数时间结束也会醒来,如果获得锁就可以运行,接着运行wait()之后的代码

等待与唤醒机制 即:线程间通信

  • wait和notify方法必须由同一个锁对象调用
  • 锁对象可以是任意对象
  • wait和notify必须在同步代码块或是同步函数中使用

在这里插入图片描述

  • 一个消费者和生产者的例子:
public class BaoZi {
    String pi;
    String xian;
    boolean flag = false;
}

public class BaoZiPu extends Thread {
    private BaoZi bz;

    public BaoZiPu(BaoZi bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        int count = 0;
        while (true) {
            synchronized (bz) {
                if (bz.flag == true) { // 说明有包子,不用生产了
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                if (count % 2 == 0) {
                    bz.pi = "薄皮";
                    bz.xian = "三鲜馅";
                } else {
                    bz.pi = "冰皮";
                    bz.xian = "猪肉大葱馅";
                }
                count++;
                System.out.println("包子铺正在生产" + bz.pi + bz.xian + "包子");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                bz.flag = true;
                bz.notify(); // 唤醒吃货线程,使其处于,锁池队列,等待拿到对象锁
                System.out.println();
                System.out.println("包子铺正在生产" + bz.pi + bz.xian + "包子,吃货可以吃了");
            }
        }
    }
}

public class ChiHuo extends Thread {
    private BaoZi bz;

    public ChiHuo(BaoZi bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (bz) {
                if (bz.flag == false) { // 没有包子,wait,到等待队列
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 被唤醒
                System.out.println("吃货正在吃" + bz.pi + bz.xian + "的包子");
                bz.flag = false;
                bz.notify();//唤醒包子铺线程,使其处于锁池队列,等待拿锁。
                System.out.println("吃货吃完了" + bz.pi + bz.xian + "的包子,包子铺开始生产包子");
                System.out.println("======================================");
            }
        }
    }
}

  • 总之就是:包子铺有包子就wait(),等着被唤醒。唤醒了就做包子(知道包子被吃了),如此反复。吃货的话,有包子就吃,吃完了就wait(),等着被唤醒,唤醒了,就知道有包子了,就吃。

通过锁对象的wait()和notify()方法实现了,线程间的通信。




线程池:

使用步骤:

  1. 使用线程池工厂类Executors里面提供的静态方法,创建线程池
    在这里插入图片描述
  2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务。
  3. 调用 ExecutorServicede的submit方法,传递线程任务,开启线程,执行
  4. 调用 ExecutorServicede的shutdown销毁线程池。

在这里插入图片描述

  • 线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值