线程汇总(1)

参考博客

1. 线程实现

  • 继承Thread类
  • 实现Runnable接口

实现Runnable接口比继承Thread类具有以下优势:
1.可以避免Java单继承带来的局限性
2. 适合多个相同程序代码的线程区处理统一资源的情况,即对共享资源的处理

2. 线程中断

  • 使用interrupt()方法中断线程,当线程处于运行状态时,另一个线程调用了它的interrupt方法来中断它,该方法只是在目标线程中设置一个标志,表示它已经被中断,并立即返回。
  • 如果线程处于sleep状态时,被其他线程中断,会抛出InterruptedException异常
  • 如果线程在调用interrupt方法后直到遇到休眠后,才会抛出InterruptedException

判断中断状态:
1. isInterrupeted实例方法:通过调用对象.isInterrupted()来检查线程的中断,如果线程被中断,该方法返回true,并且不会清除标志位,但是遇到sleep方法后,进入到catch块汇总,会清除标志位。
2. interrupted静态方法:通过Thread.interrupted()方法来检查当前线程的中断状态,如果当前线程处于中断状态,返回true,并且隐式地清除标志位,即重置为false,
3. join方法:如果在一个线程A中调用另一个线程B的join方法,那么A线程会等待B线程执行完毕后再执行join方法的代码
4. yield静态方法:调用Thread.yield()方法后,会把CPU执行权让给同等级或者之上的线程,如果没有相同级别的线程在等待CPU的执行权,则该线程继续执行

3.线程的挂起,恢复与终止

  • 如果在不合适的时候挂起线程,此时可能会发生死锁或数据的不一致。
  • 通过设置标志位或等待-通知来实现线程挂起和恢复的策略。在线程的指定位置实现线程的挂起和恢复,而不用担心不确定性。
  • 终止线程同样适用标志位。

4. 守护线程

Java中由两类线程:用户线程和守护线程,守护线程是会其他用户线程的运行提供服务。通过setDaemon(true)方法设置该线程为守护线程。
注意点:
1. setDaemon(true)必须在调用线程的start()方法之前设置
2. 在守护线程中产生的线程也为守护线程

5. 线程阻塞

线程何时处于中断状态:
1. 当线程执行Thread.sleep()时,它一直阻塞到指定的时间之后或者被另一个线程中断
2. 当线程遇到wait语句式,它会一直阻塞到接到通知(notify()),被中断或经过了指定的时间
3. 进行IO操作时会阻塞,比如:InputStream的read和write方法,不可被中断
4. 线程尝试获取被其他线程占用的锁时,也会阻塞,不可被中断

6. volatile修饰符

  • 在当前的Java内存模型下,线程可以把变量保存在本地内存中,而不是直接在主内存中进行读写。这就可能会导致数据的不一致问题,要解决这个问题,需要把变量声明为volatile,每次使用它都到主内中进行读取。
  • Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只有当线程进入或者离开同步代码块时才将私有拷贝与共享内存中的原始值进行比较。由于volatile屏蔽掉了JVM中的代码优化,所以在效率上比较低。
  • JIT或HotSpot编译器在server模式client模式编译不同,server模式为了使线程运行更快,如果其中一个线程更改了变量boolean flag 的值,那么另外一个线程会看不到,因为另外一个线程为了使得运行更快所以从寄存器或者本地cache中取值,而不是从内存中取值,那么使用volatile后,就告诉不论是什么线程,被volatile修饰的变量都要从内存中取值。
  • 假如有两个线程分别读写volatile变量时,线程A写入了某volatile变量,线程B在读取该volatile变量时,便能看到线程A对该volatile变量的写入操作,关键在这里,它不仅会看到对该volatile变量的写入操作,A线程在写volatile变量之前所有可见的共享变量,都将立即变得对B线程可见。

7. synchronized修饰符

在并发编程中,多线程同时并发访问的资源叫做临界资源。采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁

  • 类的每个实例都有自己的对象级别锁。当一个线程访问实例对象中的synchronized同步代码块或同步方法时,该线程需要获取该实例的对象级别锁。其他线程如果要访问synchronized同步代码块或同步方法,会阻塞等待,直到前面的线程从同步代码中或方法中退出。
  • 持有一个对象级别锁不会阻止该线程被交换出(即失去CPU执行权),也不会阻塞其他线程访问同一实例对象中的非synchronized代码
  • 类级别锁被特定类的所有实例共享,用于控制static成员变量以及static方法的并发访问
  • synchronized的锁机制是可重入的
  • synchronized的另一个功能:保证可见性

synchronized和volatile之间的区别:
1.volatile是一种稍弱的同步机制在访问volatile变量时不会加锁,因此就不会阻塞
2. 加锁既可以确保可见性又可以确保原子性,而volatile只能保证可见性
3. 当且仅当满足以下条件时,才应该使用volatile。1)对变量的写入操作不依赖变量的当前值

8.多线程环境中使用集合

Vector和Hashtable是线程安全的。集合本质上是非多线程安全的,当多个线程与集合交互时,为了使它多线程安全,必须采取额外的措施。在Collections有多个静态方法,它们可以获取通过同步方法封装非同步集合而达到线程安全。

public static Collection synchronizedCollection(Collection c)
public static List synchronizedList(list l)
public static Map synchronizedMap(Map m)
public static Set synchronizedSet(Set s)
public static SortedMap synchronizedSortedMap(SortedMap sm)
public static SortedSet synchronizedSortedSet(SortedSet ss)


/*
注意,ArrayList实例马上封装起来,不存在对未同步化ArrayList的直接引用(即直接封装匿名实例)。
*/
/*这是一种最安全的途径。如果另一个线程要直接引用ArrayList实例,它可以执行非同步修改。
*/
List list = Collection.synchronizedList(new ArrayList());

9. 死锁

线程A当前持有互斥所锁lock1,线程B当前持有互斥锁lock2。接下来,当线程A仍然持有lock1时,它试图获取lock2,因为线程B正持有lock2,因此线程A会阻塞等待线程B对lock2的释放。如果此时线程B在持有lock2的时候,也在试图获取lock1,因为线程A正持有lock1,因此线程B会阻塞等待A对lock1的释放。二者都在等待对方所持有锁的释放,而二者却又都没释放自己所持有的锁,这时二者便会一直阻塞下去。这种情形称为死锁。

规避死锁:
1、只在必要的最短时间内持有锁,考虑使用同步语句块代替整个同步方法;
2、尽量编写不在同一时刻需要持有多个锁的代码,如果不可避免,则确保线程持有第二个锁的时间尽量短暂;
3、创建和使用一个大锁来代替若干小锁,并把这个锁用于互斥,而不是用作单个对象的对象级别锁;

10. 可重入锁

获取锁的操作的粒度是线程。重入的一种实现方法是,为每个锁关联一个计数值和一个拥有者。

public class Father {  
    public synchronized void doSomething(){  
        //...
    }  
}  

public class Child extends Father {  
    public synchronized void doSomething(){  
        //... 
        super.doSomething();  
    }  
}  

同一线程在调用本类中其他synchronized方法/块或父类中的synchronize方法/块,都不会阻碍该线程执行,因为互斥锁是可重入的。

你可能感兴趣的:(线程汇总(1))