1、线程和进程
线程,程序执行流的最小执行单位,是进程中的实际运作单位,经常容易和进程这个概念混淆。
那么,线程和进程究竟有什么区别呢?
首先,进程是一个动态的过程,是一个活动的实体。简单来说,一个应用程序的运行就可以被看做是一个进程。
而线程,是运行中的实际的任务执行者。可以说,进程中包含了多个可以同时运行的线程。
2、线程生命周期
第一步,是用new Thread()的方法新建一个线程,在线程创建完成之后,线程就进入了就绪状态(Runnable),
此时创建出来的线程进入抢占CPU资源的状态,当线程抢到了CPU的执行权之后,线程就进入了运行状态(Running),
当该线程的任务执行完成之后或者是非常态的调用的stop()方法之后,线程就进入了死亡状态。
而我们在图解中可以看出,线程还具有一个堵塞的过程,这是怎么回事呢?
当面对以下几种情况的时候,容易造成线程阻塞:
第一种,当线程主动调用了sleep()方法时,线程会进入则阻塞状态,
除此之外,当线程中主动调用了阻塞时的IO方法时,这个方法有一个返回参数,当参数返回之前,线程也会进入阻塞状态,
还有一种情况,当线程进入正在等待某个通知时,会进入阻塞状态。
那么,为什么会有阻塞状态出现呢?
我们都知道,CPU的资源是十分宝贵的,所以,当线程正在进行某种不确定时长的任务时,Java就会收回CPU的执行权,从而合理应用CPU的资源。我们根据图可以看出,线程在阻塞过程结束之后,会重新进入就绪状态,重新抢夺CPU资源。
这时候,我们可能会产生一个疑问,如何跳出阻塞过程呢?
第一种,当sleep()方法的睡眠时长过去后,线程就自动跳出了阻塞状态,
另一种情况,则是在返回了一个参数之后,在获取到了等待的通知时,就自动跳出了线程的阻塞过程
3、单线程与多线程
多线程问题,先要搞清楚两个概念:
并发(concurrent):并发指宏观上同时进行,微观上同一时刻只有一个任务在执行。一般指单核多线程下为并发。
并行(parallel):并行指宏观上同时进行,微观上同一时刻确实是多个任务同时执行。多核处理器可以并行执行多个任务。
单线程程序:即,若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务开始执行。
多线程程序:即,若有多个任务可以同时执行。
为什么需要多线程呢?
可以拿分时多用户操作系统来理解,我们的电脑为什么可以同时听歌、放电影、看网页,,,并不是因为电脑中有多个处理器在分别干这个事情,
而是操作系统将处理器分成一个个小的时间片,这些任务轮换着使用时间片,因为时间片的切换速度非常快,可能一秒内可以切换几百万次时间片,
而CPU的处理速度也同样非常快,可能程序中的一个小的交互任务只需要一两个、十个八个时间片,对用户来说,这么快速的处理和切换是感受不到的,所以,实现了一个CPU同时处理多个任务,而不是排队处理任务。
试想我们顺序执行听歌、放电影、看网页,对用户的排队等待时间是个极大的浪费,对CPU等待用户交互的时间也是极大的浪费,应该在这个任务等待的时间去处理其它任务。
在一个程序进程中的线程也是同样的道理,如果进程内的一个交互任务可以切换成许多个小的任务,比如多文件的读写,或边读文件边相应用户其它操作,同样可以通过时间片的轮转同时进行不同的任务或相同任务的拆分,
多线程提高了CPU的利用率,节省了用户的等待时间,提高CPU的利用率,提高了整体工作效率。
多线程有那些优点?
1.减少响应时间,提升用户体验。可以对每个任务同时推进,即时响应,减少用户等待时间。参考看电影同时放音乐。
2.提高CPU利用率,提高运行效率。分多线程来执行耗时操作,记录日志,访问数据库等,同样写入五十万条数据,效率更高。
4、线程安全问题
并发(concurrency)一个并不陌生的词,简单来说,就是cpu在同一时刻执行多个任务。而Java并发则由多线程实现的。
在jvm的世界里,线程就像不相干的平行空间,串行在虚拟机中。(当然这是比较笼统的说法,线程之间是可以交互的,他们也不一定是串行。)
多线程的存在就是压榨cpu,提高程序性能,还能减少一定的设计复杂度(用现实的时间思维设计程序)。
死锁和脏数据就是典型的线程安全问题。
简单来说,线程安全就是: 在多线程环境中,能永远保证程序的正确性,只有存在共享数据时才需要考虑线程安全问题。
方法区和堆就是主要的线程共享区域。那么就是说共享对象只可能是类的属性域或静态域
那吗什么是线程安全呢?
如果你在Google搜索就会出现许多像这样的“定义”:
1,线程安全的代码是多个线程同时执行也能工作的代码
2,如果一段代码可以保证多个线程访问的时候正确操作共享数据,那么它是线程安全的
现在我们可以用一种非专业的解释来定义它:当一个类被多个线程进行访问并且正确运行,它就是线程安全的
当多个线程访问某各类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行, 并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
ArrayList和Vector有什么区别?HashMap和HashTable有什么区别?StringBuilder和StringBuffer有什么区别?
这些都是Java面试中常见的基础问题。
面对这样的问题,回答是:ArrayList是非线程安全的,Vector是线程安全的;
HashMap是非线程安全的,HashTable是线程安全的;
StringBuilder是非线程安全的,StringBuffer是线程安全的。
此时如果继续问:什么是线程安全?线程安全和非线程安全有什么区别?分别在什么情况下使用?
这里就使用ArrayList和Vector二者来说明。
二者如何取舍
非线程安全是指多线程操作同一个对象可能会出现问题。而线程安全则是多线程操作同一个对象不会有问题。
线程安全必须要使用很多synchronized关键字来同步控制,所以必然会导致性能的降低。
所以在使用的时候,如果是多个线程操作同一个对象,那么使用线程安全的Vector;否则,就使用效率更高的ArrayList。
非线程安全!=不安全
非线程安全并不是多线程环境下就不能使用。
注意我上面有说到:多线程操作同一个对象。注意是同一个对象。
如果是每个线程中new一个ArrayList,而这个ArrayList只在这一个线程中使用,那么肯定是没问题的。
线程安全的解决方法?
1.同步方法:synchronized 修饰的方法 ex:public synchronized void test(){}
弊端:方法中的所有代码,都只允许一个线程访问。
(有一种情况:一个方法中,有一个部分代码不会涉及到线程安全问题,可以允许多个线程同时访问 == 》即下面的2.同步代码块)
2.同步代码块 : synchronized(被加锁的对象){ 代码 }
3.锁机制Lock
①创建ReentrantLock对象
②调用lock方法:加锁
{代码....}
③调用unlock方法:解锁
注意:可把解锁的unlock方法的调用放在finally{}代码块中,保证一定能解锁
提醒:在同步的时候,其他代码都可以多个线程同时执行!只是被同步的代码不能同时执行!
5、线程池
为什么要使用线程池?
在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。
而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。
线程池的概念?
线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池。
线程池优点?
1、从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程。
2、也可以减少内存的消耗,提高响应速度。
线程池的使用
1、FixedThreadPool
// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task = new Runnable() {
public void run() {
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:execute()
fixedThreadPool.execute(task);
// 4. 关闭线程池
fixedThreadPool.shutdown();
2、CachedThreadPool
// 1. 创建可缓存线程池对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task = new Runnable() {
public void run() {
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:execute()
cachedThreadPool.execute(task);
// 4. 关闭线程池
cachedThreadPool.shutdown();
//当执行第二个任务时第一个任务已经完成
//那么会复用执行第一个任务的线程,而不用每次新建线程。
3、SingleThreadExecutor
// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task = new Runnable() {
public void run() {
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:execute()
singleThreadExecutor.execute(task);
// 4. 关闭线程池
singleThreadExecutor.shutdown();
4、ScheduledThreadPool
// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task = new Runnable() {
public void run() {
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:schedule()
scheduledThreadPool.schedule(task,
1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,
10, 1000, TimeUnit.MILLISECONDS);
// 延迟10ms后、每隔1000ms执行任务
// 4. 关闭线程池
scheduledThreadPool.shutdown();
6、简单的线程安全代码
继承Thread类
public class ThreadTicketA extends Thread {
public static int tickets = 100;
public static String str = new String("线程");
public void run() {
while (true) {
synchronized (str) {
if (tickets > 0) {
System.out.println(String.format("第%s个线程卖出了第%d张票",
Thread.currentThread().getName(), tickets));
--tickets;
}
}
}
}
public static void main(String[] args) {
ThreadTicketA aa1 = new ThreadTicketA();
aa1.start();
ThreadTicketA aa2 = new ThreadTicketA();
aa2.start();
}
}
实现Runnable接口
public class ThreadTicketB implements Runnable {
public int tickets = 100;
String str = new String("线程");
public void run() {
while (true) {
synchronized (str) {
if (tickets > 0) {
System.out.println(String.format("第%s个线程卖出了第%d张票",
Thread.currentThread().getName(), tickets));
--tickets;
}
}
}
}
public static void main(String[] args) {
ThreadTicketB aa = new ThreadTicketB();
Thread tt1 = new Thread(aa);
tt1.start();
Thread tt2 = new Thread(aa);
tt2.start();
}
}