Java多线程以及处理线程安全

135 阅读5分钟

多线程的创建

方式一:创建继承Thread类的子类

  • 写法一:
  • 1.创建一个继承于Thread类的子类
  • 2.重写Thread类的run()方法 ----> 将创建线程需要执行的操作的声明在run()中
  • 3.创建Thread的子类对象
  • 4.通过子类对象调用start();{①启动当前线程 ②调用当前线程的run()}
  • 5.启动多个线程,就要new多个线程对象
  • 写法二:
  • 创建Thread类匿名子类
        new Thread() {
            @Override
            public void run() {
                // 需要执行的代码
            }
        }.start();

Thread中的常用方法

 * 1.start(); 启动当前线程,调用当前线程的run();\
 * 2.run(); 通常需要重写Thread类中的此方法,将创建的线程需要执行的任务声明在此方法中
 * 3.currentThread(); 返回当前代码所在的线程
 * 4.getName(); 获取当前线程的名称
 * 5.setName(); 设置当前线程的名称
 * 6.yield(); 释放当前CPU执行权,让其他线程优先执行。
 * 7.join() throws InterruptedException; 在线程 A 中调用线程 Bjoin()方法,线程A进入阻塞状态,在线程B完全执行完毕后,线程A结束阻塞继续执行。
 * 8.stop(); 已过时,执行此方法时,强制结束当前线程。
 * 9.sleep(long millis); 让线程进入休眠状态,milltime单位为毫秒。
 * 10.isAlive(); 判断当前线程是否还处于活跃状态。
 *
 * Thread的优先级
 * 1.
 * public final static int MIN_PRIORITY = 1;
 * public final static int NORM_PRIORITY = 5;
 * public final static int MAX_PRIORITY = 10;
 *
 * 2.如何获取和设置当前线程的优先级。
 * 设置优先级setPriority(int newPriority); 数字越大,优先级越高;
 * 获取优先级getPriority();
 *
 *  说明:高优先级线程的执行权 > 低优先级线程的执行权,
 *       但是只是从概率上讲,并不意味着高优先级执行完毕才去执行低优先级的线程。
 *       
 * 线程通信: wait(); notify(); notifyAll(); 这三个方法是定义在Object类中的。

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

  • 1.创建一个实现了实现Runnable接口接口的类
  • 2.实现类去实现Runnable中的抽象方法run();
  • 3.创建实现类对象
  • 4.将此对象作为参数传递到Thread类的构造器中,创建Thread对象
  • 5.通过Thread类的对象调用start(); 实际是调用了Runnable中的Run()方法

比较创建线程的两种方式

 * 开发中:优先选择实现接口Runnable接口方式
 * 原因:1.实现接口的方式,没有单继承的局限性
 *       2.实现的方式更适合多个线程共用同一个数据情况
 *
 * 二者之间的联系:
 *      1.Thread也实现了Runnable接口,也实现了Runnable中的run()方法
 *        public class Thread implements Runnable
 *      2.两种实现方式都需要重写run()方法,并将线程要执行的逻辑代码放在run()中

守护线程和用户线程

  • 二者唯一的区别是判断JVM何时离开
  • 守护线程是用来服务用户线程的,通过在start()方法前调用 thread.setDaemon(true)可以把用户线程变成守护线程。
  • Java垃圾回收的线程就是一个守护线程。
  • 若JVM中都是守护线程,没有用户线程了,当前JVM将推出。

Java线程安全

  • 同步的方式,解决了线程的安全问题。 ---- 坏处
  • 操作同步代码块时,只能有一个线程参与,其他线程等待,相当于时一个单线程的过程,效率变低。---- 坏处

方式一:同步代码块

* synchronized(同步监视器){
*     // 需要被同步的代码 -
*     1.操作共享数据的代码,即为需要被同步的代码
*     2.共享数据:多个线程共同操作的变量
*     3.同步监视器, 俗称:锁 任何对象都可以是同步锁
*       要求多个线程比如公用同一把锁
*       补充:实现runnable接口创建多线程的方式中,也可以考虑使用this作为同步锁
*            在使用继承Thread类创建多线程中,慎用this作为同步锁,因为可能会new多个对象
*
*            可以使用 class 作为同步锁,因为类在进程中只会加载一次。
* }

方式二:同步方法

  • 1.如果操作数据的代码完整的声明在一个方法中,我们可以将此方法声明为同步的
  • 2.非静态的同步方法,同步锁(同步监视器)为this
  • 静态的同步方法,同步锁为this.getClass()
 1.
 * 使用实现Runnable接口的方法,可以把需要实现的同步代码块封装到一个方法中,并将方法声明为同步方法
 * private synchronized void methodName() {
        // 需要同步执行的代码 ---- 此时线程锁为this
 }
 2. 使用继承Thread类方法,声明同步方法,把需要实现的同步代码块封装到一个方法中,并将方法声明为静态 同步方法
 * private synchronized static void saleTicket() {
    // 需要同步执行的代码 --- 此时线程锁为 this.getClass()
 }
 

方式三:lock锁

  • lock锁 -- JDK5.0新增
//1.实例化 ReentrantLock对象

    private ReentrantLock reentrantLock = new ReentrantLock();
//2.调用锁定方法lock()锁线程
    reentrantLock.lock();
    
//3.使用try执行代码块--finally中调用unlock()解锁
 try {
 
    //需要同步执行的代码块
    
    } finally {
        // 解锁
        reentrantLock.unlock();
    }    

synchronized 和 ReentrantLock 的异同

  • 相同:
  •    都是解决线程安全问题
    
  • 不同:
  •   synchronized是加锁后执行完相应的代码,自动释放线程锁
    
  •   ReentrantLock是手动操作启动同步锁[lock()],手动解锁[unlock()]
    
  •   ReentrantLock只有代码块锁,synchronized有代码块锁和方法锁
    
  •   使用ReentrantLock,JVM将花费更少的时间来调度线程,性能更好,并具有更好的扩展性(提供了更多的子类)
    
  • 优先使用顺序 ReentrantLock > 同步代码块 > 同步方法

线程死锁

  • 理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方线程释放需要的同步资源,就行成了线程死锁。
  • 说明: 1.出现死锁后,不回抛出异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续 2.使用线程同步时,要避免线程死锁。