1、并发和并行的区别
并发是指在同一时刻只能有一个线程运行,并行是指在同一时刻有多个线程同时运行。
并发(concurrent)是在同一个cpu上同时(不是真正的同时,而是看起来是同时,因为cpu要在多个程序间切换)运行多个程序。
并行(parallel)是多个CPU同时(是真正的同时,每个cpu运行一个程序)运行多个程序。
并发的实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。
并行性指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。
2、守护线程和用户线程
Java线程分为两种:用户线程和守护线程
守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个守护线程,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程(用户线程,就是应用程序里的自定义线程)结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
守护线程和用户线程没有本质的区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意以下几点:
(1)thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出IllegalThreadStateException异常。不能把正在运行的常规线程设置为守护线程。
(2)在Daemon线程中产生的新线程也是Daemon的。
(3)守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
3、线程的六种状态
Java中线程的状态分为六种:
1. 新创建(NEW):新创建了一个线程对象,但还没有调用start()方法。
2. 可运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“可运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
3. 阻塞(BLOCKED):表示线程阻塞于锁。
4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
6. 终止(TERMINATED):表示该线程已经执行完毕。
1. 初始状态
实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。
2、可运行状态
2.1. 就绪状态
就绪状态是指尚未获取到CPU执行权的状态。
下面几种方式都可以使得线程进入就绪状态:
创建线程之后调用线程的start()方法,或该线程调用的Thread.sleep()方法结束,或在该线程中其他线程调用的join()结束,或其他线程调用notify或notifyAll方法唤醒该线程,或该线程获取到了同步方法的锁对象,或该线程的CPU时间片用完了,或在该线程中调用Thread.yield()方法。
锁池里的线程拿到对象锁后,进入就绪状态。
2.2. 运行中状态
线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
3. 阻塞状态
阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
4. 等待
处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。注意:进入Lock的lock()方法是waiting状态,不是阻塞状态,只有在进入synchronized关键字修饰的方法或代码块时才可能是阻塞状态。
5. 超时等待
处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
6. 终止状态
当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
当线程想要进入同步方法或同步代码块时,或已进入同步方法但调用了wait方法并被其他线程通过notify/notifyAll唤醒后,该线程会尝试获取锁,如果获得锁,进入RUNNABLE状态,否则被加入同步队列进入BLOCKED状态等待获取锁。
sleep、join、yield、wait方法的比较:
Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
thread.join()/thread.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程会释放线程t对象锁,不会释放其他对象锁(如果有的话)。线程t执行完毕或者millis时间到,当前线程一般情况下进入RUNNABLE状态,也有可能进入BLOCKED状态(因为join是基于wait实现的)。
obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(
long deadlines),当前线程进入WAITING/TIMED_WAITING状态。对比wait方法,不需要获得锁就可以让线程进入WAITING/TIMED_WAITING状态,需要通过LockSupport.unpark(Thread thread)唤醒。LockSupport.park()不会释放锁资源。
多个线程同时访问一个类的实例变量和静态变量时,是否需要给实例方法和静态方法加锁?
package cn.spring.test;
/**
* MultiThreadTest继承了Thread,可以当做线程使用
*/
public class MultiThreadTest extends Thread {
//类变量(属于MultiThreadTest类,多个MultiThreadTest对象可以共享)
private static int count;
//成员变量(属于具体某个MultiThreadTest对象,多个MultiThreadTest对象之间互不影响)
//private int count;
/**
* 该方法中有static和synchronized两个关键字修饰
* 1、若访问对象的成员变量,方法不能用static修饰,静态方法不能访问非静态属性。为了保证
* 多线程之间共享资源(此处指多个线程访问同一个MultiThreadTest对象)的安全性,必须加锁。
* 2、若访问类的静态变量,方法可以不用static修饰。但若多个线程访问多个MultiThreadTest
* 对象,即使方法上加锁,由于方法属于每个MultiThreadTest对象私有,多个线程获取到的锁是
* 多个对象的,多个线程的锁是不同的,而类变量是多个MultiThreadTest对象共有,那么多个线
* 程通过多个MultiThreadTest对象访问类变量时,就会不安全(不同的锁不会引起线程间阻塞)。
* 所以必须使用static修饰方法,使方法和类变量都变成多个MultiThreadTest对象共享的。
*/
private static synchronized void addCount(){
//局部变量(局部变量是线程安全的,多线程之间无需加锁,不存在资源共享)
//int count = 0;
for (int i=0;i<1000;i++){
count++;
}
System.out.println("当前线程:"+Thread.currentThread().getName()+"得到的count:"+count);
}
//由于MultiThreadTest继承了Thread,可以当做线程使用
public void run(){
addCount();
}
public static void main(String[] args){
//此处创建MultiThreadTest对象是为了调用其中的方法
MultiThreadTest multiThreadTest = new MultiThreadTest();
multiThreadTest.test1();
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("===============11111111111111==============");
multiThreadTest.test2();
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("===============22222222222222==============");
multiThreadTest.test3();
}
/**
* 由于MultiThreadTest继承了Thread,故可当做线程使用。该方法创建了十个MultiThreadTest
* 对象,即十个线程。如果MultiThreadTest中没有类变量(即静态变量),即使有成员变量,各个
* 线程之间不存在共享变量,不会对彼此产生影响。如果有类变量,方法上必须加锁,而且方法上
* 要用static修饰,否则对共享资源的操作不安全。
*/
private void test1(){
MultiThreadTest[] array = new MultiThreadTest[10];
for (int i=0;i<array.length;i++){
array[i] = new MultiThreadTest();
}
for (MultiThreadTest thread : array) {
thread.start();
}
}
/**
* 该方法中创建了一个MultiThreadTest对象,然后创建十个线程都调用该对象的addCount方法,
* 也就是说十个线程共享这个对象中的资源。若MultiThreadTest类中有类变量或成员变量,各个
* 线程之间会彼此影响,若不加锁,则线程不安全。
*/
private void test2(){
MultiThreadTest t = new MultiThreadTest();
for (int i=0;i<10;i++){
//这里使用lambda表达式代替下面创建线程的方式
new Thread(() -> t.addCount()).start();
/*new Thread(new Runnable() {
@Override
public void run() {
t.addCount();
}
}).start();*/
}
}
/**
* 该方法与第一个方法类似,创建了十个线程,同时创建十个MultiThreadTest对象,在每个线程
* 中调用了每个MultiThreadTest对象的addCount方法。如果MultiThreadTest中没有类变量,各
* 个线程之间没有资源共享,可以不加锁。若有成员变量(属于对象),由于每个MultiThreadTest
* 对象都有自己的成员变量,所以各个线程之间不存在资源共享,无需加锁。
*/
private void test3(){
for (int i=0;i<10;i++){
new Thread(new Runnable() {
@Override
public void run() {
new MultiThreadTest().addCount();
}
}).start();
}
}
}