目录
一.多线程的第一种实现方式
二.多线程的第二种实现方式
三.多线程第三种实现方式
四.多线程中的常用方法
五.线程优先级
六.守护线程(备胎线程)
七.出让线程(礼让线程)
八.插入线程(插队线程)
九.线程的生命周期与安全问题
十.同步代码块synchronized
十一.同步方法
十二.锁对象Lock
十三.死锁案例
十四.等待唤醒机制
十五.利用阻塞队列实现等待唤醒机制(不使用锁)
1.什么是进程与线程? 进程:程序的基本执行实体:每一个正在运行的软件都是一个进程 线程:是操作系统能够进行的运算调度最小的单位。被包含在进程中,是进程的实际运作单位,线程简单理解就是软件中的多个独立运行的功能 如果小白在一条流水线上每隔10分钟要搬一键货物,而搬货物很快就能搬完,可以给小白去搬多条流水线的货物 如果有多线程,CPU能够在多个线程之间来回切换,把等待的空闲时间利用起来从而提高程序运行效率 比如拷贝大文件使用多线程来完成:在这期间可以做其他事情。原神启动时背景音乐,图片,加载文件同时在做事情 2.什么是并发与并行 并发:在同一时刻,有多个指令在单个CPU上"交替"执行:比如你现在一边写笔记一边看视频,但是你只会一次做一件事(Java中允许应用程序并发运行多个执行线程) 并行:在同一时刻,有多个指令在多个CPU上"同时"执行:我的电脑是四核8线程:代表电脑能够同时运行8条线程 在计算机中,并发和并行很有可能同时都在发生 多线程的 实现方式一:继承Thread类,如果想拥有一条线程,就创建它的实现类并且开启它就可以咯 3.创建如何自己的线程类? 4.使用自己的线程类的最基本的流程? 5.如何给自己的线程设定名字?
public class Demo311 {
public static void main(String[] args) {
System.out.println("3.创建自己的线程类,继承与Thread类,实现run方法即可");
System.out.println("4.创建线程对象,并调用start方法开启线程");
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
System.out.println("5.使用Thread类中的setName方法给线程起个名字:方便在线程的run方法中调用getName获取名字以区分线程");
mt1.setName("线程1");
mt2.setName("线程2");
mt1.start();
mt2.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++)
System.out.println(getName()+"-"+ i);
}
}
多线程的第二种实现方式:实现Runnable接口:通过Thread对象--传入自己写的类的对象--来开始线程 1.如何创建runnable的实现类?表示什么? 2.如何获取当前的线程的对象? 3.如何使用runnable对象开始线程?
public class Demo312 {
public static void main(String[] args) {
System.out.println("1.创建类实现Runnable接口,实现run方法,表示线程将要执行的任务");
System.out.println("2.使用Thread的静态方法currentThread()方法获取当前线程对象");
MyRunnable myRunnable = new MyRunnable();
System.out.println("3.在Thread类的构造方法中同一个传入runnable的实现类对象,再start thread对象开启线程");
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
thread1.setName("线程1:");
thread2.setName("线程2:");
thread1.start();
thread2.start();
}
}
//创建实现类:表示多线程要执行的任务
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
//使用Thread调用静态方法获取到当前线程的对象
System.out.println(Thread.currentThread().getName() + i);
}
}
}
多线程第三种实现方式:利用Callable接口(返回多线程与运行结果)与Future(管理多线程运行结果)接口方式实现 1.第三种方式相较于前两种方式有什么特点? 在继承Thread类中的run方法不能返回结果 在实现Runnable的类中的run方法也没有返回值 如果需要返回值可以使用第三种实现方式:可以获取到多线程运行的结果 2.继承Thread类的优缺点 编程简单,可以直接使用这个类中的方法,但是不能继承其他类,扩展性差 3.如何如何使用Callable接口与Future接口实现多线程?
public class Demo313 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("(1)编写实现类,实现call方法,规定返回值");
System.out.println("(2)创建实现类的对象myCallable");
MyCallable mc = new MyCallable();
System.out.println("(3)创建FutureTask对象(为Future实现类)来管理多线程执行的结果");
FutureTask ft = new FutureTask<>(mc);
System.out.println("(4)创建线程,传入FutureTask对象并执行");
Thread t1= new Thread(ft);
t1.start();
System.out.print("(5)使用ft对象的get方法获取线程的结果:");
System.out.println(ft.get());
}
}
//1.实现callable接口
class MyCallable implements Callable {
@Override
public Integer call(){
return 100;
}
}
GetSetName方法 1.使用getName方法获取默认名字的特点? 2.如何在创建线程对象的时候设定名字? 3.如何获取到当前线程对象时,获取的对象由什么决定? 4. 如何使线程休眠(静态)?
public class Demo314 {
public static void main(String[] args) throws InterruptedException {
System.out.println("1.使用getName方法获取默认名字的特点:在底层每调用一次构造方法,静态变量就会++,根据这个静态变量起名");
System.out.print(new MyThread().getName()+" ");
System.out.print(new MyThread().getName()+" ");
System.out.println(new MyThread().getName());
System.out.println("2.在创建线程时传入字符串即可设定名字。");
System.out.print("由于继承不能继承构造方法:需要重写构方法造:传入字符串设置名字:");
System.out.println(new MyThread("线程名字").getName());
System.out.println("3.哪条线程执行到这个方法,获取到的就是那条线程的对象:JVM启动之后会自动启动多条线程");
System.out.println("其中main线程的作用就是调用main方法并执行里面的代码:");
System.out.print(Thread.currentThread().getName());
System.out.println("4.使用Thread的静态方法sleep设置当前线程线程的睡眠时间");
Thread.sleep(2000);
System.out.println("睡眠结束");
new MyThread().start();
}
}
class MyThread extends Thread {
public MyThread() {
super();
}
public MyThread(String s) {
super(s);
}
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(i + " ");
}
}
}
1.线程调度分为哪两种? 分为抢占式调度(随机执行)与非抢占式调度(轮流执行) 2.Java中采用的是哪种线程调度? Java中采用的是抢占式调度,优先级越高,这条线程抢到CPU的概率越大 3.如何获得与设置线程的优先级?
public class Demo315 {
public static void main(String[] args) {
MyThread mt0 = new MyThread();
MyThread mt1 = new MyThread();
System.out.println("1.使用getPriority()方法获得线程优先级");
System.out.print(mt0.getPriority()+" ");
System.out.println(mt1.getPriority());
System.out.println("Main线程优先级:"+Thread.currentThread().getPriority());
System.out.println("2.使用get与setPriority设置优先级,最小是1,最大是10");
mt0.setPriority(10);
mt1.setPriority(1);
mt0.start();
mt1.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + ":" + i);
}
}
}
1.守护线程的特点是? 非守护线程结束的时候,守护线程就没必要存在 2.如何设定线程为守护线程? 在下面的案例中,守护线程一般不会打印到99
public class Demo316 {
public static void main(String[] args) {
MyThread1 mt1 = new MyThread1();
MyThread2 mt2 = new MyThread2();
System.out.println("2.使用setDaemon方法设置为守护线程");
mt2.setDaemon(true);
mt1.start();
mt2.start();
}
}
class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + ":" + i);
}
}
}
class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
1.出让线程的作用 出让当前线程执行权:能够使得线程抢占更加均匀 2.如何实现礼让线程?
public class Demo317 {
public static void main(String[] args) {
MyThread mt0 = new MyThread();
MyThread mt1 = new MyThread();
System.out.println("2.使用Thread的静态方法yield每一遍循环都礼让出线程");
mt0.start();
mt1.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
//出让当前线程执行权:能够使得线程抢占更加均匀
Thread.yield();
}
}
}
1.如何插入线程? 2.插入线程有什么作用?
public class Demo318 {
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread();
mt.start();
System.out.println("1.使用Thread对象的join方法插入线程");
System.out.println("2.表示把mt这个线程插入到当前线程(在这里是main线程)线程之前,有这行与没这行输出结果不一样");
System.out.println("下入下面这一行,会强行停止main进程,不加的话则是两个进程同时进行");
mt.join();
for (int i = 0; i < 10; i++) {
Thread.sleep(1);
System.out.println("main:"+i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + ":" + i);
}
}
}
线程的生命周期: 新建:(创建线程对象):start()->就绪(有执行资格,没有执行权),抢到CPU执行权->运行 (有执行资格与执行权,java中没有定义运行这种状态,这里是为了更好理解,抢夺到执行权之后交给操作系统不管了) 停止一个线程运行的情况: 1.一旦被其他线程抢走执行权->又会回到就绪状态 2.一旦执行sleep()方法->就会变成计时等待状态,没有执行资格与执行权,停止计时等待状态之后->就绪状态,故睡眠状态结束之后还需要抢执行权 3.一旦执行wait()方法就变成等待状态,别的线程执行notify方法能变成就绪状态 4.一旦无法获取锁就会编程阻塞状态,获得锁编程就绪状态 5.run()方法执行完毕-> 死亡状态 线程的安全问题:执行下面的代码(3个窗口卖同100张票),可能会有问题:重复票号 线程1在执行完ticket++ 之后,没来得及执行输出语句,就被线程2,3执行了ticket++:导致他们都有可能输出同一个票号 该如何解决呢? 给共享数据的那一块代码锁起来,一次只能让一个线程进去
public class Demo319 {
public static void main(String[] args) {
MyThread t0 = new MyThread();
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t0.start();
t1.start();
t2.start();
}
}
class MyThread extends Thread {
static int ticket = 0;
@Override
public void run() {
while (true) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket >= 100) {
break;
}
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
}
}
}
把操作共享的数据给锁起来,一次只给一个线程运行 1.如何使用synchronized? 将数据共享的代码使用synchronized(锁对象){}包裹起来 2.锁对象应该传入什么参数? 锁对象应该传入唯一的对象:在这里定义的是一个常量变量,但一般使用本类的class对象 3.在本案例中,同步代码块能够写在while循环外面吗? 不能,当有个线程抢到同步代码块之后,就会把所有代码执行完毕,其他的窗口就不用执行任务了
public class Demo3110 {
public static void main(String[] args) {
MyThread t0 = new MyThread();
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t0.start();
t1.start();
t2.start();
}
}
class MyThread extends Thread {
static int ticket = 0;
static final Object object = new Object();
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MyThread.class) {
if (ticket >= 100) {
break;
}
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
}
}
}
}
1.同步方法是什么? 把synchronized关键字加到方法上面(写在返回值前面),和加同步代码块的功能一样 2.同步方法的锁对象是什么? 如果同步方法非静态,锁对象就是this 如果同步方法为静态,就是当前类的字节码文件对象 所以在下面的案例中 由runnable实现的多线程使用的是同一个runnable对象,调用run方法时都是调用的同一个对象中的变量和方法 所以不需要写静态方法,也不需要使用静态来修饰票数 由Thread的子类实现的多线程使用的是不同的thread对象,调用run方法时来到了不同的类当中,需要写静态的同步方法 3.StringBuilder与StringBuffer的区别是什么? 所有方法几乎都是一模一样:StringBuilder是线程不安全的,StringBuffer是线程安全的 (StringBuffer的所有方法几乎都加上了synchronized修饰)
public class Demo3111 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t0 = new Thread(r);
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t0.start();
t1.start();
t2.start();
/* MyThread t0 = new MyThread();
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t0.start();
t1.start();
t2.start();*/
}
}
class MyRunnable implements Runnable {
int ticket = 0;
@Override
public void run() {
do {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (ticketPlus());
}
synchronized boolean ticketPlus() {
if (ticket >= 100) return false;
ticket++;
System.out.println("窗口" + Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");
return true;
}
}
class MyThread extends Thread {
static int ticket = 0;
@Override
public void run() {
do {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (ticketPlus());
}
static synchronized boolean ticketPlus() {
if (ticket >= 100) return false;
ticket++;
System.out.println("窗口" + Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");
return true;
}
}
为了更加清晰的表示如何加锁与如何释放锁,提供了新的锁对象Lock 1.Lock对象在使用的时候有什么特点? 在Lock当中需要手动上锁与手动释放锁 2.使用什么类去实现锁对象呢? Lock为接口,使用其对象需要用到ReentrantLock(可重入的锁)来实例化 3.如何上锁,如何关锁?可以用什么方法使得关锁这个操作到最后一定会执行? 使用lock方法上锁,使用unlock方法关锁,也是和synchronized包裹的范围一样 有时候退出循环操作中可能会忘记释放锁。使用try-catch-finally去释放锁:最为稳妥
public class Demo3112 {
public static void main(String[] args) {
MyThread t0 = new MyThread();
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t0.start();
t1.start();
t2.start();
}
}
public class MyThread extends Thread {
static int ticket = 0;
//创建对象:用同一把锁,需要加静态
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//上锁
lock.lock();
try {
//有时候退出循环操作中可能会忘记释放锁,这里由于使用了finally,这里不需要释放锁
if (ticket >= 100)break;
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
} catch (Exception ignored) {
} finally {
//释放锁
lock.unlock();
}
}
}
}
1.死锁可能出现的情况? 在程序当中出现了锁的嵌套:外面一个锁,里面一个锁 想要避免死锁就需要注意多线程中锁嵌套的情况 在这个案例中,使用了两个线程,执行两端不同的代码:但是使用的是相同的锁对象 在线程一拿到锁A,线程二拿到锁B的情况下,两个线程由于得不到锁进行不下去
public class Demo3113 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
}
}
public class MyThread extends Thread{
static final Object objA = new Object();
static final Object objB = new Object();
@Override
public void run() {
while (true){
if ("线程一".equals(getName())){
synchronized (objA){
System.out.println("线程一拿到了A锁,准备去拿B锁");
synchronized (objB){
System.out.println("线程一拿到了B锁,现在线程一能够结束啦");
}
}
}else {
synchronized (objB){
System.out.println("线程二拿到了B锁,准备去拿A锁");
synchronized (objA){
System.out.println("线程二拿到了A锁,现在线程二能够结束啦");
}
}
}
}
}
}
线程中的等待者和唤醒者(等待唤醒机制):可以让线程轮流执行 其中一条线程叫做生产者,负责生产数据,另一条线程叫做消费者,负责消费数据 1.线程的运行由什么决定? 产品的有无决定是哪一条线程运行 2.消费者抢到CPU执行权会做什么? 如果是消费者抢到CPU执行权, 没有产品,只能等(wait)生产者生产 如果有产品,就消费-唤醒厨师生产。 3.生产者抢到CPU会做什么? 如果是生产者抢到CPU执行权,有产品,只能等着(wait)消费者消费 没有食物就制作-唤醒消费者 4.如何唤醒单个线程与多个线程 notify是随机唤醒单个线程 notifyAll是唤醒所有
public class Demo3114 {
public static void main(String[] args) {
Consumer consumer = new Consumer();
Producer producer = new Producer();
consumer.start();
producer.start();
}
}
public class Product {
//控制生产者和消费者的执行:如果使用boolean:只能控制两条线程轮流执行
public static int flag = 0;
//总运行次数
public static int count = 10;
//锁
public static final Object myLock = new Object();
}
1.什么是wait方法?什么时候能够停止wait? wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,当然自己停止抢占CPU, 以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了notify方法或者notifyAll (notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了 但不是马上得到锁,因为锁还在别人手里,别人还没释放), 调用wait方法的一个或多个线程就会解除wait状态,重新参与竞争对象锁, 程序如果可以再次得到锁,就可以继续向下运行。 2.如何使用wait方法与notify方法? 不能通过线程对象去调用,需要通过锁对象(同一个)去调用
public class Consumer extends Thread{
@Override
public void run() {
while (true){
synchronized (Product.myLock){
if (Product.count == 0)break;
if (Product.flag==0) {
try {
//等待的时候要用锁对象去wait()
Product.myLock.wait();//让当前线程和锁进行绑定
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
Product.flag=0;
Product.count--;
System.out.println("消费者消费完毕,还能再消费"+Product.count+"个");
//唤醒这把锁绑定的所有线程
Product.myLock.notifyAll();
}
}
}
}
}
/*
* 1.循环
* 2.同步代码块
* 3.到了末尾怎么办
* 4.没有到末尾怎么办
*/
public class Producer extends Thread{
@Override
public void run() {
while (true){
synchronized (Product.myLock){
//到了末尾
if (Product.count == 0)break;
if (Product.flag==1) {
//有产品,等待
try {
Product.myLock. wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//没产品,生产,唤醒
Product.flag=1;
System.out.println("生产者生产完毕");
Product.myLock.notifyAll();
}
}
}
}
}
1.阻塞的含义? put时当队列满的时候不能再加入元素 take第一个数据取不到会等待 2.阻塞队列实现了哪接口? Iterable(可迭代器遍历)与Collection(单列集合)接口 实现了Queue接口,实现了BlockingQueue接口(阻塞队列) 3.阻塞队列的实现类有哪些?分别有什么特点? ArrayBlockingQueue(有界)与LinkedBlockingQueue(近乎无界-21亿)
public class Demo3115 {
public static void main(String[] args) {
//创建阻塞队列的对象:在其他两个类里面也定义一个这样的变量,通过构造方法获取这个对象,这样能够让两个类使用同一个对象。
ArrayBlockingQueue abq = new ArrayBlockingQueue<>(1);
Consumer consumer = new Consumer(abq);
Producer producer = new Producer(abq);
consumer.start();
producer.start();
}
}
public class Consumer extends Thread{
ArrayBlockingQueue queue;
public Consumer(ArrayBlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true){
try {
queue.take();
System.out.println("消费者消费完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Producer extends Thread{
//也可以使用构造方法传入一个对象来初始化变量,也能写静态代码块:只能在一个类里面写,保证是同一个对象
ArrayBlockingQueue queue;
public Producer(ArrayBlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
//生产者只需要put一个产品就可以了
while (true){
try {
//在put方法中会创建锁,如果队列装满了,就wait,没有装满就放数据
queue.put("产品");
//这里由于输出语句是写在锁之外,所以输出可能会有连续
System.out.println("生产了一个产品");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}