并发编程和串行
并发编程的目的:
- 让程序充分利用非计算机资源·、
- 加快程序响应速度(耗时任务、web服务器)、
- 简化异步时间的处理。
适合并发编程的场景
- 任务会阻塞线程,导致之后的代码不能执行:比如一边从文件中读取,一边进行大量计算的情况
- 任务执行时间过长,可以划分为分工明确的子任务:比如分段下载
- 任务间断性执行:日志打印
- 任务本身需要协作执行:比如生产者消费者问题
并发编程的挑战之–频繁的上下文切换
-
cpu为线程分配时间片,时间片非常短(毫秒级别),cpu不停的切换线程执行,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这 个任务的状态,让我们感觉是多个程序同时运行的。
-
减少上下文切换的开销 :
- 无锁并发编程(多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些方法规避使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据)
- CAS
- 使用最小线程。(避免创建不需要的线程,比如任务很少,但是创建了很多县城来处理,这样会造成大量线程都处于等待状态)。
- 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
并发编程的挑战之–死锁
当发生死锁时 控制台输入jps查看当前进程id找到死锁进程的id如图
在输入jstack pid 查看详细情况
或者终端输入jconsole启动java监视和管理控制台,选择相应的进程连接
点击检测死锁查看详情
并发编程的挑战–线程安全
在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源。如,同一内存区(变量,数组,或对象)、系统(数据库,web services等)或文件。实际上,这些问题只有在一或多个线程向这些资源做了写操作时才有可能发生,只要资源没有发生变化,多个线程读取相同的资源就是安全的。
public class Counter {
protected long count = 0;
public void add(long value){
this.count = this.count + value;
}
}//线程不安全
并发编程的挑战–资源的限制
线程基础
线程和进程的区别
- 进程:是系统进行扥配和管理资源的基本单位。
- 线程:进程的一个执行单元,是进程内调度的实体、是cpu调度和分派的基本单位,是比进程更小的独立运行的基本单位。线程也被称为轻量级进程,线程是程序执行的最小单位。
- 一个程序最少一个进程,一个集成至少一个线程。
- 进程有自己的独立地址空间,每启动一个进程,系统就会为他分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
- 线程是共享进程中的数据的,使用相同的地址空间,因此cpu切换一个线程的花费远比进程小得多
- 线程之间的通信更方便,统一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式进行。
- 如何处理好同步和互斥是编写多线程程序的难点。
线程的状态及相互转换
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):处于可运行状态的线程正在JVM中执行,但它可能正在等待来自操作系统的其他资源,例如处理器。
- 阻塞(BLOCKED):线程阻塞于synchronized锁,等待获取synchronized锁的状态。
- 等待(WAITING):Object.wait()、join()、 LockSupport.park(),进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待(TIME_WAITING):Object.wait(long)、Thread.join()、LockSupport.parkNanos()、LockSupport.parkUntil,该状态不同于WAITING,
它可以在指定的时间内自行返回。 - 终止(TERMINATED):表示该线程已经执行完毕。
线程创建
- 继承Thread
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() );
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.setName("nizaishuonimane");
thread.start();
}
}
- 实现Runnable
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.setName("niubi");
thread.start();
}
}
实际开发中,选第2种:java只允许单继承,增加程序的健壮性,代码可以共享,代码跟数据独立
- 匿名内部类创建线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
- Lambda表达式创建(JDK8)
new Thread(()->{
System.out.println("Lambda");
}
).start();
- 线程池创建线程
public class ThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(()->{
Thread.currentThread().setName("no.1");
System.out.println(Thread.currentThread().getName());
});
}
}
线程的挂起和恢复操作
- 什么是挂起线程?
线程的挂起操作实质上就是使线程进入“非可执行”状态下,在这个状态下CPU不会分给线程时间片,进入这个状态可以用来暂停一个线程的运行。
在线程挂起后,可以通过重新唤醒线程来使之恢复运行
- 为什么要挂起线程
cpu分配的时间片非常短、同时也非常珍贵。避免资源的浪费。
- 怎么挂起线程
1.被废弃的方法
thread.suspend()该方法不会释放线程所占用的资源。如果使用该方法将某个线程挂起,则可能会使其他等待资源的线程死锁。
thread.resume()方法本身并无问题,但是不能独立于suspend()方法存在。
2.可以使用的方法
wait() 暂停执行、放弃已经获得的锁、进入等待状态,释放资源
notify() 随机唤醒一个在等待锁的线程(随机唤醒)
notifyAll() 唤醒所有在等待锁的线程,自行抢占cpu资源
wait notify notifyAll 方法都必须在同步代块中调用
- 什么时候适合挂起线程
我等的船还不来(等待某些未就绪的资源),我等的人还不明白。直到notify方法被调用
public class ThreadWait implements Runnable{
private static Object object = new Object();
@Override
public void run() {
synchronized (object){
System.out.println(Thread.currentThread().getName()+"挂起前操作。。。");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("释放资源");
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new ThreadWait(),"xxx");
t1.start();
System.out.println(Thread.currentThread().getName());
Thread.sleep(2000);
synchronized (object){
object.notify();
}
}
}
线程的中断操作(interrupt)
- 废弃方法
stop(),调用stop,线程立刻停止,此时有可能引发相应的线程安全问题。 - interrupt().
public class ThreadInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(!Thread.currentThread().isInterrupted()){//判断线程是否处于中断状态
System.out.println(Thread.currentThread().getName()+"正在运行");
}
}
);
t.start();
Thread.sleep(1*1000);
t.interrupt();
}
}
线程的优先级
thread.setPriority(int priority) //优先级在1-10之间不可超过此范围
线程的优先级设置可以为1-10的任一数值,Thread类中定义了三个线程优先级,分别是:MIN_PRIORITY(1)、NORM_PRIORITY(5)、MAX_PRIORITY(10),一般情况下推荐使用这几个常量,不要自行设置数值。
守护线程
线程分类
- 用户线程、守护线程
- 守护线程:任何一个守护线程都是整个程序中所有用户线程的守护者,只要有活着的用户线程,守护线程就活着。当JVM实例中最后一个非守护线程结束时,也随JVM一起退出
守护线程的用处:jvm垃圾清理线程
建议: 尽量少使用守护线程,因其不可控 不要在守护线程里去进行读写操作、执行计算逻辑
/**
*先设置守护进程,再开始线程
*/
thread.setDaemon(true);
thread.start();
线程安全
什么是线程安全&#x