1、进程与线程

  一个程序就是一个进程,一个程序中的多个任务被称为线程。进程是资源分配的基本单位,线程是进程中执行运算的最小单位,亦是调度运行的基本单位。多线程的好处并发执行提高了程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态

2、线程的实现方式

  • 继承java.lang.Thread类
  • 实现java.lang.Runnable接口,然后交给java.lang.Thread类执行
  • 实现java.util.concurrent.Callable接口,作为参数构建一个java.util.concurrent.FutureTask对象,然后交给java.lang.Thread执行。
  • 使用java.util.concurrent.Executors类的线程池
public class ThreadTest {
    public void test1() {
        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "这是一个返回值";
            }
        };
        FutureTask<String> futureTask = new FutureTask<String>(callable);
        new Thread(futureTask).start();
        try {
            System.out.println(futureTask.get()); // get方法会阻塞主线程的执行
        } catch (Exception e) {
        }
        System.out.println("执行完毕");
    }
}

3、wait()、notify()/notifyAll()、sleep()

  wait()、notify()/notifyAll()方法是Object对象中的方法,他们必须结合synchronized关键字使用,主要用于线程间的通讯。wait()会交出对象的控制权,然后让线程处于等待状态;notify()会唤醒一个等待这个对象控制权的线程(如果有多个线程,唤醒哪个线程由操作系统决定),等待线程在获取对象控制权后会继续执行(注意:调用notify时不会释放对象的控制权,必须运行到synchronized块结束);notifyAll()会唤醒所有等待线程。

  sleep()是Thead类中的静态方法,用户将线程暂停一段时间,时候到后程序继续执行,不涉及到线程通讯和对象锁。

4、interrupt()、interrupted()、isInterrupted()

  这3个方法都是Thread类中的方法。

  interrupt用于中断线程,如果需要中断的线程不是interrupt()调用者线程,系统会检查权限,只有权限允许的情况下才会中断,否则会报错。中断线程仅仅是指设置一下中断状态标记,并不会停止线程。当线程处于wait、sleep、join阻塞状态时,中断线程会打破这种阻塞,并让这些方法抛出InterruptedException异常,并且中断标志状态还原。

  interrupted是一个静态方法,用于判断当前线程是否处于中断状态,调用后会清除中断标志。

  isInterrupted()用于判断线程是否处于中断状态,它不会清除中断标志。

  实际中这几个用到不多,感觉就是为了解除死锁用的。

5、线程池

  线程的创建要花费昂贵的资源和时间,如果任务来了才创建创建线程那么响应时间会边长,而且一个进程能创建的线程有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们就被称为线程池。从JDK1.5起,Java提供了Executor框架来创建不同的线程池。

  更多关于线程池的使用查看:Java线程池ThreadPoolExecutor&&Executors

6、CyclicBarrier和CountDownLatch

  CyclicBarrier和CountDownLatch都具有计数功能,用于多线程间的协作。

  详细参考:Java并发编程-CountDownLatch&CyclicBarrier

7、线程安全

  提到线程安全,我们就必须了解一下并发编程的三个问题:

  • 原子性:即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。代码 i++、j=i、k=i+j等就不具备原子性,i++有取值、运算、赋值3个操作指令
  • 可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
  • 有序性:程序执行的顺序按照代码的先后顺序执行,CPU有时会优化指令排序,所以代码的执行运行不一定是我们代码的书写顺序。

  在并发编程中,只有上面出现其中任何一个问题都可能会导致程序运行不正确。

  相关功能:Java锁关键字volatile

8、ThreadLocal

  ThreadLocal一般称为线程本地变量,它将变量与线程绑定在一起,为每个线程维护一个独立的变量副本,并且将对象的可见范围维护在同一个线程内。线程变量会存储在ThreadLocalMap对象中(看着是个map,事实它内部只有数组,没有链表,但也有相似特征,数组初始化容量都为16,都是双倍扩展),它是Thread的内部变量,初始为空。

  应用中我们会定义多个ThreadLocal对象,ThreadLocal在初始化时就确定了threadLocalHashCode(通过AtomicInteger增长实现),也就是确定了ThreadLocalMap内部数组中的位置。

  在Web应用系统中,我们常通过拦截器将用户登录信息存储在ThreadLocal中,这样我们就可以随时随地获取到登录用户信息。

9、线程通讯

  • 共享变量
  • wait, notify,notifyAll(这3个方法是Object对象中的方法,且必须与synchronized关键字结合使用)
  • CyclicBarrier、CountDownLatch
  • Lock/Condition机制
  • 管道,创建管道输出流PipedOutputStream和管道输入流PipedInputStream
  • 利用LockSupport

  详细代码示例见:Java并发编程-线程间通讯

 

posted on 2020-05-03 21:49  玄同太子  阅读(215)  评论(0编辑  收藏  举报