title: Java多线程_线程优先级、线程安全与同步
date: 2022-03-04 08:57:25
tags: Java


Java多线程_线程优先级、线程安全与同步

一、线程优先级与常用API

  每个线程执行时都有一定的优先级,优先级高的获取较多的执行机会,优先级低的线程则较少。线程默认的优先级与父线程优先级相同,Thread类提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级,其中setPriority()方法的参数范围在1-10之间,也可以使用Thread类的三个常量(如下),这三个静态常量来设置优先级可以保证程序会有更好的可移植性。

/**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

常用API有:

int getPriority() : 返回线程的优先级

void setPriority() : 设置优先级

Thread.State getState() : 返回线程的当前状态

boolean isAlive() : 是否是活动状态

void setDaemon() : 设置线程为守护线程,当Java虚拟机退出时,所有的守护线程都终止

boolean isDaemon() : 判断当前线程是否是守护线程

void join()
   void join(long millis)

static void sleep(long millis) :

static void yield() :

相同点:

1.都是Thread类的静态方法。

2.进入同步代码块或方法,被监视器锁锁定的线程遇到这两个方法,都不会释放锁。

不同点:

1.sleep()方法将当前线程进入超时等待状态。

2.yield()方法不会改变当前线程的状态,都是Runnable状态,这个方法是让当前线程有可能从Running状态–>Ready状态,也有可能不礼让,当前线程只会礼让给优先级高或者相同的其他Ready状态线程。

3、sleep()方法有声明InterruptedException异常,需要在编译期进行处理,yield()方法没有声明异常。

二、线程安全与同步

2.1线程安全

当使用多个线程来访问同一个对象资源进行读写操作的时候,CPU的调度导致数据不一致,很容易出现线程安全问题。

售票系统演示线程安全问题:

/**
 * ClassName: Demo6
 * Package: PACKAGE_NAME
 * Description:
 *
 * @Date: 2022/3/1 10:43
 * @Author: tancheng
 */
class TicketSys {
    private int K78 = 100;   // 票的数量

    // 售票方法
    public void saleTicket() {
        // 判断余票数
        if(K78 > 0) {  // 取出K78中的数据进行判断
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"抢到了第" + (K78--) + "张票");
        }
    }
}

// 携程网
class XieCheng extends Thread {

    private TicketSys ticketSys;

    public XieCheng() {

    }

    public XieCheng(TicketSys ticketSys, String name) {
        super(name);
        this.ticketSys = ticketSys;
    }


    public void run() {
        for(int i=0; i<100; i++) {
            // 调用关联的系统售票
            this.ticketSys.saleTicket();
        }
    }
}

// 飞猪往
class FlyPig extends Thread {

    private TicketSys ticketSys;

    public FlyPig() {

    }

    public FlyPig(TicketSys ticketSys, String name) {
        super(name);
        this.ticketSys = ticketSys;
    }

    public void run() {
        for(int i=0; i<100; i++) {
            // 调用关联的系统售票
            this.ticketSys.saleTicket();
        }
    }
}





public class Demo6 {

    public static void main(String[] args) {
        // 创建一个售票系统
        TicketSys ts12306 = new TicketSys();

        // 创建app
        XieCheng xc = new XieCheng(ts12306, "携程");
        FlyPig fp = new FlyPig(ts12306, "飞猪");


        xc.start();
        fp.start();


    }

}

结构如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O0RilFCp-1646362274749)(D:\大云汉IT2021\javaSE\2022-3-1\随堂笔记.assets\线程安全.png)]

2.2解决线程安全问题方式(线程同步)

解决线程的安全问题相对的会降低程序的运行效率,为了减少这种负面影响,建议只对需要进行修改操作的方法进行同步,单线程环境中不需要考虑线程安全,只在多线程环境中使用同步。

2.2.1同步代码块

采用Java多线程的synchronized关键字来声明同步代码块,使用同步监视器锁来解决。语法如下:

   synchronized(监视器锁) {
       同步代码块
   }

作用:被同一个监视器锁监视的代码块,一次只能让一个线程进入,进入的线程持有这个监视器锁,其他线程没有这个锁是进入不了同步代码块,其他线程就进入阻塞(blocked)状态,只有当持有同步锁的线程释放锁后,其他线程才会去争夺锁。

2.2.2同步方法

与同步代码块相对应,Java多线程还提供了同步方法,使用synchronized关键字来修饰某个方法,该方法则为同步方法。

对于synchronized修饰的实例方法(非static方法)来说,默认的同步锁对象是this,也就是调用该方法的对象,同步静态方法时默认的同步锁对象是当前字节码对象 。

被同步监视器锁锁定的线程何时释放锁:

①.从同步代码块、同步方法出来时释放锁

②.遇到break,return

③. 遇到未处理的异常

④.遇到wait()方法释放锁,获得同步锁的线程进入waiting()

2.2.3同步锁

  从Java5开始提供了一种更强大的线程同步方式——Lock接口。Lock中的实现类ReentrantLock(可重入锁),使用Lock()加锁、unLock()解锁,因为需要手动控制来释放锁资源,所以需要在使用try/catch解决异常时将unLock()方法放到finally里面释放锁资源。

package com.Demo;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

 
public class TestLock {

      private Lock lock = new ReentrantLock();

      public static void main(String[] args) {

           final TestLock testLock = new TestLock();

           new Thread() {

                 @Override

                 public void run() {

                      testLock.insert(Thread.currentThread());

                 }

           }.start();

           new Thread() {

                 @Override

                 public void run() {

                      testLock.insert(Thread.currentThread());

                 }

           }.start();

      }

      public void insert(Thread thread) {

           lock.lock();

        try {

            System.out.println(thread.getName()+"得到了锁");

           

        } catch (Exception e) {

            // TODO: handle exception

        }finally {

            System.out.println(thread.getName()+"释放了锁");

            lock.unlock();

        }

      }
}

其中还有个ReadWriteLock()方法,称为读写锁。可以让只需要读取功能的线程进入,将需要修改功能的线程阻塞在外面,但需要修改功能的线程只能有一个获取锁。意味着不再是只能有一个线程获取锁,更加在保证安全的同时提高程序运行的效率。

public interface ReadWriteLock {

    /**

     * Returns the lock used for reading.

     *

     * @return the lock used for reading.

     */

    Lock readLock();

 

    /**

     * Returns the lock used for writing.

     *

     * @return the lock used for writing.

     */

    Lock writeLock();

}
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐