一,Thread类
1.1,并发于并行
并发:指两个或者多个事件在同一时刻发生(一起发射一个弓箭)
并行:指两个或多个事件在同一个时间段内发生。(这一个小时我一边打游戏,一边看电视)
多核处理器:每个CPU并发执行一个任务,多任务并发执行,并行处理程序(了解)
1.2,线程与进程
进程:是指一个内存中运行的应用程序。(运行中的软件)
线程:进程内部的一个独立执行单元。(软件中的小模块)一个进程并发运行多个线程。
1.3,线程和进程的区别
进程:有独立的内存空间,进程中的数据存放空间是独立的,至少有一个线程。
线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。
1.4线程的调度
JVM采用的是抢占式调度,造成多线程执行结果的随机性。
2.1,创建线程类
第一种方式:继承Thread类来创建并启动多线程。
1.定义Thread子类,并重写该类的run()方法,run方法也叫线程执行体。
2.创建Thread子类的实例,即创建了线程的对象
3.调用线程对象的start()方法来启动该线程
Thread类中的具体方法(课外辅导时间来啦!)
嵌套类
static class Thread.State() 线程状态
static interface Thread.UncaughtExceptionHandler 未捕获异常时候调用的接口
构造方法:
Thread() 分配新的 Thread 对象。
Thread(Runnable target, String name) 分配新的 Thread 对象。
方法摘要:
static int activeCount() 返回当前线程的线程组中活动线程的数目。
static Thread currentThread() 返回对当前正在执行的线程对象的引用。
String getName() 返回该线程的名称。
Thread.State getState() 返回该线程的状态。
void interrupt() 中断线程。
void join() 等待该线程终止。
void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响
static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
二,线程
1.1多线程原理
每一个线程执行的时候都有一个属于自己的栈内空间,来执行自己的线程。
**start()**方法开启一个新的栈内空间,开启一个新的线程
1.2创建线程方式二
采用java.lang.Runnable也是非常常见的一种,我们只需要重写run方法即可。
步骤如下:
1.定义Runnable接口的实现类,并重写接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
2.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
3.调用该线程对象**start()**方法来启动线程。
多线程实现类测试方法
public class Demo {
public static void main(String[] args) {
MyRunable mr = new MyRunable();
Thread t = new Thread(mr,"小强");
t.start();
for(int i = 0;i < 100;i++){
System.out.println("旺财" + i);
}
}
}
MyRunnbale多线程实现类接口
public class MyRunable implements Runnable {
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
通过实现Runnable接口,使得该类有多线程的特征,run()方法是多线程的一个执行目标,所有的多线程都在run方法里面,Thread类实际上也是Runnable接口的类。
接口 Runnable中只有一个方法
那就是void run() 使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。
实际上所有的多线程方法都是通过Thread的start()方法来运行的,不管吧最终实现Runnable还是继承了Thread都是通过Thread对象的API来控制的。
Runnbale实现类里包含的run()方法仅作为线程执行体,而实际的对象依然是Thread实例,只是Thread线程负责执行target的**run()**方法
1.2Thread和Runnbale的区别
如果一个类继承Thread,则不适合资源的共享,但如果实现了Runnbale接口,则很容易实现资源共享。
如果使用Runnbale接口来实现多线程,不同的线程之间可以进行资源的共享。比如下面是实现了Runnbale的多线程
Thread t1 = new Thread(MyRunnable);t1.setName("售票窗口1");
Thread t2 = new Thread(MyRunnable);t2.setName("售票窗口2");
Thread t2 = new Thread(MyRunnable);t2.setName("售票窗口3");
t1.start();
t2.start();
t3.start();
原本线程如果是继承了Thread 类的话
代码执行效果如下:
执行了多线程之间的资源共享:
执行效果如下:
我们根本目的其实就是为了卖出5张票通过三个窗口来进行卖票。
实现Runnable接口比继承Thread类所具有的优势4点:
1.适合多个相同线程的程序代码的线程去共享同一个资源(我上面已经描述,三个售票窗口卖出5张票)
2.可以避免java单继承的局限性(继承了Thread类以后就无法继承其他类,但是实现了Runnbale接口以后还能实现其他接口,继承其他类)
3.增加程序健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立
4.线程池只能放入实现Runnbale或Callable类线程,不能直接放入继承Thread的类
(PS:联想记忆法)
(我健身比我继承遗产多了哪些好处,我的身体不会只是我的身体,可以被多个女人分享,我也不会只是局限于继承遗产的富二代,我身体更加健壮,我所知道的知识含量也会被共享,钱和知识分开独立,健身房里面只欢迎跑步的人,不欢迎继承遗产的富二代)
1.3匿名内部类方式实现线程的创建
Runnbale r = new Runnable(){
public void run(){
for(int i = 0; i < 20; i++){
System.out.println("张宇:"+i);
}
};
new Thread(r).start();
for(int i = 0; i < 20; i++){
System.out.println("费玉清:"+i);
}
}
}
二,线程安全
2.1线程安全
如果多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果一样的,而且其他的变量值也和预期的一样,就是线程安全的。(我挖id我快扫查出来的QQ号,和我手工单次查出来的QQ号是一样,那么我就说这个是安全的QQ号)
注意:
1.不代表所有的多线程都是线程不安全的
2.资源共享并不代表线程安全。
3.线程安全问题都是由全局变量及静态变量引起的,若每个线程中对全局变量在,静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般需要考虑线程同步,否则的话就有可能影响线程安全。
ps:(这是由全局的QQ和死了的静态QQ引起的,快扫如果只用来扫QQ,不写在界面上,那么快扫永远不会奔溃,这个快扫就是线程安全的,但是快扫一但在界面上显示了扫描出来的QQ,那么快扫很有可能就会奔溃,一般要加入同步按钮,导致快扫的线程不安全。)
2.2同步代码块
同步代码块:synchronized可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥的访问。
(小葵花妈妈课堂中的互斥的访问)互斥的访问:进程互斥:
两个或两个以上的进程,不能同时进入关于同一组共享变量的临界区域,否则可能发生与时间有关的错误,这种现象被称作进程互斥· 也就是说,一个进程正在访问临界资源,另一个要访问该资源的进程必须等待。
2.3同步方法
同步方法:使用synchronized修饰的方法就叫做同步方法,保证A线程在访问临时资源的时候B线程绝不会访问,B线程只能等待。
同步锁:
对于非static方法,同步锁就是this。
对于static方法,同步锁就是当前方法所在类的字节码文件。(类名.class)
synchronized 定义在方法内的:
public class Ticket implements Runnable{
private int ticket = 100;
Object lock = new Object();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
synchronized (lock) {
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
}
}
}
}
synchronized 修饰在方法上的:
public class Ticket implements Runnable{
private int ticket = 100;
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
sellTicket();
}
}
/*
* 锁对象 是 谁调用这个方法 就是谁
* 隐含 锁对象 就是 this
*
*/
public synchronized void sellTicket(){
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
}
}
2.4Lock锁
lock提供了一个功能更加强大的锁机制,比synchronized更好用。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
- public void lock() `:加同步锁。
- public void unlock():释放同步锁。
但是我们更加倾向于使用synchronized ,因为synchronized 可以自动释放同步锁
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
lock.lock();
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
lock.unlock();
}
}
}
三,线程状态
3.1线程状态概述
在API中java.lang.Thread.State
这个枚举中给出了六种线程状态:
NEW(新建) :线程刚创建,还未启动,还没调用start()方法。
Runnable (可运行):线程可以在java模拟机中运行的状态
Waiting(无限等待):线程等待另一个线程唤醒,进入这个状态后是不能自动唤醒的
Timed(计时等待):倒计时唤醒
Terminated(被终止):非正常退出导致线程死亡
Blocked(阻塞状态):如果一个对象试图获取对象锁,但是对象锁被其他线程持有,那么这个线程处于阻塞状态。
3.2Timed Waiting(计时等待):
1.进入TIMED_WAITING状态的一种常见情形是调用sleep方法,单独的线程也可以调用,不一定非要由协作关系。
2.为了让其他线程有机会执行,可以将Thread.sleep放入**run()**之内,保证线程执行过程中会睡眠
3.sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态。
新创建
new
|
可运行——>sleep带参(时间) 计时等待(timed waiting)
run <—— 参数时间到达
|
被终止
Terminated
3.3Blocked(锁阻塞)
Blocked状态在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一个状态。
3.4Waiting(无限等待)
Waiting状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一个状态。
那么我们之前遇到过这种状态吗?答案是并没有,
通过上述案例我们会发现,一个调用了某个对象的Object.wait方法的线程会等待另一个线程调用此对象的Object.notify()方法或Object.notifyAll()方法
其实waiting并不是一个线程的操作(同事们之间的竞争关系,但是大多数情况下他们还是合作关系)
当多个线程协作时,A,B,A进入等待,B获得同步锁,B调用notifyA唤醒进入Runnable(可运行)状态。
四,等待唤醒机制
4.1线程间的通信
概念:多个线程在处理同一个资源,但是处理的动作却不相同。
这就是协调调度,不同于抢占式调度,协调调度让线程间更加有规律的执行一件事情。
4.2等待唤醒机制
什么是等待唤醒机制
这是多线程之间的一种协作机制
调用wait和notify方法需要注意的细节:
1.wait方法和notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象wait方法调用后的线程。
2.wait方法与notify方法是属于Object类的方法。因为:锁对象可以是任意对象,而任意对象所属的类都继承了Object类。
**3.wait方法与notify方法必须要在同步代码块或者是同步函数中使用。**因为:必须要通过锁对象调用这两个方法。同步代码块(被synchronized 修饰的代码块是同步代码块,同步函数:就是同步方法代码块)
五,线程池
5.1线程池思想概述
思想概述:执行完一个线程不进行销毁,等待继续执行其他任务。
概述:就是一个容纳多个线程的容器,其中线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务:
任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完成后的收尾工作,任务的执行状态等。
线程池管理器(ThreadPool):用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务。
(重点)多线程数据传递
9.1、通过构造方法传递数据
package mythread;
public class MyThread1 extends Thread
{
private String name;
public MyThread1(String name)
{
this.name = name;
}
public void run()
{
System.out.println("hello " + name);
}
public static void main(String[] args)
{
Thread thread = new MyThread1("world");
thread.start();
}
}
9.2、通过变量和方法传递数据
package mythread;
public class MyThread2 implements Runnable
{
private String name;
public void setName(String name)
{
this.name = name;
}
public void run()
{
System.out.println("hello " + name);
}
public static void main(String[] args)
{
MyThread2 myThread = new MyThread2();
myThread.setName("world");
Thread thread = new Thread(myThread);
thread.start();
}
}
三、通过回调函数传递数据
package mythread;
class Data
{
public int value = 0;
}
class Work
{
public void process(Data data, Integer numbers)
{
for (int n : numbers)
{
data.value += n;
}
}
}
public class MyThread3 extends Thread
{
private Work work;
public MyThread3(Work work)
{
this.work = work;
}
public void run()
{
java.util.Random random = new java.util.Random();
Data data = new Data();
int n1 = random.nextInt(1000);
int n2 = random.nextInt(2000);
int n3 = random.nextInt(3000);
work.process(data, n1, n2, n3); // 使用回调函数
System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+"
+ String.valueOf(n3) + "=" + data.value);
}
public static void main(String[] args)
{
Thread thread = new MyThread3(new Work());
thread.start();
}
}