多线程

程序:一段静态的代码

进程:正在运行的一个程序,系统在运行时为每一个进程分配不同的内存区域。

线程:进程的进一步细化,与进程共享一个内存区域,但是有独立的运行栈和程序计数器。

单核CPU:假的多线程,一个线程执行一次,时间间隔较短,类似多车道,一个收费站。

多核CPU:更好的发挥多线程的效率。

一个java程序至少有三个线程:main()、gc()垃圾回收、异常处理线程。

并行:多个CPU执行多个任务

并发:一个CPU执行多个任务,如:秒杀,多人做同一件事

多线程的优点:

  1. 提高程序的相应,对于图形化界面,提高用户的体验
  2. 提高cpu的利用率。
  3. 将程序拆分成多个任务,方便代码的独立运行。

1、线程的创建和使用

线程的创建

方式一:继承于Thread

  1. 创建一个继承Thread类的子类
  2. 重写run()方法
  3. 创建Thread类的子类
  4. 通过子类对象调用start()方法

start()方法:①启动该线程 ②调用run()方法

注意

  1. 直接调用run()方法并不会开启新的线程,是主线程正常运行run()方法。
  2. 不可以两次运行(同一对象调用2次start方法)相同的线程,必须重新new 线程对象。

方式二:实现Runnable接口

  1. 创建一个实现Runnable接口的类。
  2. 实现类实现run()方法。
  3. 创建实现类对象
  4. new Thread(实现类对象)
  5. 通过Thread类的对象调用 start()

Thread中的常用方法:

  1. start(),启用线程,调用run()
  2. run(),将需要多线程需要执行的方法写里面
  3. currentThread(),静态方法,返回当前执行代码的线程
  4. getName(),获取线程名
  5. setName(),设置线程名 / new Thread("线程名");
  6. yield(),释放当前cpu执行权
  7. join():在线程A中调用线程B的join方法,此时A就进入阻塞状态,直到B执行完,A才继续执行。
  8. sleep(long millitime):让当前线程睡眠一定的毫秒时间
  9. isAlive():判断当前线程是否存活

线程的调度

  • 线程的优先级:

MAX_PRIORITY :10

NORM_PRIORITY :5 默认优先级

MIN_PRIORITY :1

注意:高优先级的线程要抢占低优先级的线程cpu执行权,但是只是从概率上讲,并不是一定。

  • 获取或设置当前线程的优先级

getPriority(): 获取

setPriority();设置

比较两种创建线程的方式:

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

原因:

  1. 实现的方式没有类单继承的局限性
  2. 实现的方式更适合处理多个线程共有数据的情况

2、线程的生命周期

  1. 新建:调用start()
  2. 就绪:等待取得cpu资源
  3. 运行:
  4. 阻塞:①sleep() ②join() ③等待同步锁 ④wait()
  5. 死亡:①执行完 ②stop() ③error/exception且没有处理。

3、线程的同步

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

同步的方式:

好处:解决了线程的安全问题

局限性:操作同步代码时,只有一个线程能操作,效率比较低。

方式一:同步代码块

    synchronized(同步监视器){
    //需要被同步的代码
    }

说明:

  1. 需要操作共享数据的,就视为同步的代码。
  2. 同步监视器(锁),任何一个类的对象都可以充当锁。要求:多个线程共用一把锁
  3. 在实现Runnable接口的方式中,可以使用this充当锁
  4. 在继承Thread的方式中,慎用this充当锁。可考虑当前对象

方式二:同步方法

操作共享数据的代码完整声明在一个方法中,可使用同步方法的方式。

实现Runnable接口的方式

public synchronize void  a(){
}

继承Thread的方式

public static synchronize void  a(){
}

注意:

  1. 在该方式中,没有显式的锁。
  2. 非静态的同步方法,默认锁是this。
  3. 静态的同步方法,琐是当前类本身。

改写单例模式中的懒汉式为线程安全:

class Bank{
    private Bank(){}
    private static Bank instance = null;

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

4、线程的死锁

理解:不同线程占用对方需要的资源不释放,处于僵持的状态,都在互相等待。

说明:出现死锁后程序并不会出现异常,只是所有线程都处于阻塞的状态。

解决方法:

  1. 专门的算法、原则
  2. 尽量减少同步资源的定义
  3. 避免嵌套同步

5、线程的同步--锁

Lock锁-----jdk5.0新增

  1. 实例化 ReentrantLock()对象

    ReentrantLock lock = new ReentrantLock();
    
  2. 调用锁定方法lock.lock();

  3. 解锁lock.unlock();

synchronize 与 Lock 异同? (面试题)

  • 相同:都可以解决线程安全问题

  • 不同:synchronize 机制在执行完相应的同步代码后,自动释放锁。

    ​ Lock需要手动上锁(lock())和解锁(unLock());

使用顺序(建议/非必须)

Lock-->同步代码块-->同步方法

如何解决线程安全问题?有几种方式?(面试题)

三种:同步块,同步方法,锁Lock

6、线程的通信

线程通信的三个方法:

  1. wait() : 一旦执行此方法,当前线程就进入阻塞状态,并释放锁
  2. notify() : 执行此方法,唤醒被wait()的线程,如果有多个,就唤醒优先级高的
  3. notifyAll() : 执行此方法,唤醒全部wait()的线程

注意:

  1. 三个方法只能使用在同步代码块或同步方法中。
  2. 三个方法的调用者,必须是同步代码块或同步方法中的监视器(锁),否者会出现异常。
  3. 三个方法定义在Object中,不是Tread中。

实例:两个线程交替打印1-100

public class Test {

    public static void main(String[] args) {
        Number number = new Number();

        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("线程1");
        t2.setName("线程2");

        t1.start();
        t2.start();
    }
}


class Number implements Runnable{
    int number = 1 ;

    @Override
    public void run() {
        while (true){
            synchronized (this) {
                notify();
                if (number<=100){
                    System.out.println(Thread.currentThread().getName()+":"+number);
                    number++;
                }
                else{
                    break;
                }
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

sleep()和 wait()方法的异同? (面试题)

  • 相同:都可以使线程阻塞
  • 不同:
  1. 两者声明的位置不同,sleep()声明在Thread类,而wait()声明在Object类。
  2. sleep可以在任何位置使用,而wait()只能在同步代码块和同步方法中使用。
  3. 当两者都使用在同步代码块和同步方法中时,sleep()不会释放锁,wait()会释放锁

例题生产者消费者

产品小于0个不能购买,大于0个可购买。

产品大于20不能再生产,不足20继续生产。

销售类

class Clerk{

    private int num = 0;

    public synchronized void produceProduct() {
        if(num<20){
            num++;
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":开始生产第"+num+"个产品");
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void customerProduct() {
        if(num>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":开始消费第"+num+"个产品");
            num--;
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

生产者

class Producter extends Thread{

    private Clerk clerk;

    public Producter(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true){
            clerk.produceProduct();
        }
    }
}

消费者

class Customer extends Thread{

    private Clerk clerk;

    public Customer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true){
            clerk.customerProduct();
        }
    }
}

测试

public class ProductTest {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producter p1 = new Producter(clerk);
        Customer c1 = new Customer(clerk);
        Customer c2 = new Customer(clerk);

        p1.setName("生产者1");
        c1.setName("消费者1");
        c2.setName("消费者2");

        p1.start();
        c1.start();
        c2.start();
    }
}

7、新增线程的创建方式

方式一:实现Callable接口

JDK5.0 新增

与实现runnable接口相比更加强大:

  1. call方法中可以有返回值。
  2. call()可以抛出异常,被外面的操作捕获。
  3. Callable支持泛型。

步骤:

  1. 创建Callable的实现类
  2. 将要执行的操作写在Callable实现类的call方法中。
  3. 创建callable的实现类对象
  4. 创建FutureTask 对象 ,callable的实现类对象作为参数。
  5. new Thread(FutureTask 对象).start()开启线程;
  6. 可使用FutureTask 对象的get()方法,获取call()方法的返回值。

方式二:使用线程池

思路:提前建立好多个线程,放入线程池,随用随取。

好处:①提高响应速度②降低资源消耗

③便于线程管理:

  1. 线程池大小
  2. 线程数
  3. 没有任务的持续时间

步骤:

 //1.提供指定数量的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //2.执行指定线程操作,需要提供实现runnable/callable类的对象
        executorService.execute(new Number());
		//executorService.submit(callable callable);
        //3.关闭线程池
        executorService.shutdown();

创建多线程有几种方式?(面试题)

4种:继承Tread

实现Runnable

实现Callable

线程池

原文地址:https://www.cnblogs.com/xiaolaodi1999/p/13385062.html