并发编程中的3个概念
1.原子性:操作中包含的步骤,要么都执行成功,要么都失败。
2.可见性:一个线程修改了变量的值,其他线程能够立即看到修改的值。
3.有序性:JVM处理器在执行指令时会重排序以优化运行效率,但是会考虑数据依赖问题。但是如果多线程时重排序会造成程序执行的正确性。所以多线程时要保证代码执行的有序性。
原子性
例如:
x = 10; //语句1
y = x; //语句2
x++; //语句3
x = x + 1; //语句4
上面4个语句,哪些是原子性操作?
答:第一个是,其他的都不是。第一个x赋值为10,然后再同步到主存中,是一个原子操作。
第二个首先要从主存中读x的值,然后再赋值给y,2个步骤各自是原子操作,但合在一起不是原子操作。
同理x++,x=x+1,也不是原子操作。
可以看出Java内存模型中读取、赋值操作为原子操作。那如何保证更大范围的原子操作呢?
那就使用到了synchronized和Lock,这两个悲观锁可以保证同一时刻只有一个线程访问被修饰的代码,自然也就是原子操作了。
可见性
经过volatile修饰的共享变量,可以将修改后的值立即更新到主存。如果普通共享变量,值更新到主存的时机是不确定的,所以没有可见性。除此之外,synchronized和Lock在释放锁之前会更新主存,也能保证可见性。
有序性
Java内存模型允许编译器、处理器对指令重排序,单线程无影响,多线程会影响,所以synchronized、Lock都能保证有序性。
添加volatile关键字修饰后,能够保证一定的有序性,同时Java内存模型先天有一些happens-before原则也可以保证有序性。
happens-before(先行发生行为)原则:
程序次序规则:一个线程内,按照书写顺序,写在前面的先执行。事实上单线程程序指令依然会重排,但是能够保证结果的一致性。
锁定规则:一个锁必须释放后,才能够继续加锁。
volatile规则:写操作发生在读操作之前。一个线程写,一个线程读,读在写后面。
传递规则:A操作先于B操作,B操作先于C操作,则A操作限于C操作。
线程启动规则:Thread的start()方法先行于线程内的一切操作。
线程中断规则:Thread的interrupt()方法先行于该线程代码检测到中断事件的发生。
线程的终结规则:线程中所有的操作都先行于线程的终止检测。
对象的终结规则:一个对象的初始化方法先行于finalize()方法的开始。
相关题目
1.进程和线程之间区别?
一个进程是一个独立的运行环境,线程是进程中的一个任务,进程中可以包含多个线程,各个线程间可以共享进程的资源。
2.多线程编程的好处?
多线程用以提高程序的执行效率,一些线程资源等待时,cpu可以去处理其他线程,减少了空闲状态。多个线程间还可以共享堆内存,所以多线程程序比单线程程序更好。
3.用户线程和守护线程的区别?
默认新建的thread是用户线程,thread.setDaemon(true)可设置守护线程。平常处理逻辑的线程为用户线程,程序运行时在后台提供通用服务的线程设置为守护进程(如jvm的垃圾回收线程)。守护线程创建的线程也是守护线程。
4.如何创建线程?
两种方法:通过继承Thread类或者实现Runable接口覆盖或实现其中的run()方法,然后再调用new thread再start()
5.线程的生命周期?
新建:当new thread()时,线程处理“新建”状态;
就绪&运行:thread.start()之后,如何没有分配cpu等资源为就绪,分配了cpu等资源为运行状态;
阻塞:线程被挂起为阻塞状态,如thread.join(),thread.sleep(),等待用户输入等,将线程放入到阻塞队列中,阻塞消除变为就绪状态;
等待:当前线程需要等待其他线程的一些特定动作时(如某个对象被synchronized(o)加锁;o.wait()时需要其他线程o.notify()、o.notifyAll()唤醒);
超时等待:等待的基础上,超时了;
终止:线程运行完毕。
6.我们可以直接调用thread的run()方法吗?
可以的,直接调用和普通类就一样了。如果要在一个线程中启动另一个线程的话,就需要thread.start()了
7.线程的优先级?
我们可以定义线程的优先级,1代表最低,10代表最高,一般来说高优先级线程会有具有优先权,但是它的实现是操作系统实现的,并不能一定保证高优先级在低优先级线程前执行。
8.什么是线程调度器Thread Schedule和时间分片time slicing?
线程调度器是一个操作系统服务,它负责为就绪态的线程分配CPU时间片,可以继续线程优先级或线程等待时间,尽量通过程序自己控制线程的调度,而不是依赖于不同操作系统的默认实现。
9.什么是多线程中的上下文切换(context-switching)?
上下文切换时存储和恢复CPU状态的过程,它使得线程能够从中断点恢复执行。
10.如何确保main()方法是程序最终结束的线程?
在main()方法中开启别的线程时,使用别的线程对象.join()方法,将其同步在main()线程里即可。
11.线程间如何通信?
线程间如果共享资源时,可以采用object对象的wait(),notify(),notiyAll()等方法通过加锁的方式进行通信。
12.为什么Java中wait(),notify()t,notifyAll()方法被定义在Object类里?
因为Java并没有可供所有对象使用的锁、同步器,但是所有类都继承Object类,这样每个对象都拥有了锁、监视器可用。
13.为什么wait(),notify(),notifyAll()方法必须要在同步方法或者同步代码块中被调用?
因为对象的这些方法要调用的话,该线程必须持有该对象的锁,调用完方法后再释放锁以便让其他线程可以得到这个锁继续操作,这样就需要通过同步来实现。
14.为什么thread的sleep和yield方法是静态的?
这是一个调用对象的强制规范问题,因为sleep和yield只能对运行中的线程起作用,使用静态让这两个方法只能强行获取当前线程对象并操作,如果不指定静态那么可以控制其他非运行状态的线程执行该方法,显然是不可以的。
15.如何确保线程安全?
这里线程安全指的是线程间共享资源时不会错乱。
1:通过synchronized来对对象加锁实现线程安全;
2:使用线程安全的集合如ConcurrentHashMap来存储共享数据;
3:使用原子类如AtomicInteger存储共享数据;
4:使用锁;
5:使用volatile关键字修饰变量,保证线程从堆内存读取数据,而不是从线程cache读取数据。
16.volatile关键字的作用?
volatile关键字用来修饰变量,被修饰的变量具有多线程下的可见性、有序性、原子性(保证原子操作,原子操作的组合操作不能保证原子性)
17.同步块和同步方法哪个更好?
同步块往往对对象的锁能精确到更细粒度,所以使用同步块较好。
18.如何创建守护线程?
新建一个thread后,thread.setDaemon(true)然后再调用thread.start()就可以了。
19.什么是ThreadLocal?
线程类中的普通全局变量,在多线程的时候会有线程不安全的问题,可以使用TheadLocal变量,各个线程通过get()、set()方法获得各自线程的变量。
20.什么是Thread Group,为什么不建议使用它?
ThreadGroup这个类提供了关于线程组的信息,主要2个功能是获取线程组中活跃线程列表、为线程设置线程外部异常处理(因为线程run()方法不能抛出异常,只能打日志)。Java1.5后可以通过setUncaughtExceptionHandler(UncaughtExceptionHandler eh) 来设置,ThreadGroup已经过时,所以不建议使用。
21.什么是Thread Dump线程转储?
是一个JVM活动线程的列表,可以用来分析系统瓶颈死锁等,一般使用JDK自带的jstack等工具来分析。
22.什么是死锁DeadLock,如何分析和避免死锁?
两个以上的线程永远互相阻塞的情况叫做死锁。
分析死锁:通过分析Thread Dump查看阻塞的线程以及他们等待的资源,每个资源有id,可以根据资源的id查看哪些线程拥有了它的对象锁。
避免死锁:避免嵌套锁,在需要的地方使用,避免无期限等待。
23.什么是Java Timer类?
Java Timer类是一个工具类,可以安排一个线程在未来的某个时间点执行,执行一次或者周期执行。TimerTask是一个实现了Runnable接口的抽象类,继承它然后交给Timer对象安排即可。
package com.journaldev.threads;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class MyTimerTask extends TimerTask {
@Override
public void run() {
System.out.println("Timer task started at:"+new Date());
completeTask();
System.out.println("Timer task finished at:"+new Date());
}
private void completeTask() {
try {
//assuming it takes 20 secs to complete the task
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String args[]){
TimerTask timerTask = new MyTimerTask();
//running timer task as daemon thread
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(timerTask, 0, 10*1000);
System.out.println("TimerTask started");
//cancel after sometime
try {
Thread.sleep(120000);
} catch (InterruptedException e) {
e.printStackTrace();
}
timer.cancel();
System.out.println("TimerTask cancelled");
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
24.什么是线程池?
线程池用于管理多个工作线程。Executors是线程池的工厂类可以方便的创建线程池对象。
1. newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
25.?
26.?
27.?
28.?
网友评论