1. 基本概念
- 同步
多个线程执行某一个方法(或代码块)时,如果某个线程执行完该方法(或代码块)之后下一个线程才进入该方法(或代码块),则称该方法(或代码块)为同步的。 - 异步
多个线程执行某一个方法(或代码块)时,如果某个线程尚未执行完该方法(或代码块),下一个线程已经进入该方法(或代码块),则称该方法(或代码块)为异步的。 - start()与run()的区别
Thread.java类中的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用Thread中的run()方法,也就是使线程得到运行,启动线程,具有异步执行的效果。如果调用代码thread.run()就不是异步执行了,而是同步,那么此线程对象并不交给“线程规划器”来进行处理,而是由main主线程来调用run()方法,也就是必须等run()方法中的代码执行完后才可以执行后面的代码。 - 非线程安全
- 非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程。
- “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了。
- 解决非线程安全问题使用的是synchronized关键字。
- Thread.currentThread()与this的区别
Thread t1 = new Thread(target);
在上述代码中,Thread.currentThread()指的是t1线程,this指的是target线程。 - 可重入锁
- 在一个synchronized方法/块内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。
- 当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。
2. 创建线程的方式
- 继承Thread类
public class MyThread extends Thread {...}
- 实现Runnable接口
public class MyRunnable implements Runnable {...}
- 因Thread类也实现了Runnable接口,故不管是Thread继承类创建的对象还是Runnable实现类创建的对象,均可通过构造函数Thread(Runnable target)来创建线程,并交给该线程进行调用。
Thread类源码:
public class Thread implements Runnable {...}
线程对象构建:
Thread thread1 = new Thread(myThread);
Thread thread2 = new Thread(myRunnable);
当调用thread1.run()(或thread2.run())方法时,myThread(或myRunnable)应用的对象被绑定到一个private变量target上,当thread1.run()(或thread2.run())被执行的时候,它会调用target.run()方法。
3. 常用方法
- isAlive()
测试线程是否处于活动状态。 - sleep(long millis)
在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()
返回的线程。 - interrupt()
停止线程,但该方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。 - interrupted()
测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能。 - isInterrupted()
测试线程Thread对象是否已经是中断状态,但不清除状态标志。 - yield()
放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片。 - setPriority(int newPriority)
设置线程的优先级,优先级分为1~10这10个等级,如果小于1或大于10,则JDK抛出异常IllegalArgumentException()。
- 线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A是一样的。
- setDaemon(boolean on)
将当前线程设置为守护线程。
4. 停止线程的方式
- 当run()方法执行完后线程终止;
- 使用stop()方法强行终止线程;
- 该方法已作废,因为如果强行让线程停止则有可能使一些清理性的工作得不到完成。另外一个情况就是对锁定的对象进行了“解锁”,导致数据得不到同步的处理,出现数据不一致的问题。
- 使用interrupted()方法;
- 该方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。
- 异常法
- 建议使用异常法来实现线程的停止,因为在catch块中可以对异常的信息进行相关的处理,而且使用异常流能更好、更方便地控制程序的运行流程,不至于代码中出现多个return;造成污染。
- 使用return停止线程;
- 将方法interrupt()与return结合使用也能实现停止线程的效果。
5. 同步的性质
- 同步不可以继承。
- 和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。
- 当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。
- Java还支持对“任意对象”作为“对象监视器”来实现同步的功能。这个“任意对象”大多数是实例变量及方法的参数,使用格式为synchronized(非this对象)。
- 使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁。
- 同步的三个结论:
- 当多个线程同时执行synchronized (x) {…}同步代码块时呈同步效果;
- 当其他线程执行x对象中synchronized同步方法时呈同步效果;
- 当其他线程执行x对象方法里面的synchronized (this)代码块时也呈现同步效果。
- synchronized关键字加到static静态方法上是给Class类上锁,而加到非static静态方法上是给对象上锁。Class锁可以对类的所有对象实例起作用。
- 大多数情况下,同步synchronized代码块都不使用String作为锁对象,而改用其他,比如
new Object()
实例化一个Object对象,但它并不放入缓存中。 - 只要对象不变,即使对象的属性被改变,运行的结果还是同步。
- 关键字synchronized可以使多个线程访问同一个资源具有同步性,而且它还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能,即具有volatile关键字的功能。也就是说,synchronized具有互斥性和可见性。
6. 死锁
- 只要互相等待对方释放锁就有可能出现死锁。
- JDK自带工具检测死锁:
- jps命令,可显示当前正在运行的所有java进程的pid。
- jstack命令,用
jstack -l pid
可查询对应pid中的各个线程状态和死锁情况。
7. volatile关键字
- 关键字volatile的主要作用是使变量在多个线程间可见,其原理为强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
- 主要使用场合是在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用,也就是用多线程读取共享变量时可以获得最新值使用。
8. volatile和synchronized的比较
- volatile性能比synchronized要好,volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。
- 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
- volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性。
- volatile解决的是变量在多个线程之间的可见性;而synchronized解决的是多个线程之间访问资源的同步性。
备注
- 使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。即执行start()方法的顺序不代表线程启动的顺序。
- System.out.println()方法内部是同步的。
- 线程安全包括原子性和可见性两个方面,Java的同步机制都是围绕这两个方面来确保线程安全的。