Java学习日记:多线程

单线程程序:即,若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务才开始执行。

多线程程序:即,若有多个任务可以同时执行。多个任务可以并发执行。

Java程序的运行原理

由java命令启动JVM,JVM启动就相当于启动了一个进程,该线程在负责java程序的运行,而且这个线程运行的代码存在于main方法中,我们把这个线程称之为主线程。

  • 进程:是正在运行的程序

独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的并发性:任何进程都可以同其他进程一起并发执行

  • 线程:是进程中的单个顺序控制流,是一条执行路径

单线程:一个进程如果只有一条执行路径,则称为单线程程序

多线程:一个进程如果有多条执行路径,则称为多线程程序

并发并行

普通解释:

并发:交替做不同事情的能力

并行:同时做不同事情的能力

专业术语:

并发:不同的代码块交替执行

并行:不同的代码块同时执行

并发和并行的意义:

并发和并行都可以处理“多任务”,二者的主要区别在于是否是“同时进行”多个的任务。但是 涉及到任务分解(有先后依赖的任务就不能做到并行)、任务运行(可能要考虑互斥、锁、共享等)、结果合并

CPU的时间观

为什么要进行“并发、并行、多任务、多线程、异步”等等相关的一些操作,就是为了充分利用CPU的巨大潜能,因为很多任务是需要时间的,比如文件的读写、网络数据流的下载等等,如果让CPU处于一个闲置状态,不充分利用它,程序的效率就很低。

线程的三种创建

Thread类

实现步骤

🍉自定义线程类MyThread继承Thread类

🍉重写run()方法,编写线程执行体

🍉创建线程对象,调用start()方法启动线程

public class Demo1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <10; i++) {
            if (i%2==1){
                System.out.println(i);
            }
        }
    }
}
class Text{
    public static void main(String[] args) {
        Demo1 a = new Demo1();
        Demo1 b = new Demo1();
        a.start();
        b.start();
    }
}

Runnable 接口

推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

实现Runnable接口实现多线程的步骤:

1)定义一个类并实现Runnable接口。

2)重写Runnable接口的run()方法,在run()方法中包含线程需要执行的任务。

3)通过Thread类创建线程对象,并把Runnable接口的实现类对象作为参数传递。

4)调用线程对象的start()方法开启线程,并调用Runnable实现类的run()方法。

public class Demo3 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <10; i++) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}
class Text1{
    public static void main(String[] args) {
        Demo3 d3 = new Demo3();

        Thread th1 = new Thread(d3,"线程1");
        Thread th2 = new Thread(d3,"线程2");
        Thread th3 = new Thread(d3,"线程3");

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

Callable接口(了解)

1️⃣ 让类实现Callable接口,确定返回值的类型

2️⃣ 重写call方法

3️⃣ 实例化该类

4️⃣ 实例化FutureTask的对象,在构造函数中把刚刚实例化的类放进去

5️⃣ 实例化Thread类的对象,在构造函数中把刚刚实例化的FutureTask的对象放进去

6️⃣ 调用Thread类的对象的start方法

7️⃣ 通过FutureTask类的get方法获得返回值,该方法是阻塞的

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//1:实现
public class Demo5 implements Callable<String> {

    private int num;
    private String name;

    public Demo5(int num, String name) {
        this.num = num;
        this.name = name;
    }
//2:重写
    @Override
    public String call() throws Exception {
        for (int i = 1; i <=num; i++) {
            Thread.sleep(1000);
            System.out.println(name+"在烤肠子烤了"+i+"串");
        }
        return name+"烤完了";
    }
}
class Text2{
    public static void main(String[] args) {
        //3:实例化
        Demo5 da = new Demo5(5,"KXBB");
        Demo5 db = new Demo5(8,"WQQ");
        //4:实例化FutureTask对象
        FutureTask<String> ft1 = new FutureTask<>(da);
        FutureTask<String> ft2 = new FutureTask<>(db);
        //5:实例化Thread对象
        Thread t1 = new Thread(ft1);
        Thread t2 = new Thread(ft2);
        //6:启动
        t1.start();
        t2.start();
        //7:get()获取返回值
        try {
            String s1 = ft1.get();
            System.out.println(s1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        try {
            String s2 = ft2.get();
            System.out.println(s2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

线程状态 

 设置线程的优先级

public class Demo6 {
    public static void main(String[] args) {
        Test3 t1 = new Test3(1,"低级");
        Test3 t2 = new Test3(10,"高级");

        t1.start();
        t2.start();
    }
}
class Test3 extends Thread{
    private int num;
    private String name;

    public Test3() {
    }

    public Test3(int num, String name) {
        super(name);
        this.setPriority(num);
    }

    @Override
    public void run() {
        for (int i = 0; i <10; i++) {
            System.out.println(getName()+"执行第"+i+"次");
        }
    }
}

线程睡眠

public class Test2 {
    public static void main(String[] args) {
        for(int i = 1; i < 10; i++) {
            System.out.println(i);
            try {
                Thread.sleep(1000); // 对主线程休眠1秒钟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程让步(不一定让,看cpu决策)

public class Test {
    public static void main(String[] args) {
        new TestThread("高级", 10).start();
        new TestThread("低级", 1).start();
    }
}

class TestThread extends Thread {
    public TestThread() {}
    public TestThread(String name, int pro) {
        super(name); // 设置线程名字
        this.setPriority(pro); // 设置线程的优先级
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(this.getName() + "线程第" + i + "次执行!");
            Thread.yield(); // 线程让步
        }
    }
}

线程合并

Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞 ,可以想象成插队

public class Demo2 implements Runnable{
    public static void main(String[] args) {
        Demo2 d2 = new Demo2();
        Thread t2 = new Thread(d2);
        t2.start();
        for (int i = 0; i <50; i++) {
            if (i==20){
                try {
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("main"+i);
        }
    }

    @Override
    public void run() {
        for (int i = 0; i <50; i++) {
            System.out.println("join"+i);
        }
    }
}

守护线程

守护线程的用途:

守护线程通常用于执行一些后台作业,例如在你的应用程序运行时播放背景音乐,在文字编辑器里做自动语法检查、自动保存等功能。Java的垃圾回收也是一个守护线程。守护线的好处就是你不需要关心它的结束问题。例如你在你的应用程序运行的时候希望播放背景音乐,如果将这个播放背景音乐的线程设定为非守护线程,那么在用户请求退出的时候,不仅要退出主线程,还要通知播放背景音乐的线程退出;如果设定为守护线程则不需要了。

public class Demo3 {
    public static void main(String[] args) {
        t1 t1 = new t1();
        t2 t2 = new t2();
        t1.start();
        t2.setDaemon(true);
        t2.start();
    }
}
class t1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <5; i++) {
            System.out.println("主线程执行---"+i);
        }
    }
}
class t2 extends Thread{
    int i = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("副线程执行---"+(i++));
        }
    }
}
//运行以上代码,通过执行的结果可以看出:前台线程是保证执行完毕的,后台线程还没有执行完毕就退出了。

线程同步(解决买票问题)

一:同步代码块

Thread类

public class WindowTest {
    public static void main(String[] args) {
        Window2 w1 = new Window2();
        Window2 w2 = new Window2();
        Window2 w3 = new Window2();
        w1.start();
        w2.start();
        w3.start();

    }
}

class Window2 extends Thread{
    private static int ticket = 100;
    //private Object obj = new Object();
    private static Object obj = new Object();
    @Override
    public void run() {
        while (true){
            //synchronized (this){  //错误,此时的this代表 w1 w2 w3三个对象
            synchronized (Window2.class){    //扩展:反射 Class cla = Window2.class
            //synchronized (obj){
                if (ticket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName()+":卖票,票号为:"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }
        }
    }
}

Runbable接口

public class RunnableTest {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口一:");
        t2.setName("窗口二:");
        t3.setName("窗口三:");

        t1.start();
        t2.start();
        t3.start();
    }
}
class Window implements Runnable{
    private static int ticket = 100;
    //Object obj = new Object();

    @Override
    public void run() {
        while (true){
            //synchronized(obj){
            synchronized(this){  //此时的this:唯一的Window的对象
                if (ticket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }
        }
    }
}

银行存钱案例

import javax.swing.plaf.basic.BasicMenuBarUI;
import java.util.Map;

public class Demo1 {
    public static void main(String[] args) {
        Money money = new Money();
        Bank bank = new Bank(money);
        new Thread(bank,"wqq").start();
        new Thread(bank,"zbk").start();
    }
}
class Bank implements Runnable{
    Money money;
    int num = 3000;
    public Bank(Money money) {
        this.money = money;
    }

    @Override
    public void run() {
        synchronized (this){
            if (money.getMoney()>=num) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                money.save(num);
                System.out.println(Thread.currentThread().getName() + "成功,余额为" + money.getMoney());
            }else   System.out.println(Thread.currentThread().getName()+"失败,余额为"+money.getMoney());
        }
    }
}
class Money {
    private int money = 5000;
    public int getMoney(){
        return money;
    }
    public void save(int num){
        money -= num;
    }
}

二:同步方法

Thread类《静态方法》

public class WindowDemo {
    public static void main(String[] args) {
        Window4 w1 = new Window4();
        Window4 w2 = new Window4();
        Window4 w3 = new Window4();
        w1.start();
        w2.start();
        w3.start();
    }
}

class Window4 extends Thread{
    private static int ticket = 100;

    @Override
    public void run() {
        while (true){
            show();
        }
    }

    public static synchronized void show(){  同步监视器  Window4.class
    //public synchronized void show(){  同步监视器  w1  w2  w3
        if (ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
            ticket--;
        }
    }
}

Runbale接口《非静态》

//同步方法
public class RunnableDemo {
    public static void main(String[] args) {
        Window3 w = new Window3();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口一:");
        t2.setName("窗口二:");
        t3.setName("窗口三:");

        t1.start();
        t2.start();
        t3.start();
    }
}
class Window3 implements Runnable{
    private  static int ticket = 100;

    @Override
    public void run() {
        while (true){
            if (ticket > 0){
                show();
            }else {
                break;
            }
        }
    }
    //同步方法
    private synchronized void show(){  //同步监视器  this
        if (ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
            ticket--;
        }
    }
}

 银行存钱案例

import javax.swing.plaf.basic.BasicMenuBarUI;
import java.util.Map;

public class Demo1 {
    public static void main(String[] args) {
        Money money = new Money();
        Bank bank = new Bank(money);
        new Thread(bank,"wqq").start();
        new Thread(bank,"zbk").start();
    }
}
class Bank implements Runnable{
    Money money;
    int num = 3000;
    public Bank(Money money) {
        this.money = money;
    }

    @Override
    public void run() {
        synchronized (this){
            if (money.getMoney()>=num) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                money.save(num);
                System.out.println(Thread.currentThread().getName() + "成功,余额为" + money.getMoney());
            }else   System.out.println(Thread.currentThread().getName()+"失败,余额为"+money.getMoney());
        }
    }
}
class Money {
    private int money = 5000;
    public int getMoney(){
        return money;
    }
    public void save(int num){
        money -= num;
    }
}

总结:synchronized关键字使用注意点

1、只能同步方法和代码块,而不能同步变量和类。

2、只有共享资源的读写访问才需要同步,如果不是共享资源,根本没有同步的必要。

3、编写线程安全的代码会使系统的总体效率会降低,并且容易发生死锁的情况。

4、无论是同步代码块还是同步方法,必须获得对象锁才能够进入同步代码块或同步方法进行操作。

5、如果是非静态同步方法,对象锁为方法所在的对象;如果是静态同步方法,对象锁为方法所在的类(唯一)。

6、如果两个线程要执行一个类中的静态同步方法或非静态同步方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说,如果一个线程在对象上获得一个锁,就没有任何其它线程可以进入(该对象的)类中的任何一个同步方法。

死锁(不常见)

死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,

都在等待对方放弃自己需要的同步资源,就形成了线程死锁

说明:出现死锁后,不会抛异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

我们使用同步时,要避免出现死锁

public class Demo6 {
    public static Object obj1 = new Object();
    public static Object obj12 = new Object();
    public static void main(String[] args) {
        new Thread(new Lock1()).start();
        new Thread(new Lock2()).start();
    }
}
class Lock1 implements Runnable{

    @Override
    public void run() {
        System.out.println("Lock1开始执行");
        while (true){
            synchronized (Demo6.obj1){
                System.out.println("LockA 锁住 obj1");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (Demo6.obj12){
                    System.out.println("LockA 锁住 obj2");
                }
            }
        }
    }
}
class Lock2 implements Runnable{

    @Override
    public void run() {
        System.out.println("Lock2开始执行");
        while (true){
            synchronized (Demo6.obj12){
                System.out.println("LockB 锁住 obj2");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (Demo6.obj1){
                    System.out.println("LockB 锁住 obj1");
                }
            }
        }
    }
}

产生死锁的四个必要条件:

1. 互斥条件:一个资源每次只能被一个进程使用。

2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

3. 不剥夺条件 : 进程已获得的资源,在末使用完之前,不能强行剥夺。

4. 循环等待条件 : 若干进程之间形成一种头尾相接的循环等待资源关系。

线程通讯

wait() :等待,将正在执行的线程释放其执行资格和执行权,进入阻塞状态,并存储到线程池中。

notify():唤醒,唤醒线程池中 wait()的线程,一次唤醒一个,而且是任意的。

notifyAll():唤醒全部,可以将线程池中的所有 wait() 线程都唤醒。

class Number implements Runnable{
    private int number = 1;
    @Override
    public void run() {
        while(true){
            synchronized (this){
                //唤醒被阻塞的单个线程        notifyAll();唤醒所有线程
                notify();
                if (number <= 100){
                    System.out.println(Thread.currentThread().getName()+":"+number);
                    number++;

                    try {
                        //使得调用wait()方法的线程进入阻塞状态,此时释放锁
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    break;
                }
            }
        }
    }
}
public class CommTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);
        t1.setName("线程一:");
        t2.setName("线程二:");
        t1.start();
        t2.start();
    }

}

sleep()和 wait()方法的区别

1、sleep不要求同步;wait必须先同步。

2、sleep在休眠的时间内,不能唤醒;wait在等待的时间内,能唤醒。

sleep不释放锁(如果有锁),不释放 CPU 使用权;wait释放锁,释放 CPU 使用权。

生产者消费者案例(在自己可见中,不太懂)

Lock锁接口(解决同步问题)

直接上代码

// 卖票线程任务类
class TicketRunnable implements Runnable {
    // 共享数据,保存火车票的总量
    private static int ticketNum = 300;
    // 创建一个锁对象
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            lock.lock(); // 获取锁
            try {
                // 当票数小于等于0时,窗口停止售票,跳出死循环
                if (ticketNum <= 0)
                    break;
                // 当票数大于0时,售票窗口开始售票
                Thread.sleep(1); // 模拟切换线程的操作
                // 输出售票窗口(线程名)卖出的哪一张票
                String name = Thread.currentThread().getName();
                System.out.println(name + "--卖出第" + ticketNum + "张票");
                // 卖票之后,总票数递减
                ticketNum--;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock(); // 释放锁
            }
        }
    }
}
// 测试类
public class Test2 {
    public static void main(String[] args) {
        // 创建线程任务类对象
        TicketRunnable tr = new TicketRunnable();
        // 开启三个售票线程
        new Thread(tr, "窗口1").start();
        new Thread(tr, "窗口2").start();
        new Thread(tr, "窗口3").start();
    }
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo3 {
    public static void main(String[] args) {
        Bank2 bank2 = new Bank2(new Money2());
        new Thread(bank2,"wqq").start();
        new Thread(bank2,"zbk").start();
    }
}
class Bank2 implements Runnable{
    public Bank2(Money2 money2) {
        this.money2 = money2;
    }

    int num = 3000;
    Money2 money2 = new Money2();
    static ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        lock.lock();
        try {
            if (money2.getMoney()>=num){
                Thread.sleep(1);
                money2.save(num);
                System.out.println(Thread.currentThread().getName()+"成功,余额为"+money2.getMoney());
            }else System.out.println(Thread.currentThread().getName()+"失败,余额为"+money2.getMoney());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
class Money2{
    private int money = 5000;
    public int getMoney(){
        return money;
    }
    public void save(int num){
        money -= num;
    }
}

synchronized 与 Lock 的对比

Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)

synchronized是隐式锁,出了作用域自动释放

Lock只有代码块锁,synchronized有代码块锁和方法锁

使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展

性(提供更多的子类)

优先使用顺序:

Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方

法体之外)

面试记背

相同:二者都是可以解决线程安全问题

不同:synchronized机制在执行完相应的同步代码块以后,自动的释放同步监视器

Lock需要手动的启动同步( lock() ),同时结束同步也需要手动的实现(unlock())

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值