JAVA高级学习笔记

JAVA高级笔记

一、多线程

程序、进程、线程

在这里插入图片描述

  • 了解

在这里插入图片描述

  • 优点

在这里插入图片描述

创建多线程方式一:继承Thread类

  1. 创建一个继承与Thread类的子类
  2. 重写Thread类的run() 方法 --> 将此线程执行的操作声明在run() 中
  3. 创建Thread类的子类的对象
  4. 通过此对象去调用Thread的start() 方法
public class ThreadTest {
   
    public static void main(String[] args) {
   
        MyThread myThread = new MyThread();
        // 不能使用myThread.run()来调用,这样就是单线程了
        myThread.start();
        for (int i = 0; i < 100; i++) {
   
            if (i % 2 == 0) {
   
                System.out.println(i + "abc");
            }
        }
    }
}

class MyThread extends Thread{
   
    @Override
    public void run() {
   
        for (int i = 0; i < 100; i++) {
   
            if (i % 2 == 0) {
   
                System.out.println(i);
            }
        }
    }
}
  • 要想启动线程,调用start() 方法,不能用run() 方法来调用
  • 同一个对象不能调用两次start() 方法,想用同一套线程方案,需要再重新创建一个对象
线程的常用方法
  • start() :启动当前线程,调用当前线程的run()

  • run() :通常需要重写Thread类中的此方法,将创建的线程要执行的操作

  • currentThread() :静态方法,返回执行当前代码的线程

  • getName() :获取当前线程的名字

    sout(Thread.currentThread().getName());
    
  • setName() :设置当前线程的名字

    对象名.setName("名称");
    // 给主线程命名
    Thread.currentThread().setName("主线程");
    

    还可以用构造器的方式给主线程命名

  • yield() :释放当前cpu的执行权,有可能在下一刻又会被该线程执行

  • join() :在线程a中,调用线程b的join() 方法,此时线程a就进入阻塞状态,知道线程b完全执行完之后,线程a才结束阻塞状态

    例如在线程a中,调用线程b.join();(本身会抛出异常,需要用try-catch),执行join方法时a线程将就会进入阻塞状态,等b线程执行完之后才继续执行a线程

  • sleep(long millitime) :让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线城市阻塞状态

  • isAlive() :判断当前线程是否存活

线程优先级

MAX_PRIORITY: 10 --> 较高优先级

MIN_PRIORITY: 1 --> 较低优先级

NORM_PRIORITY: 5 --> 默认优先级

  • getPriority() : 获取线程的优先级
  • setPriority(int priority) : 设置线程的优先级

高优先级的线程要抢占低优先级cpu的执行权。但是只是从概率上讲的。

高优先级的线程高概率的情况下被执行,并不意味着只有当高优先级的线程执行完之后,低优先级的线程才执行。

卖票练习
/**
 * @ClassName ThreadTest2
 * @Description 卖票练习测试
 * @Author LiangHui
 * @Date 2020/7/16 8:42
 * @Version V1.0
 */
public class ThreadTest2 {
   
    public static void main(String[] args) {
   
        Window t1 = new Window();
        Window t2 = new Window();
        Window t3 = new Window();

        // 线程命名
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        t1.start();
        t2.start();
        t3.start();

    }
}

class Window extends Thread{
   
    private static int ticket = 100;
    @Override
    public void run() {
   
        while (true) {
   
            if (ticket > 0) {
   
                // 此处还存在线程安全问题,或同时有多个线程进入if中
                System.out.println(getName() + " 卖票,票号为:" + ticket);
                ticket--;
            }else {
   
                break;
            }
        }
    }
}

创建多线程方式二:实现Runnable接口

  1. 创建一个实现Runnable接口的类
  2. 实现类去实现Runnable中的抽象方法:run()
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类的对象调用start()
public class ThreadTest3 {
   
    /**
     * @Author LiangHui
     * @Description main方法,主程序入口
     * @Date 2020/7/16 10:13
     * @param args
     * @return void
     */
    public static void main(String[] args) {
   
        // 创建实现类的对象
        MyThread3 myThread3 = new MyThread3();
        // 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(myThread3);
        // 通过Thread类的对象调用start()
        // 要看源码才知道为什么会执行MyThread类中的run()方法
        // 因为target不为null 所以调用了Runnable类型的target的run()方法
        t1.start();
    }
}

class MyThread3 implements Runnable{
   
    /**
     * @Author LiangHui
     * @Description 实现run()抽象方法
     * @Date 2020/7/16 10:14
     * @param
     * @return void
     */
    public void run() {
   
        for (int i = 0; i < 100; i++) {
   
            if (i % 2 == 0) {
   
                // currentThread() 获取当前线程
                System.out.println(Thread.currentThread().getName() + "偶数" + i);
            }
        }
    }
}
卖票练习
public class ThreadTest4 {
   
    public static void main(String[] args) {
   
        Window2 window2 = new Window2();
        Thread t1 = new Thread(window2);
        Thread t2 = new Thread(window2);
        Thread t3 = new Thread(window2);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();

    }
}

class Window2 implements Runnable {
   
    // 因为只创建了一个Windows2对象,所以不用添加static
    private int ticket = 100;
    /**
     * @Author LiangHui
     * @Description 实现run()抽象方法,实现卖票功能
     * @Date 2020/7/16 10:40
     * @param
     * @return void
     */
    @Override
    public void run() {
   
        while (true) {
   
            if (ticket > 0) {
   
                System.out.println(Thread.currentThread().getName() + " 票号:" + ticket);
                ticket--;
            } else {
   
                break;
            }
        }
    }
}

比较两种创建线程的方式

  • 开发中,优先选择实现先Runnable接口的方式

    • 实现的方式没有类的单继承性的局限性
    • 实现的方式更适合来处理多个线程有共享数据的情况
  • 联系

    • Thread类也是实现了Runnable接口的

      public class Thread implements Runnable
      
    • 两种方法都需要重写run() 方法,将线程要执行的逻辑声明在run() 中

线程的生命周期

在这里插入图片描述

在这里插入图片描述

线程安全问题

  • 问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
  • 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
  • 如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。

在Java中,我们通过同步机制,来解决线程的安全问题

方式一:同步代码块
synchronized(同步监视器){
   
    // 需要被同步的代码
}
  • 说明:
    • 操作共享数据的代码,即为需要被同步的代码。 --> 不能包含代码多了,也不能包含代码少了。
    • 共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
    • 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
  • 要求:多个线程必须要共用同一把锁。即同步监视器必须是相同的对象
  • 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。

同步代码块处理实现Runnable

public class ThreadTest5 {
   
    /**
     * @Author LiangHui
     * @Description main方法,主程序入口
     * @Date 2020/7/16 18:05 
     * @param args 
     * @return void
     */
    public static void main(String[] args) {
   
        Window3 window3 = new Window3();
        Thread t1 = new Thread(window3);
        Thread t2 = new Thread(window3);
        Thread t3 = new Thread(window3);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();
    }

}

class Window3 implements Runnable{
   
    private int ticket = 100;
    /**
     * @Author LiangHui
     * @Description 实现run()方法,实现卖票功能
     * @Date 2020/7/16 17:30
     * @param
     * @return void
     */
    @Override
    public void run() {
   
        // 在操作共享变量的地方添加同步代码块
        // synchronized(同步监视器),同步监视器可以使任意的对象,前提是必须是公用的对象(锁)
        while (true) {
   
            synchronized (this) {
   
                if (ticket > 0) {
   
                    System.out.println(Thread.currentThread().getName() + " 票号:" + ticket);
                    ticket--;
                } else {
   
                    break;
                }
            }
        }
    }
}

同步代码块处理继承Thread类

class Window4 extends Thread {
   
    private static int ticket = 100;
    /**
     * @Author LiangHui
     * @Description 重写run()方法,实现卖票功能
     * @Date 2020/7/17 7:15
     * @param
     * @return void
     */
    @Override
    public void run() {
   
        // 类也是对象,所以Window4.class是对象,并且是唯一的
        while (true) {
   
            synchronized (Window4.class) {
   
                if (ticket > 0) {
   
                    System.out.println(getName() + " 票号:" + ticket);
                    ticket--;
                } else {
   
                    break;
                }
            }
        }
    }
}
方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的

同步方法块处理实现Runnable

class Window5 implements Runnable {
   
    private int ticket = 100;
    /**
     * @Author LiangHui
     * @Description 实现run()抽象方法,实现卖票功能
     * @Date 2020/7/16 10:40
     * @param
     * @return void
     */
    @Override
    public void run() {
    // 同步方法,不能在run()方法中添加同步监视器
        while (true) {
   
            buyTicket();
        }
    }
    /**
     * @Author LiangHui
     * @Description 创建同步方法,将操作共享变量的代码包含进去
     * @Date 2020/7/17 8:57
     * @param
     * @return void
     */
    private synchronized void buyTicket(){
   
        if (ticket > 0) {
   
            System.out.println(Thread.currentThread().getName() + " 票号:" + ticket);
            ticket--;
        }
    }
}

同步代码块处理继承Thread类

class Window6 extends Thread{
   
    private static int ticket = 100;
    /**
     * @Author LiangHui
     * @Description 重写run()方法,实现卖票功能
     * @Date 2020/7/17 9:04
     * @param
     * @return void
     */
    @Override
    public void run() {
   
        while (true) {
   
            buyTicket();
        }
    }
    /**
     * @Author LiangHui
     * @Description 创建静态的同步方法,将操作共享变量的代码包含进去
     * @Date 2020/7/17 9:07 
     * @param  
     * @return void
     */
    private static synchronized void buyTicket() {
   
        if (ticket > 0) {
   
            System.out.println(Thread.currentThread().getName() + " 票号:" + ticket);
            ticket--;
        }
    }
}
使用同步方式的优劣
  • 同步的方式,解决了线程的安全问题。—好处

  • 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 —局限性

解决单例模式的懒汉式实现中的线程安全问题
public class SingletonLazyTest {
   
    public static void main(String[] args) {
   
        Bank bank = Bank.getInstance();
    }
}

class Bank {
   
    private Bank() {
   

    }
    // 私有化构造器
    // 内部创建类的对象,并且为空
    private static Bank instance = null;
    // 同步方法,锁(同步监视器)-->Bank.class
    // public static synchronized Bank getInstance(){
   
    public static Bank getInstance(){
   
        // // 存在线程安全问题
        // if (instance == null) {
   
        //     instance = new Bank();
        // }
        // return instance;

        // 同步代码块
        // 方式一:效率稍差,每次new对象的时候,都要排队拿锁,等候时间长,效率稍差
        // synchronized (Bank.class) {
   
        //     if (instance == null) {
   
        //         instance = new Bank();
        //     }
        // }
        // return instance;
        // 方式二:效率稍高,只要第一个进去了,后面的就不用拿锁,直接返回,双重检验
        if (instance == null) {
   
            synchronized (Bank.class) {
   
                if (instance == null) {
   
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

完整版

public class SingletonLazyTest {
   
    public static void main(String[] args) {
   
        Bank bank = Bank.getInstance();
    }
}

class Bank {
   
    private Bank() {
   

    }
    private static Bank instance = null;
    public static Bank getInstance(){
   
        if (instance == null) {
   
            synchronized (Bank.class) {
   
                if (instance == null) {
   
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

线程死锁问题

在这里插入图片描述

演示死锁问题

public class ThreadTest9 {
   
    public static void main(String[] args) {
   
        StringBuffer str1 = new StringBuffer();
        StringBuffer str2 = new StringBuffer();

        new Thread(){
   
            @Override
            public void run() {
   
                synchronized (str1) {
   
                    str1.append("a");
                    str2.append(1);
                    try {
   
                        sleep(100);
                    } catch (InterruptedException e) {
   
                        e.printStackTrace();
                    }
                    synchronized (str2) {
   
                        str1.append("b");
                        str2.append(2);
                        System.out.println(str1);
                        System.out.println(str2);
                    }
                }
            }
        }.start();

        new Thread(new Runnable() {
   
            @Override
            public void run() {
   
                synchronized (str2) {
   
                    str1.append("c");
                    str2.append(3);
                    try {
   
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
   
                        e.printStackTrace();
                    }
                    synchronized (str1) {
   
                        str1.append("d");
                        str2.append(4);
                        System.out.println(str1);
                        System.out.println(str2);
                    }
                }
            }
        }).start();
    }
}

线程a拿到str1的锁(同步监视器),因为有嵌套的同步代码块,所以要结束当前a线程,必须要拿到str2的锁。

而线程b与线程a同时进行,b线程拿到str2的锁,也是因为嵌套的同步代码块,所有要结束当前b线程,必须要拿到str1的锁

因为a线程拿到了str1的锁,所以b线程无法得到str1的锁,所以b线程阻塞,等待str1的锁释放

同理,b线程拿到str2的锁,所以a线程无法得到str2的锁,所以a线程阻塞,等待str2的锁释放

两个线程各自拿着对方的锁,所以造成线程死锁

解决方法
  • 专门的算法、原则
  • 尽量减少同步资源的定义
  • 尽量避免嵌套同步

线程安全问题 — JDK5.0新增

方式三:Lock锁
public class ThreadCreateLockTest {
   
    public static void main(String[] args) {
   
        Window7 window7 = new Window7();
        Thread t1 = new Thread(window7);
        Thread t2 = new Thread(window7);
        Thread t3 = new Thread(window7);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

class Window7 implements Runnable{
   
    private int ticket = 100;
    // 实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();
    // 实例化ReentrantLock,参数为true,让他们公平竞争,当线程a出来之后,不会立马重新进行争夺,等后面的进程完成之后再重新争夺
    //private ReemtrantLock lock = new ReentrantLock(true);
    @Override
    public void run() {
   
        while (true) {
   
            try {
   
                // 调用锁定方法lock()
                lock.lock();

                if (ticket > 0) {
   
                    System.out.println(Thread.currentThread().getName() + " 票号为:" + ticket);
                    ticket--;
                } else {
   
                    break;
                }
            } finally {
   
                // 调用解锁方法unlock()
                lock.unlock();
            }
        }
    }
}
  • 在线程中实例化ReentranLock对象,然后调用lock() 方法上锁,再调用unlock() 方法解锁。
  • 操作的代码部分要用try-finally包含,finally中写unlock();

synchronized与Lock对比

在这里插入图片描述

线程面试题1

  • synchronized与lock的异同
    • 相同:二者都可以解决线程安全问题
    • 不同
      • synchronized机制在执行完相应的同步代码之后,自动释放同步监视器
      • lock需要手动的启动同步监视器( lock() ),同时结束同步也需要手动的实现( unlock() )

线程的通信

线程之间的交互

使用wait()和notify()/notifyAll()
  • wait() 将线程进入阻塞状态,同时线程释放同步监视器

  • notify() 执行此方法,就会唤醒被wait() 的一个线程,如果有多个线程被wait() 那么就会根据优先级唤醒一个线程

  • notifyAll() 将所有被w

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值