Java 知识 复习

1. 并发编程

下面的程序需要导入 java.util.concurrent.*

1.1 用户线程 (四种方式)

创建用户线程的方式有4种,分别是 继承 Thread类、实现 Runnable 接口、实现 Callable 接口、以及使用线程池。

1.1.1 继承自 Thread 类

这种方式比较简单,通过继承 Thread 类,并重写 run() 方法。在使用的时候 实例化并调用 start() 方法即可。

public class MyMain {
    public static void main(String[] args) {

        MyThread myThread = new MyThread();
        myThread.start();
    }
}

class MyThread extends Thread{

    @Override
    public void run() {
        System.out.println("继承自Thread创建线程!");
    }
}

1.1.2 实现 Runnable 接口

实现 Runnable 接口并重写 run()方法,但由于没有 start() 方法启动线程,所以需要借助 Thread 来启动。

public class MyMain {
    public static void main(String[] args) {

        Runnable myRunnableImpl = new MyRunnableImpl();

        Thread thread = new Thread(myRunnableImpl);
        thread.start();
    }
}

class MyRunnableImpl implements Runnable{

    @Override
    public void run() {
        System.out.println("实现Runnable创建线程!");
    }
}

1.1.3 实现 Callable 接口

通过实现 Callable 接口创建线程。
与 Runnable 接口的不同:

  • Callable 需要重写的是 call() 方法
  • Callable 有返回值,所以需要借助 FutureTask 这个容器用于获取结果
  • 注意 通过 FutureTask 容器获取计算结果的方法 get() 会有异常抛出
public class MyMain {
    public static void main(String[] args) {

        Callable<String> myCallable = new MyCallableImpl();

        FutureTask<String> futureTask = new FutureTask<>(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();

        try{
            String result = futureTask.get();
            System.out.println(result);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

class MyCallableImpl implements Callable<String>{

    @Override
    public String call(){

        System.out.println("实现Callable接口创建线程!");
        return "这是 call 方法的返回值!";
    }
}

小结

以上三种方式创建线程,最后都是通过 Thread 进行执行。Callable 有结果返回,所以用 FutureTask 容器保存,但最后也还是交由 Thread 执行。

1.4 线程池创建线程

这种方式避免了线程的反复创建和销毁。
首先通过 Executors 工具类的 newFixedThreadPool() 创建线程池(ExecutorsService)
ExecutorsService 内有两个成员方法可以提交线程,分别是 submit() 和 execute()
区别:

  • 方法的参数
    • submit() 方法 接受的变量可以是 Runnable、Callable、Thread
    • execute() 方法 不能接受 Callable
  • 返回值
    • submit() 方法的返回值为 Future,可以接受 Callable 的 call 方法的返回值
    • execute() 方法无返回值

最后,线程池使用结束,可以使用 shutDown() 方法关闭线程池

public class MyMain {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        ExecutorService pool = Executors.newFixedThreadPool(5);

        Callable<String> myCallable = new MyCallableImpl();
        Runnable myRunnable = new MyRunnableImpl();
        Thread myThread = new MyThread();

        // submit 可以获取结果
        Future<String> result = pool.submit(myCallable);
        System.out.println(result.get());

        // execute 无返回结果
        pool.execute(myRunnable);
        pool.execute(myThread);


        // 关闭线程池
        pool.shutdown();
    }
}

1.2 守护线程

守护线程,俺一开始以为它是个很强的玩意,守护嘛。但实际上它对于程序而言似乎并不是那么重要。
守护线程,如果其它用户线程(非守护线程)终止了,那么守护线程也就终止,与守护线程是否执行完成没有关系。所以一般用于执行一些任务、服务,如定期缓存清理等。
因为守护线程随时都可能被中止,不能保证完整性,所以守护线程不用于实现一些关键业务逻辑。而且,守护线程的优先级低,通常低于用户线程,所以,资源优先提供给用户线程。

通过 Thread 的成员方法 setDaemon(true) 可以将线程设置为守护线程。
值得注意:守护线程需要在用户线程启动后才能启动,否则会发送 IllegalThreaStatusException 异常

public class MyMain {
    public static void main(String[] args){

        Thread myThread = new MyThread();
        
        myThread.setDaemon(true);
        myThread.start();
    }
}

2. Java 锁机制

Java 的锁机制,这里将简单介绍 synchronized关键字、ReentrantLock、ReadWriteLock
后两个需要 import java.util.concurrent.locks.*;

2.1 synchronized关键字

synchronized 关键可以修饰方法,也可以作用在对象上。

public class MyMain {
    public static void main(String[] args) {

        Thread thread = new MyThread();

        ExecutorService pool = Executors.newFixedThreadPool(12);
        pool.execute(thread);
        pool.execute(thread);
        pool.execute(thread);
        pool.execute(thread);
        pool.execute(thread);

        pool.shutdown();
    }
}

class MyThread extends Thread{
    private static Integer num = 10;

    @Override
    public void run() {
        while (sell());
    }

    private synchronized boolean sell(){

        if (num<=0)
            return false;

        System.out.printf("%s---%d\n",Thread.currentThread().toString(),num);
        num -= 1;

        return true;
    }
}
class MyThread extends Thread{
    private static Integer num = 1000;

    @Override
    public void run() {
        while (sell());
    }

    private boolean sell(){

        if (num<0)
            return false;

        synchronized (num){
            System.out.printf("%s---%d\n",Thread.currentThread().toString(),num);
            num--;
        }

        return true;
    }
}

2.2 ReentrantLock

使用ReentrantLock,需要在实例化的时候传入 ReentrantLock对象,需要上锁的时候,调用 lock.lock(),解锁的时候使用 lock.unlock() 即可。

public class MyMain {
    public static void main(String[] args) {

        ReentrantLock lock = new ReentrantLock();

        Runnable myRunnable = new MyRunnableImpl(lock);
        ExecutorService pool = Executors.newFixedThreadPool(5);

        pool.execute(myRunnable);
        pool.execute(myRunnable);
        pool.execute(myRunnable);

        pool.shutdown();
    }
}

class MyRunnableImpl implements Runnable{
    private static Integer num = 100;
    private final ReentrantLock lock;

    MyRunnableImpl(ReentrantLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (sell());
    }

    private boolean sell(){

        lock.lock();
        try{
            if (num<0)
                return false;
            
            System.out.printf("%s---%d\n",Thread.currentThread().toString(),num);
            num--;
        }finally {
            lock.unlock();
        }

        return true;
    }
}

2.3 ReentrantReadWrite

这是读锁和写锁,允许多个线程同时持有读锁,但只允许一个线程持有写锁。

public class MyMain {
    public static void main(String[] args) {

        ReadWriteLock lock = new ReentrantReadWriteLock();

        Callable<String > myCallable = new MyCallableImpl(lock);
        ExecutorService pool = Executors.newFixedThreadPool(5);

        pool.submit(myCallable);
        pool.submit(myCallable);
        pool.submit(myCallable);

        pool.shutdown();
    }
}

class MyCallableImpl implements Callable<String>{
    private static Integer num = 100;
    private ReadWriteLock lock;

    MyCallableImpl(ReadWriteLock lock){
        this.lock = lock;
    }
    @Override
    public String call() throws Exception {
        while (sell());
        return null;
    }

    public boolean sell(){

        lock.readLock().lock();
        try {
            if (num<0)
                return false;
        }finally {
            lock.readLock().unlock();
        }

        lock.writeLock().lock();
        try{
            System.out.printf("%s****%d\n",Thread.currentThread(),num);
            num--;
        }finally {
            lock.writeLock().unlock();
        }
        return true;
    }
}
posted @ 2023-10-13 18:25  思而行  阅读(26)  评论(0)    收藏  举报