[Java学习日记]多线程

目录

一.多线程的第一种实现方式

二.多线程的第二种实现方式

三.多线程第三种实现方式

四.多线程中的常用方法

五.线程优先级

六.守护线程(备胎线程)

七.出让线程(礼让线程)

八.插入线程(插队线程)

九.线程的生命周期与安全问题

十.同步代码块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);
    }
}

[Java学习日记]多线程_第1张图片

 


 

二.多线程的第二种实现方式

多线程的第二种实现方式:实现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);
        }

    }
}

[Java学习日记]多线程_第2张图片 

 


 

三.多线程第三种实现方式

多线程第三种实现方式:利用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;
    }
}

 [Java学习日记]多线程_第3张图片

 


 

四.多线程中的常用方法

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 + " ");
        }
    }
}

 [Java学习日记]多线程_第4张图片

 


 

五.线程优先级

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);
        }
    }
}

[Java学习日记]多线程_第5张图片 

 [Java学习日记]多线程_第6张图片

 


 

七.出让线程(礼让线程)

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();
        }
    }
}

[Java学习日记]多线程_第7张图片 

 


 

八.插入线程(插队线程)

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);
        }
    }
}

[Java学习日记]多线程_第8张图片 

 


 

九.线程的生命周期与安全问题

线程的生命周期:
新建:(创建线程对象):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 + "张票!!!");
        }
    }
}

 

 [Java学习日记]多线程_第9张图片

 


 

十.同步代码块synchronized

把操作共享的数据给锁起来,一次只给一个线程运行
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 + "张票!!!");
            }
        }
    }
}

 [Java学习日记]多线程_第10张图片

 


 

十一.同步方法

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

为了更加清晰的表示如何加锁与如何释放锁,提供了新的锁对象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锁,现在线程二能够结束啦");
                    }
                }
            }
        }
    }
}

 [Java学习日记]多线程_第11张图片


 

十四.等待唤醒机制

线程中的等待者和唤醒者(等待唤醒机制):可以让线程轮流执行
其中一条线程叫做生产者,负责生产数据,另一条线程叫做消费者,负责消费数据
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();
                }
            }
        }
    }
}

[Java学习日记]多线程_第12张图片

 


 

十五.利用阻塞队列实现等待唤醒机制(不使用锁)

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();
            }
        }
    }
}

[Java学习日记]多线程_第13张图片 

 

你可能感兴趣的:(JAVA黑马程序员笔记分享,学习,java)