隐藏

JAVA多线程(2(线程安全))
2022年 02月 22 日

Calvin

创建线程的方式三:实现Callable接口。(JDK 5.0新增'了解即可')


如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?



  1. call()可以有返回值的。

  2. call()可以抛出异常,被外面的操作捕获,获取异常的信息

  3. Callable是支持泛型的


//1、实现Callable接口
class NumThread implements Callable{

//2、重写call方法
@Override
public Object call() throws Exception {
    int sum=0;
    for (int i = 0; i < 100; i++) {
        if (i%2==0){
            System.out.println(i);
            sum+=i;
        }
    }
    return sum;
}

}

public class ThreadNew {

public static void main(String[] args) {
    //3、创建实现Callable接口的对象
    NumThread numThread=new NumThread();
    
    //4、将创建实现Callable接口的对象作为参数传到FutureTask类,并new一个对象
    FutureTask futureTask=new FutureTask(numThread);
    
    //5、FutureTask的对象作为thread类的参数
    new Thread(futureTask).start();
    try {
        //6、获取方法返回值
        Object o = futureTask.get();
        System.out.println(o);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

}


创建线程的方式四:使用线程池



  • 优点:



  1. 提高响应速度(减少了创建新线程的时间)

  2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

  3. 便于线程管理


常用方法:

*.setCorePoolSize(int number) :核心池的大小

*.setMaximumPoolSize() :最大池大小

*.setKeepAliveTime() :线程没有任务时最多保持多长时间后会终止



  • 面试题:创建多线程有几种方式?


答: 一共有三种方式创建多线程:



  1. 通过实现Runnable 接口;

  2. 通过继承Thread 类本身;

  3. 通过Callable 和Future 创建线程;

  4. 使用线程池创建多线程。


几种方式的优缺点:



  • 采用继承Thread类方式:

    (1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程

    (2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类

  • 采用实现Runnable接口方式:

    (1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。

    (2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法

  • Runnable和Callable的区别:

    (1)Callable规定的方法是call(),Runnable规定的方法是run()

    (2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的

    (3)call方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常

    (4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务的执行情况,可取消任务的执行,还可获取执行结果

  • 使用线程池:

    优点:

    (1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

    (2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

    (3)提高线程的可管理性。线程是稀缺资源,使用线程池可以进行统一的分配、调优和监控。

    缺点:

    (1)启动较慢

    (2)复杂性较高




死锁:



  • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃己需要的同步资源,就形成了线程的死锁.

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


解决办法:



  1. 使用专门的算法、原则。

  2. 尽量减少同步资源的定义

  3. 尽量避免嵌套同步


解决线程安全问题的几种方法:(解决多个线程同时操作同一个共有属性的问题)


概念:



  1. 悲观锁---总有有刁民想害朕,当前线程认为自己在使用数据的时候,一定有别的线程来修改数据,因此在获取数据的时候先加锁,确保数据不会被线程修改。(像单例模式的lazy)

  2. 乐观锁就是线程认为在自己使用数据时,不会有别的线程来修改数据,就不会加锁,只是在更新数据的时候去判断之前有没有别的线程来更新了数据。(像单例模式的crazy)


方法一:使用synchronized关键字,悲观锁,使用它的时需要一个唯一的,每个线程都可以访问的监听对象 通常就是当前类的对象,此关键字不会造成死锁,使用synchronized可以拿来修饰类,静态方法,普通方法和代码块。



  1. 必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全

  2. 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)


同步代码块:

 synchronized (对象){
   // 需要被同步的代码;



  • synchronized还可以放在方法声明中,表示整个方法为同步方法。


     public synchronized void show (String name){

        ......
}


示例代码:


 public synchronized void products(){

    if (productCount<20){
        productCount++;
        notify();//通信方法,用来唤醒其他线程
    }else {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } } }


方法二:使用lock锁


class Window implements Runnable{

private int ticket = 100;
private ReentrantLock lock = new ReentrantLock(); //1.实例化ReentrantLock

public void run() {
    while(true){
        try{
            lock.lock();//2.调用锁定方法lock()
            if(ticket > 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
                ticket--;
            }else{
                break;
            }
        }finally {
            //3.调用解锁方法:unlock()
            lock.unlock();
        }} }}



  • 面试题:synchronized 与 Lock的异同?

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

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

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

  • 优先使用顺序:

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

JAVA多线程(2(线程安全))

创建线程的方式三:实现Callable接口。(JDK 5.0新增'了解即可')


如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?



  1. call()可以有返回值的。

  2. call()可以抛出异常,被外面的操作捕获,获取异常的信息

  3. Callable是支持泛型的


//1、实现Callable接口
class NumThread implements Callable{

//2、重写call方法
@Override
public Object call() throws Exception {
    int sum=0;
    for (int i = 0; i < 100; i++) {
        if (i%2==0){
            System.out.println(i);
            sum+=i;
        }
    }
    return sum;
}

}

public class ThreadNew {

public static void main(String[] args) {
    //3、创建实现Callable接口的对象
    NumThread numThread=new NumThread();
    
    //4、将创建实现Callable接口的对象作为参数传到FutureTask类,并new一个对象
    FutureTask futureTask=new FutureTask(numThread);
    
    //5、FutureTask的对象作为thread类的参数
    new Thread(futureTask).start();
    try {
        //6、获取方法返回值
        Object o = futureTask.get();
        System.out.println(o);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

}


创建线程的方式四:使用线程池



  • 优点:



  1. 提高响应速度(减少了创建新线程的时间)

  2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

  3. 便于线程管理


常用方法:

*.setCorePoolSize(int number) :核心池的大小

*.setMaximumPoolSize() :最大池大小

*.setKeepAliveTime() :线程没有任务时最多保持多长时间后会终止



  • 面试题:创建多线程有几种方式?


答: 一共有三种方式创建多线程:



  1. 通过实现Runnable 接口;

  2. 通过继承Thread 类本身;

  3. 通过Callable 和Future 创建线程;

  4. 使用线程池创建多线程。


几种方式的优缺点:



  • 采用继承Thread类方式:

    (1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程

    (2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类

  • 采用实现Runnable接口方式:

    (1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。

    (2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法

  • Runnable和Callable的区别:

    (1)Callable规定的方法是call(),Runnable规定的方法是run()

    (2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的

    (3)call方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常

    (4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务的执行情况,可取消任务的执行,还可获取执行结果

  • 使用线程池:

    优点:

    (1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

    (2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

    (3)提高线程的可管理性。线程是稀缺资源,使用线程池可以进行统一的分配、调优和监控。

    缺点:

    (1)启动较慢

    (2)复杂性较高




死锁:



  • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃己需要的同步资源,就形成了线程的死锁.

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


解决办法:



  1. 使用专门的算法、原则。

  2. 尽量减少同步资源的定义

  3. 尽量避免嵌套同步


解决线程安全问题的几种方法:(解决多个线程同时操作同一个共有属性的问题)


概念:



  1. 悲观锁---总有有刁民想害朕,当前线程认为自己在使用数据的时候,一定有别的线程来修改数据,因此在获取数据的时候先加锁,确保数据不会被线程修改。(像单例模式的lazy)

  2. 乐观锁就是线程认为在自己使用数据时,不会有别的线程来修改数据,就不会加锁,只是在更新数据的时候去判断之前有没有别的线程来更新了数据。(像单例模式的crazy)


方法一:使用synchronized关键字,悲观锁,使用它的时需要一个唯一的,每个线程都可以访问的监听对象 通常就是当前类的对象,此关键字不会造成死锁,使用synchronized可以拿来修饰类,静态方法,普通方法和代码块。



  1. 必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全

  2. 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)


同步代码块:

 synchronized (对象){
   // 需要被同步的代码;



  • synchronized还可以放在方法声明中,表示整个方法为同步方法。


     public synchronized void show (String name){

        ......
}


示例代码:


 public synchronized void products(){

    if (productCount<20){
        productCount++;
        notify();//通信方法,用来唤醒其他线程
    }else {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } } }


方法二:使用lock锁


class Window implements Runnable{

private int ticket = 100;
private ReentrantLock lock = new ReentrantLock(); //1.实例化ReentrantLock

public void run() {
    while(true){
        try{
            lock.lock();//2.调用锁定方法lock()
            if(ticket > 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
                ticket--;
            }else{
                break;
            }
        }finally {
            //3.调用解锁方法:unlock()
            lock.unlock();
        }} }}



  • 面试题:synchronized 与 Lock的异同?

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

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

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

  • 优先使用顺序:

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

上一篇
JAVA多线程
下一篇
String常用方法

评论区(暂无评论)

这里空空如也,快来评论吧~

我要评论