Java八股240926

关于线程、死锁、集合以及Redis分布式锁

Java线程安全机制

线程安全问题主要涉及多个线程同时访问和修改共享数据时可能出现的不可预期的结果

产生原因

  • 共享数据

    • 多个线程同时访问同一个共享的可变状态变量时,就可能出现线程安全问题。产生数据不一致的情况。

    • 如果变量是不可变的,或者每个线程都有自己独立的副本,那么一般不会出现线程安全问题。

  • 线程调度的不确定性

    • 不同的运行环境下,线程的执行顺序可能不同,从而导致不同的结果。

表现形式

  • 数据竞争

    • 多个线程同时读写共享变量,导致最终结果取决于线程执行的顺序,不可预测。
    // 模拟数据竞争
        /**
         * 两个线程同时修改一个共享变量,导致最终结果不正确。
         */
        public static void dataRace() {
            // int sharedValue = 0;
            AtomicInteger sharedValue = new AtomicInteger(0); //共享变量
            class Incrementer implements Runnable { //增量器
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        sharedValue.incrementAndGet();
                    }
                }
            }
            Thread t1 = new Thread(new Incrementer());
            Thread t2 = new Thread(new Incrementer());
            t1.start();
            t2.start(); //分别启动两个线程
            try {
                t1.join();
                t2.join();//暂停主线程,先执行这两个线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("数据竞争结果:" + sharedValue);
            // 预期结果应该是 2000,但由于数据竞争,实际结果可能小于 2000。
        }竞争,实际结果可能小于 2000}
    
  • 死锁

    • 当两个或多个线程相互等待对方持有的资源时,就会发生死锁。每个线程都无法继续执行,导致程序停滞。
    // 模拟死锁
        /**
         * 两个线程分别获取两个不同的锁,并且以相反的顺序请求对方持有的锁,导致死锁。
         * */
        public static void deadlock() {
            Object lock1 = new Object();
            Object lock2 = new Object();
            /**
             * 内部类可以访问外部类的成员变量和方法,并且可以具有与外部类不同的访问修饰符。
            */
            class Thread1 implements Runnable {
                @Override
                public void run() {
                    /**
                     * synchronized主要用于确保在同一时刻只有一个线程可以访问被它修饰的代码块或方法。
                     * 它可以防止多个线程同时访问共享资源时出现数据不一致或其他并发问题。
                     * 用于实现线程同步
                     * */
                     /**
                      * 线程 A 首先获取了锁 1,此时锁 1 被线程 A 持有。
                      * 同时,线程 B 获取了锁 2,锁 2 被线程 B 持有。
                      * 接着,线程 A 尝试获取锁 2,但是由于锁 2 已经被线程 B 持有,所以线程 A 进入等待状态,等待线程 B 释放锁 2。
                      * 同样地,线程 B 尝试获取锁 1,然而锁 1 被线程 A 持有,线程 B 也进入等待状态,等待线程 A 释放锁 1。
                      * 最终互相都进入等待状态。
                      * */
                    synchronized (lock1) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (lock2) {
                            System.out.println("Thread 1 acquired both locks.");
                        }
                    }
                }
            }
            class Thread2 implements Runnable {
                @Override
                public void run() {
                    synchronized (lock2) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (lock1) {
                            System.out.println("Thread 2 acquired both locks.");
                        }
                    }
                }
            }
            Thread t3 = new Thread(new Thread1());
            Thread t4 = new Thread(new Thread2());
            t3.start();
            t4.start();
        }
    
  • 活锁

    • 与死锁类似,但是线程并没有被阻塞,而是在不断地尝试执行操作,但由于相互协作的问题,始终无法完成任务。

    • 两个线程在遇到冲突时都主动让步,然后重新尝试,这样可能会导致它们一直在重复这个过程,而无法取得进展。

        // 模拟活锁
        /**
         * 两个线程都在不断地尝试获取一个共享资源,但由于相互让步,始终无法获取到资源。
         * */
        public static void livelock() {
            /**
             * AtomicInteger 是 Java 中的一个类,用于以原子方式对整数进行操作。
             * 它提供了在多线程环境下对整数进行安全的更新操作的方法,例如自增(incrementAndGet)、自减(decrementAndGet)等,而不需要使用传统的同步机制(如锁)。
             * 这样可以避免多线程竞争条件,提高并发性能。
             * */
            AtomicInteger resource = new AtomicInteger(1);
            class ThreadA implements Runnable {
                @Override
                public void run() {
                    while (resource.get() > 0) {
                        if (resource.get() == 1) {
                            System.out.println("Thread A wants resource.");
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            if (resource.get() == 1) {
                                System.out.println("Thread A yields.");
                                Thread.yield();
                                /**
                                 * yield()
                                 * 这个方法会使当前正在执行的线程让出 CPU 时间片,让其他具有相同优先级的线程有机会执行。
                                 * 但不能保证该线程在让出后就不再被调度执行,它只是一个提示,告诉系统可以调度其他线程。
                                 * */
                            }
                        }
                    }
                }
            }
            class ThreadB implements Runnable {
                @Override
                public void run() {
                    while (resource.get() > 0) {
                        if (resource.get() == 1) {
                            System.out.println("Thread B wants resource.");
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            if (resource.get() == 1) {
                                System.out.println("Thread B yields.");
                                Thread.yield();
                            }
                        }
                    }
                }
            }
            Thread t5 = new Thread(new ThreadA());
            Thread t6 = new Thread(new ThreadB());
            t5.start();
            t6.start();
        }
    
    
  • 资源耗尽

    • 多个线程无限制地创建资源而不释放,可能会导致系统资源耗尽,如内存泄漏、文件描述符耗尽等。
        // 模拟资源耗尽
        /**
         * 一个线程不断地创建大量的内存对象,可能导致内存溢出
         * */
        public static void resourceExhaustion() {
            class ResourceConsumer implements Runnable {
                @Override
                public void run() {
                    while (true) {
                        byte[] bytes = new byte[1024 * 1024];
                    }
                }
            }
            Thread t7 = new Thread(new ResourceConsumer());
            t7.start();
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("可能出现内存溢出等资源耗尽情况。");
        }
    
    

Java避免死锁

  • 避免嵌套加锁

  • 使用固定的加锁顺序,确保所有线程以相同的顺序获取这些锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class ResourcePair {
    /**
      *ReentrantLock 是 Java 中的一个同步机制。它是一种可重入的互斥锁,实现了 Lock 接口。ReentrantLock 与传统的同步代码块(使用 synchronized 关键字)相比,提供了更多的高级功能,例如尝试获取锁的限时等待、中断等待锁的线程等。它可以替代 synchronized 进行同步控制,在一些复杂的同步场景中非常有用。
      **/
    private final Lock lock1 = new ReentrantLock();
    private final Lock lock2 = new ReentrantLock();

    public void operation1() {
        // 以固定顺序获取锁
        lock1.lock();
        try {
            // 模拟操作 1 的一些代码
            System.out.println("Operation 1 started.");
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock1.unlock();
        }
    }

    public void operation2() {
        // 以固定顺序获取锁
        lock1.lock();
        try {
            // 模拟操作 2 的一些代码
            System.out.println("Operation 2 started.");
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock1.unlock();
        }
    }
}

public class DeadlockAvoidanceExample {
    public static void main(String[] args) {
        ResourcePair resources = new ResourcePair();
        Thread thread1 = new Thread(resources::operation1);
        Thread thread2 = new Thread(resources::operation2);
        thread1.start();
        thread2.start();
    }
}

还可以使用超时机制来尝试获取锁,如果在一定时间内无法获取锁,则放弃操作,避免长时间的等待导致死锁。同时,在设计多线程程序时,要仔细考虑线程之间的交互和资源的获取方式,以降低死锁的风险。

Java的集合框架

Java 中的集合框架是一个用于存储和操作对象集合的体系结构。它提供了一系列接口和类,包括 List、Set、Queue、Map 等,以及它们的实现类,如 ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap 等。这些类和接口提供了不同的功能和特性,以满足不同的需求

Java遍历Map集合

  • 使用for循环结合Map.Entry遍历
import java.util.HashMap;
import java.util.Map;

public class MapIterationExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 5);
        map.put("banana", 3);
        map.put("cherry", 7);

        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println("Key: " + key + ", Value: " + value);
        }
    }
}

可以同时获取键和值

  • 使用keySet遍历键,再根据键获取值
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapIterationExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 5);
        map.put("banana", 3);
        map.put("cherry", 7);

        Set<String> keys = map.keySet();
        for (String key : keys) {
            Integer value = map.get(key);
            System.out.println("Key: " + key + ", Value: " + value);
        }
    }
}

需要先获取键再查询值,需要查询两次。

  • 使用Java8的forEach方法和Lambda表达式
import java.util.HashMap;
import java.util.Map;

public class MapIterationExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 5);
        map.put("banana", 3);
        map.put("cherry", 7);

        map.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value));
    }
}
  • 使用迭代器遍历entrySet
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

public class MapIterationExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 5);
        map.put("banana", 3);
        map.put("cherry", 7);

        Iterator<Entry<String, Integer>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<String, Integer> entry = iterator.next();
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println("Key: " + key + ", Value: " + value);
        }
    }
}

Redis数据结构

  • 字符
  • 哈希
  • 列表
  • 集合
  • 有序集合
import redis.clients.jedis.Jedis;

public class RedisDataStructuresExample {
    public static void main(String[] args) {
        // 创建 Jedis 连接
        Jedis jedis = new Jedis("localhost", 6379);

        // 字符串操作
        jedis.set("myKey", "Hello Redis!");
        String value = jedis.get("myKey");
        System.out.println("String value: " + value);

        // 哈希操作
        jedis.hset("user:1", "name", "John");
        jedis.hset("user:1", "age", "30");
        String name = jedis.hget("user:1", "name");
        System.out.println("User name: " + name);

        // 列表操作
        jedis.lpush("myList", "item1");
        jedis.lpush("myList", "item2");
        String firstItem = jedis.lpop("myList");
        System.out.println("First item from list: " + firstItem);

        // 集合操作
        jedis.sadd("mySet", "element1");
        jedis.sadd("mySet", "element2");
        long setSize = jedis.scard("mySet");
        System.out.println("Set size: " + setSize);

        // 有序集合操作
        jedis.zadd("mySortedSet", 10, "itemA");
        jedis.zadd("mySortedSet", 20, "itemB");
        long rank = jedis.zrank("mySortedSet", "itemA");
        System.out.println("Rank of itemA: " + rank);

        // 关闭连接
        jedis.close();
    }
}

Redis设置分布式锁

还没学明白。。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值