多线程学习笔记

尊重他人劳动成果,转载请注明出处:
http://blog.csdn.net/czd3355/article/details/73168419

此文章纯属记录 Java 多线程的一些知识点,涉及程度比较入门。如果读者是想深入学习的话,或许其他文章会更合你的胃口。内容基本来自《Java 多线程编程核心技术》

1. 线程和进程的定义区别

进程执行着的应用程序。

线程是在进程中独立运行的子任务。

2. 多线程访问成员变量与局部变量

如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,它们对该成员变量是彼此影响的,也就是说一个线程对成员变量的改变会影响到另一个线程。

如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝(即便是同一个对象中的方法的局部变量,也会对每一个线程有一个拷贝),一个线程对该局部变量的改变不会影响到其他线程。

3. 线程各状态描述

这里写图片描述
这里写图片描述

4. synchronized

  • 关键字 synchronized 取得的锁都是对象锁,而不是把一段代码或方法作为锁。因此哪个线程先执行 synchronized 关键字的方法,哪个线程就持有该方法所属对象的锁。此时其他线程就只能处于等待状态,前提是这多个线程访问同一个对象
  • 同步方法和同步代码块的区别是什么?
    • 同步方法默认使用当前类对象(即 this)作为锁;
    • 同步代码块可以选择任意对象作为锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码,而不是整个方法。
      注:同步是一种高开销的操作,因此应该尽量减少同步的内容。 通常没有必要同步整个方法,使用 synchronized 代码块同步关键代码即可。
  • 多个线程调用同一个对象中的不同 synchronized 同步方法或 synchronized(this) 同步代码块时,调用效果是同步的。
  • 若使用 synchronized(非 this 对象 x) 的方式来实现同步的话有一下三个结论:
    • 当多个线程同时执行 synchronized(x){} 同步代码块时呈同步效果
    • 当其他线程执行 x 对象中的 synchronized 同步方法时呈同步效果
    • 当其他线程执行 x 对象方法中的 synchronized(this) 代码块时呈同步效果
  • 如果 synchronized 加到静态方法或写成 synchronized(ClassName.class) 上,则说明是对 当前的 *.Java 文件对应的 Class 类加锁,因此该对象不管 new 出多少个,锁都只有一个。不同于将 synchronized 加到非静态方法上,这是给当前对象上锁。new 出多少个对象就有多少个锁
  • 线程出现异常,锁会自动释放。
  • 同步不具有继承性。
  • 只要锁对象不变,即使对象的属性被改变了,运行的结果还是同步的。

5. volatile

  • 关键字 volatile 的主要作用是使变量在多个线程间可见。即被 volatile 关键字修饰的变量每次被线程访问时,都强迫从主内存中重读该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存。这样任何时候,不同的线程总能看到该变量的最新值。
    这里写图片描述
    关于可见性和 volatile 更为详细的内容可以参考这篇文章:
  • Java 中的多线程之内存可见性

  • 使用 volatile 的条件

    • 对变量的写操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值
    • 该变量不会与其他状态变量一起纳入不变形条件中
    • 在访问变量时不需要加锁
  • volatile 的典型用法:检查某个状态标记以判断是否退出循环。例如:单例模式中对实例的判断
volatile SingletonClass instance=null;
...
if(instance==null){
    instance=new SingletonClass();
}
...

6. wait()

  • wait() 方法的作用是使当前执行代码的线程进行等待。
  • 它是 Object 类的方法,并且这个 Object 对象就是锁对象,同时和调用 notify() 的对象也是同一个对象,即两个方法拥有同一个对象锁
  • 只能在同步方法或同步代码块中调用,否则抛出 IllegalMonitorStateException
  • 执行后会释放对象锁,进入等待队列
  • wait(long timeout):等待 timeout 时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。

7. notify()

  • 只能在同步方法或同步代码块中调用,否则抛出 IllegalMonitorStateException
  • 随机唤醒一个 wait 状态的线程,唤醒后该线程并不能马上获取对象锁。因为
    在执行 notify() 方法后,当前线程不会马上释放该对象锁,所以呈 wait 状态的线程也就不能马上获取该对象锁,而要等到执行 notify() 方法的线程将程序执行完,即退出同步方法或同步代码块后,wait 状态的线程才可以获取该对象锁。
  • 如果调用 notify() 时没有处于等待状态的线程,则该方法会被忽略。
  • notifyAll() 方法可以使所有正在等待队列中等待同一对象锁的全部线程唤醒,进入可运行状态

8. Lock 的使用

8.1 Lock 实现类简单介绍

Lock 是一个接口,有以下 3 个实现类
- ReentrantLock:它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大
- ReentrantReadWriteLock 有两个嵌套类
- ReentrantReadWriteLock.WriteLock
- ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock 类是一种读写锁,使用它可以加快运行效率。
读写锁表示也有两个锁(对应上方两个嵌套类),一个是读操作相关的锁,也称共享锁;另一个是写操作相关的锁,也叫排他锁。
并且多个读锁之间之间不互斥,读锁与写锁互斥(不管是先读后写还是先写都读,只要是同一个锁就会互斥),写锁与写锁之间互斥。在没有线程进行写入操作时,进行读取操作的多个线程都可以获取读锁,而进行写入操作的线程只有在获取写锁后才能进行写入操作。即多个线程可以同时进行读取操作,但同一时刻只允许一个线程进行写入操作

8.2 lock(),await() 和 signal()

  • lock() 的作用是获取锁,是 Lock 接口的方法。该方法结合 unlock() 方法(作用:释放锁)也可以达到线程同步的作用。
  • Object 类的 wait() 方法相当于 Condition 类的 await() 方法
  • Object 类的 wait(long timeout) 方法相当于 Condition 类的 await(long timeout,TimeUnit unit) 方法
  • Object 类的 notify() 方法相当于 Condition 类的 signal() 方法
  • Object 类的 notifyAll() 方法相当于 Condition 类的 signalAll() 方法
  • 彼此不同点:Condition 类可以实现多路通知功能,即一个 Lock 对象里面可以创建多个 Condition 对象,线程对象可以注册在指定的 Condition 中。从而可以有选择性地进行线程通知,在调度线程上更加灵活。对比于 notify()/notifyAll(),被通知的线程是由 JVM 随机选择的。使用 synchronized 就相当于整个 Lock 对象只有一个单一的 Condition 对象,所有的线程都注册在它一个对象身上。

8.3 锁 Lock 分为公平锁和非公平锁。

  • 公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先进先出的顺序(FIFO)
  • 非公平锁是一种锁的抢占机制,是随机获得锁的。这个方式可能会造成某些线程一直拿不到锁,所以也就是不公平的。

ReentrantLock 是 Lock 接口的实现类,有两个构造方法,一个是 public ReentrantLock(boolean fair); 参数 fair:如果此锁应该使用公平的排序策略,则该参数为 true。另一个是无参的,这等价于 ReentrantLock(false);

9. join()

演示代码:

public class Test(){
    public static void main(){
        try{
            MyThread myThread = new MyThread();
            myThread.join();
            System.out.println("当myThread线程的run方法执行完后才能执行该语句");
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

结合上述代码,我们来解释一下 join() 方法的作用:使所属的线程对象 myThread 正常执行 run() 方法中的任务,而使当前线程,即 main 线程进行无限期的阻塞,等待线程 myThread 销毁后再继续执行线程 main 后面的代码。

join(long millis) 方法:等待 millis 毫秒,执行效果和 sleep(long millis) 差不多,只在同步的处理上有区别。
因为 join(long millis) 方法内部使用了 wait(long timeout) 方法来实现,所以 join(long millis) 方法具有释放锁的特点,而 sleep(long millis) 方法则不会释放锁

10. 通过管道进行线程间通信

字节流方式
- PipedOutputStream
- PipedInputStream

字符流方式
- PipedWriter
- PipedReader

使用 outputStream.connect(inputStream);inputStream.connect(outputStream); 使两个 Stream 之间产生通信链接,这样才可以将数据进行输入输出。

n. 其他知识点

  • 线程的优先级具有继承性,即 A 线程启动了 B 线程,则 A,B 线程的优先级一样
  • tield() 方法表示放弃当前 CPU 资源。但有可能刚放弃又马上获得 CPU 资源
  • 如何确保 N 个线程可以访问 N 个资源同时又不导致死锁?
    指定线程获取锁的顺序。通过这种方法,所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值