多线程
进程跟线程的区别:
进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个进程.
多进程:操作系统中同时运行多个程序.一个程序至少有一个进程
线程:堆空间是共享的,栈空间是独立的,线程消耗的资源也比较小,互相之间可以影响的,又称之为轻型进程或进程元.
多线程:在同一个进程中运行的多个任务.一个进程至少有一个线程
Java操作进行
在java代码中如何去运行一个进程(简单讲解,获取进程中的数(IO))
方式一:Runtime类exec方法.
方式二:ProcessBuilder 的start方法 推荐使用
创建并启动线程
子线程一旦创建跟主线程有相同的级别.
线程类thread类和Runnable类的子类才能称之为线程类.
主线程(main方法运行,表示主线程)
方式一:继承Thread类;
1):定义一个类,让类继承于java.lang.Thread类
2):在类中覆盖Thread中的run方法
3):在run方法中编写需要执行的操作--线程执行体(run方法里面的代码)
4):在main方法中,创建线程对象,并启动线程.
- 创建线程的格式: 线程类 a = new 线程类()
- 调用线程对象的start方法 a.start;启动一个线程
注意:千万不要调用run方法.如果调用run方法,依然还是一个线程,并没有开启新的线程.
方式二:实现Runnable接口;
1):定义一个类,让类继承于java.lang.runnable接口
2):在类中覆盖runnable接口中的run方法
3):在run方法中编写需要执行的操作--线程执行体(run方法里面的代码)
4):在main方法中,创建线程对象,并启动线程.
public Thread(Runnable target)分配新的 Thread 对象.
- 创建线程类对象
Thread t = new Thread(new 继承于runnable接口的对象)
- 调用线程对象的start方法 t.start();
使用匿名内部类来创建此线程
第一种:使用接口创建匿名内部类对象来创建线程(常用)
第二种:使用类来创建匿名内部类对象来创建线程
案例分析
public class AppleExtendsDome extends Thread { }
------------------------------------------------------------------------------- //五个人同时吃50个苹果 } |
分析继承方式和实现方式的区别
继承方式:
1):java中类是单继承的,如果继承了Thread了,该类就不能再有其他的直接父类了
2):从操作上分析,继承方式更简单,获取线程名字也简单.getName()
3):从多线程贡共享同一个资源上分析,继承方式不能做到
实现方式:
1):java中可以实现多接口,此时该类还可以继承其他类,并且还可以实现其他接口.
2):从操作上分析,实现方式稍微复杂点,获取线程名字也比较复杂,得使用Thread.currentThread().getName()来获取当前线程的引用.
3):从多线程共享同一个资源上分析,实现方式可以做到
两种实现方式的区别和联系:
在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下好处:
1):避免点继承的局限,一个类可以继承多个接口。
2):适合于资源的共享
通过三个同学吃抢苹果的例子,说明使用实现方式才合理
线程不安全的问题分析
当多线程并发访问同一个对象的时候,可能出现线程不安全的问题.
解决方案:保证编号跟总数必须同步完成
A线程进入操作的时候B,C只能在外面等着,A操作结束,B和C才有机会金融入代码区执行
--------------------------------------------------
线程同步(重要)
方式一:同步代码块:
/** private int apple = 50; public void run() { } public static void main(String[] args) { } } |
语法:synchronized (同步锁){
//需要同步操作的代码块
}
同步锁:此处存放引用数据类型/对象/class文件均可
为了保证每一线程都能正常执行原子操作,java引入了线程同步机制
同步监听对象/同步锁/同步监听器/互斥锁
对象的同步锁只是一个概念,可以想象成对象上标记一个锁。
Java程序运行使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象。
注意:在任何时候,最多允许一个线程拥有同步锁。
方式二:同步方法:使用 synchronized 修饰的方法,叫做同步方法,保证线程执行该方法的时候,其他线程只能在方法外等候
Synchronized public void play(){
//TODO
}
同步锁是谁呢?
对于非static方法同步锁就是this;
对于static方法,我们使用当前方法所在类的字节码对象(xxx.class);
不要使用synchronized修饰run方法,修饰之后,某一个线程就执行完了所有的功能,好比是多个线程出现串行
解决方案:把需要同步操作的代码定义在一个新的方法中,并且该方法使用synchronized修饰,在用run方法调用该新的方法即可.
问题:假设现在有一筐苹果,数量为50个,现在分给三个人吃,直到吃完为止.
分析:
1):苹果的数量是固定的,逐步递减.所以只能有一个线程对象.
2):分给三个人吃,所以必须创建三个线程.
3):一个人吃完了才能下一个人吃,所以需要保证线程同步.
此时考虑该构造方法:public Thread(Runnable group, String name)分配新的 Thread 对象。
public static Thread currentThread()返回对当前正在执行的线程对象的引用。
public final String getName()返回该线程的名称。
/** private int apple = 50; public void run() { } public static void main(String[] args) { } } |
方式三:锁机制(Lock):
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。同步方法和语句具有的功能Lock都有,除此之外更能体现面向对象. 此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
详情见:
/** private int apple = 500; public void run() { eat(); public void eat() { if (apple > 0) { System.out.println(Thread.currentThread().getName() + "吃了第" + apple + "个苹果"); } catch (Exception e) { } finally { } //同步锁 (Lock) |
同步锁池:
/** public Consume(Intermediary a) { public void run() { } ------------------------------------------------------- /** /** /** try { ----------------------------------------------------------------------------------------------- /** public Producer(Intermediary a) { public void run() { for (int i = 0; i < 50; i++) { } ----------------------------------------------------------------------- public class Test { new Thread(new Producer(up)).start(); |
问题一:出现性别紊乱的情况
使用同步锁方式解决(参照线程同步三种方式)
同步锁必须选择多个线程共同的资源对象
当前生产者在生产数据的时候(先拥有同步锁),其他线程就在锁池中等待获取锁匙
当线程执行完同步代码块的时候,就会释放同步锁,其他线程开始抢夺使用权.
问题二:应该生产一个数据消费一个数据
解决方案:使用等待和唤醒机制
线程通信wait和notify方法介绍
Object.long里面的方法
void wait( ) 导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法.
void notify() 唤醒在此对象监视器上等待的单个线程
Void notifyAll() 唤醒在此对象监视器上等待的所有线程
注意:上述方法只能被同步监听锁对象来调用,否则报错:IllegalMonitorStateException
多个线程只有使用相同的一个对象的时候,多线程之间才有互斥效果
我们把这个用来做互斥的对象称之为,同步监听对象/同步锁
------------------------------------
同步锁对象可以选择任意类型的对象即可,只需要保证多个线程使用的是相同锁对象即可.
------------------------------------
因为,只有同步监听锁对象才能调用wait和notify方法,所以wait和notify方法应该存在于Object类中,而不是Thread类中
案例分析:银行存取钱系统
银行管理系统类
存钱类(跟取钱类综合参照说明)
取钱类
测试类代码:
线程通信:使用Lock和Condition接口
Wait和notify方法只能被同步监听锁对象来调用,否则报错:IllegalMonitorStateException
Lock机制根本没有同步锁,也就没有自动获取锁和自动释放锁的概念(更加体现面向对象)
使用格式:
1):创建lock对象: Lock lock = new ReentrantLock();
2):创建condition对象: Condition condition = Lock.new condition();
3):需要同步执行的方法体
案例
线程的生命周期
有人又把阻塞状态, 等待状态, 计时等待状态综合称之为阻塞状态
---------------------------------------------------------
线程对象的状态存放在Thread类的内部类state中:
注意:Thread.state类其实是一个枚举类.
线程对象的状态是固定的,只有6种,此时使用枚举来表示最恰当的.
在给定的时间点上,一个线程只能处于一种状态.这些状态是虚拟机状态,他们并没有反映所有操作系统线程状态
1):新建状态使用new创建一个线程对象,仅仅在对重分配内存空间. 在调用start方法 之前
新建状态下,线程根本没有启动,仅仅是存在一个线程对象而已.
New Thread();就属于新建状态
2):可运行状态(runnale):分成两种状态,ready和running.分别表示就绪状态和运行状态
- i. 就绪状态:线程对象,调用start方法之后,等待JVM的调用
- ii. 运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并运行.
线程对象的start方法,只能调用一次,否则报错.
3):堵塞(blocked)由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
此时JVM不会给线程分配CPU,知道线程重新进入就绪状态,才有机会转到运行状态.
阻塞状态只能先进入就绪状态,不能慧姐进入运行状态
正在睡眠:用sleep(long t) /wait(带参)方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用notify()方法回到就绪状态)
被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)
4)死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
线程控制操作
1):线程睡眠: 让执行的线程进入等待状态,睡眠一段时间.
方法:public static void sleep(long millis) throws InterruptedException
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行).调用sleep方法后,当前线程放弃CPU,在指定时间段类,sleep所在线程不会获得执行的机会.
该状态下的线程不会释放同步锁.
2)联合线程:线程的join方法表示一个线程等待另一个线程完成后才执行,join方法被调用之后,线程对象处于阻塞状态.
4):后台线程:在后台运行的线程,其目的是为了其他线程提供服务,也称之为”守护线程”,JVM的垃圾会竖起就是一个典型的后台线程.
如果主线程结束了,那么后台线程也跟着结束了
方法:void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。
参数:
on - 如果为 true,则将该线程标记为守护线程。
线程优先级:
每个线程都有优先级,优先级高低只和线程获得执行机会的次数多少有关,并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度.
MAX_PRIORITY = 10 最高优先级
MIN_PRIORITY = 1 最低优先级
NORM_PRIORITY = 5 默认优先级
-----------------------------------------------
int getPriority(); 返回线程的优先级。
void setPriority(int newPriority); 更改线程的优先级。
public static Thread currentThread();返回对当前正在执行的线程对象的引用。
注意:不同的操作系统支持的线程优先级不同的,建议使用上述三个优先级,不要自定义.
线程礼让(一般不用)
public static void yield()
对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。 调度程序可以自由地忽略这个提示。
产量是一种启发式尝试,以改善否则会过度利用CPU的线程之间的相对进度。 其使用应与详细的分析和基准相结合,以确保其具有预期的效果。
很少使用这种方法。 它可能对调试或测试有用,可能有助于根据种族条件重现错误。 在设计并发控制结构(例如java.util.concurrent.locks包中的并行控制结构)时也可能有用。
线程组:
Thread(ThreadGroup group,Runnable target)分配新的 Thread 对象。这种构造方法与 Thread(group, target, gname) 具有相同的作用,其中的 gname 是一个新生成的名称。自动生成的名称的形式为 "Thread-"+n ,其中的 n 为整数。
类 ThreadGroup线程组表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。