------- android培训、java培训、期待与您交流! ----------
多线程
一:什么是多线程
1.进程:进程就是正在进行中的程序。
每一个进程执行都有一个执行顺序,该顺序叫执行路径。或者叫一个控制单元。
例:我们电脑中多个运行程序,不是同时运行的,而是cpu在快速的切换运行,一个cpu只能在同一个时间内执行一个程序。之所以我们感觉实在同时运行是因为cpu切换的速度快。
2.线程:线程是进程中的内容,每个应用程序最起码有一个线程 。因为线程是程序中的控制单元。
或者叫执行路径。
线程就是进程中的一个独立的控制单元。
线程在控制着进程的执行。
3.Java有两个进程编译进程和运行进程。
Java运行进程:
java虚拟机启动的时候会有一个进程Java.exe
该进程中至少有一个线程赋值Java程序的执行。
而且这个线程运行的代码存在于main方法中。
该线程称之为主线程。
扩展:例如:其实更细节说明jvm,jvm启动不只一个线程,还有负责垃圾回收机制的线程。
3.线程存在的意义:
多线程可以让多个代码同时运行。
例如:迅雷的多线程可以提高效率。
二.创建线程
1.线程(控制单元)真正是系统创建的。进程中的线程也是系统创建的,Java中的jvm依赖于系统,所以它只需要去调用系统当中的内容,就能完成这个动作了。这也就是说Java给我们提供好了对这类事物的对象。
2.创建新执行线程有两种方法。
1)一种方法是:继承方式
一,通过对api的查找,Java已经提供了对线程这类事物的描述,Thread类。将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。
二,步骤:
1.继承Thread类
2.子类覆盖父类中的run方法,将线程运行的代码存放在run中。
3.建立子类对象的同时线程也被创建。
4.通过调用start方法开启线程。
三,其中为什么要覆盖run方法呢?
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代 码。该存储功能就是run方法。也就是说Thread类中的run方法,用于存 储 线程运行的代码主线程的代码存在main方法。
四,复写Thread类中的run方法的原因:
其实创建Thread d = new Thread();//对象编译和运行也不会出错,
但我们调用d.start();方法时其调用的是d.run方法的内容,而d.run方法的
内容为空所以不显示。如果我们继承run的方法,就可以覆盖掉run的方法的
内容写上自己的代码,这样就能运行我们写的代码。
五,复写Thread类中的run方法的目的:
将自定义代码存储在run方法,让线程运行。
例:
class Demo extends Thread
{
public void run()
{
for(int x=0; x<60; x++)
System.out.println("demo run---+"+x);
}
}
class ThreadDemo1
{
public static void main(String[] args)
{
Demo d = new Demo();//建立好一个对象就创建好一个线程。
d.start();//运行线程
for(int x=0; x<60; x++)
System.out.println("hello world---+"+x);
}
}
2)创建线程的另外一种方法:实现方式
一,创建线程的另一种方法是声明实现 Runnable(可运行的意思)接口的类。 该 类然后实现 run 方法。
二,Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个 称为 run 的无参数方法。
三,步骤:
1.定义类实现Runnable接口
2.覆盖Runnable接口中的run方法。
将线程要运行的代码存放在该run方法中。
3.通过Thread类建立线程对象。
4.将Runnable接口的子类作为实际参数传递给Thread类的构造函数;
5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方 法。
例:
class Ticket implements Runnable//1.定义类实现Runnable接口
{
private int tick =100;
public void run()//2.覆盖Runnable接口中的run方法。将线程要运行的代码存放在该run方法中
{
while(true)
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+"...sale..."+tick--);
}
}
}
}
class ThreadTest3
{
public static void main(String[] args)
{
Ticket t = new Ticket();//将Runnable接口的子类作为实际参数传递给Thread类的构造函数;
Thread t1 = new Thread(t);//3.通过Thread类建立线程对象。
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();//调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
t2.start();
t3.start();
t4.start();
}
}
3.实现方式与继承方式有什么区别?
1)实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。 2)两种方式的区别:
继承Thread:线程代码存放在Thread子类run方法中。
实现Runnable:线程代码皴法在接口的子类的run方法。
三.线程的特性
多线程的特性:随机性,谁抢到谁执行,至于执行多长,CPU说的算。
简单来说:
运行多个程序的时候cpu在做快速的切换进程,更确切的说cpu在切换进程中的线程。发现运行结果每次都不同,因为多个线程都在获取cpu的执行权,CPU执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序有一个程序运行。(多核除外)
cpu在做着快速的切换,已达到看上去是同时运行的效果。我们可以形象把多线程的运 行行为在互相抢夺cpu的执行权。
四.线程的运行状态
1.线程的运行状态:
进程结束就代表存储空间没了。
2.线程的常见四种状态:
1)被创建
通过start()到运行这状态;
2)运行
运行状态既有执行资格,又有执行权;
1.运行通过sleep(time)到冻结这状态;
2.wait()运行到冻结这状态;如果没有传参数就回不到运行状态;
3)冻结:分为睡眠和等待。
冻结状态就是放弃执行资格。
当从冻结状态恢复时,不一定是回到运行状态,可能先回到阻塞状态;
说明这个程序有执行资格,但还不一定有执行权。
1.sleep冻结时间到返回运行状态;
2.notify();冻结回到运行状态
4)消亡
stop();运行到消亡状态;run方法结束;
5)阻塞(临时状态)
如果运行多个线程时,cpu只运行一个线程,其他线程就是
阻塞状态,他们在等待cpu执行权。
专业术语:具备运行资格,但没有执行权
五.多线程的安全问题
1.多线程产生问题的原因:
当多条语句在操作同一个线程共享数据时,有一个线程对多条语句只执行了 一部分,还没有另一个线程参与进来执行,导致共享数据的错误。
例子:
假设:程序运行有三个线程1,2,3,1线程进来后,cpu运行到判断语句后 33 行后跳到2线程运行也执行到判断语句33行后,再跳到3线程运行,三线程也 一样。现在返回1线程,cpu从34行语句开始读,如果这是输入的数据为0,再运 行tick--则打印票数就变成-1的票数,而实际生活中没有-1的票数则程序就除了问 题。
class Ticket implements Runnable
{
private int tick =100;
Object obj = new Object();
public void run()//覆盖run方法;
{
while(true)
{
synchronized(obj)//同步代码块;
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...sale..."+tick--);
}
}
}
}
}
class ThreadDemo6
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
2.解决多线程出现的问题:线程同步
什么是线程同步:
例:
假设有4个线程,当线程1抢到执行权,读取synchronized(obj)语句后,相当于给synchronized(obj)代码块的内容上一把锁,让其他2,3,4线程读取不了程序块内的内容当线程1执行完synchronized(obj)代码块内的内容时候,才把代码块的锁解开,这时其他线程才能访问这个代码块,这就保证也该线程一定运行完此代码块。
而不会因为出现判断语句,而出现程序错误
程序的同步的代码格式:
synchronized名称为锁旗标或监视器。
synchronized(对象)
{
需要被同步的代码
}
同步的前提:
1.必须要有两个或者两个以上的线程。
2.必须是多个线程使用同一个锁。
3.必须保证同步中只有一个程序在运行。
同步的优点与弊端:
同步的好处:
解决了多线程的安全问题。
同步的弊端:
多个程序需要判断,较为消耗资源。
例:
class Ticket implements Runnable
{
private int tick =100;
Object obj = new Object();
public void run()//覆盖run方法;
{
while(true)
{
synchronized(obj)//同步代码块;
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}//这个可以不写
System.out.println(Thread.currentThread().getName()+"...sale..."+tick--);
}
}
}
}
}
class ThreadDemo6
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
3.同步有两种表现形式:
第一种:同步代码块。
同步代码块,用的是obj的锁
Object obj = new Object();
public void add()
{
synchronized(obj)
{
}
}
第二种:同步函数。
同步函数的锁是this.
public synchronized void add()
{
}
同步函数特例:
静态进内存,内存中没有本类对象,但是一定有该类对应的字节码文件对象,
类名.class 该对象的类型是class。
静态的同步方法:使用的锁是该方法所在类的字节码文件对象。类名.class
类名.class这个对象在内存是唯一,使用后就不再重新加载。
例:
class Ticket implements Runnable
{
private int tick =100;
Object obj = new Object();
boolean flay = true;
//同步代码块,用的是obj的锁
public void run()。
{
if(flay)
{
while(true)
{
synchronized(this)//run方法有所属对象//(obj)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...code..."+tick--);
}
}
}
}
else
while(true)
show();
}
//同步函数用的是this的锁。
public synchronized void show()
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...show..."+tick--);
}
}
}
class ThisLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
//这个代码让主线程停止10毫秒,这样能运行的只有t1程序。
try{Thread.sleep(10);}catch(Exception e){}
t.flay = false;
t2.start();
}
}
4.单例模式下的懒汉式的多线程:
懒汉式与饿汉式不同:
1.懒汉式在于实例的延迟加载
2.如果懒汉式在多线程会出现安全问题。
这时可以加同步来解决。用双重判断可以解决效率问题。
举例:
class Single
{
private static Single = null;
pivate Single(){}
public static Single getInstance()
{
if(s= null)//第一重循环
{
synchronized(Single.class)//不满足方法体的判断语句,锁不会自动解开。
{
if(s==null)//第二重循环
s=new Single();
}
}
return s;
}
}
class SingleDemo
{
public static void main(String[] args)
{
System.out.println("Hello world");
}
}
5.同步的弊端:死锁
1)口头话描述死锁:
就是你持有一个锁,我也持有一个锁,我要带你那里面去运行,我则要拿你的锁,而你要到我这边运行,你也得跟我要锁,我不放我的锁要进你那边去,你不放你的锁要进我这边来,谁都不放,着就导致了死锁的现象。当死锁的产生程序就会挂在哪里不动了。都在互相要锁。
2)死锁的出现:通常同步中嵌套同步。
例:
class Ticket implements Runnable
{
private int tick =1000;
Object obj = new Object();
boolean flay = true;
public void run()
{
if(flay)
{
while(true)
{
synchronized(obj)
{
show();
}
}
}
else
while(true)
show();
}
public synchronized void show()
{
synchronized(obj)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...code..."+tick--);
}
}
}
}
class DeadLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
t.flay = false;
t2.start();
}
}
六.多线程API中常用方法
1.public final void join()
throws InterruptedException等待该线程终止。
join的特点:
当A线程执行到了B线程的.join();方法时,A就会等待,等B线程都执行完,A线程才会执行。
join可以用过来临时加入线程执行。
2.守护线程:void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。
1.将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时, Java 虚拟机退出。
2.该方法必须在启动线程前调用。
守护线程:可以理解为后台线程。
后台线程特点:
1.开启后和前台线程(例如:主线程)抢夺cpu的执行权运行。
2.当所有前台线程结束后,后台线程会自动结束。
3.停止线程:
1.定义循环结束标记
因为线程运行代码一般都是循环,只要控制循环即可。
2.使用interrupt(中断)方法。
该方法是结束线程的冻结状态,是线程回到运行状态中来。
注:stop方法已经过时不再使用。
3.停止线程只有该一种方法,run方法结束
4.例子:
class StopThread implements Runnable
{
private boolean flag = true;
public void run()
{
while(flag)
{
System.out.println(Thread.currentThread().getName()+"...run");
}
}
public void changeFlag()
{
flag = false;
}
}
class StopThreadDemo
{
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
//守护线程在此添加:
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
//添加代码:
t1.join();
//join的意思是说:主线程在向下走的时候读到t1.join();t1要抢夺
//cpu执行权。主线程把执行权让出,等T1执行完毕后,主线才恢复 执行权。
t2.start();
/*
如果把t1.join();放在t2下面:发生的现象:主线程开启了t1,t2线程, 这时候执行权可能在主线程手里,这时当主线程碰到t1.join();时候,主线程释 放执行权,这时候还有t1,t2有资格抢夺执行权,这时候cpu就对t1,t2进行交 替执行。可是主线程什么时候活,主线程要等到t1结束,主线程才活。就 是说谁让主线程释放执行权,主线程就等谁运行结束后才有资格抢夺cpu的执 行权。
*/
int num = 0;
while(true)
{
if(num++ == 60)
{
st.changeFlag();
break;
}
System.out.println(Thread.currentThread().getName()+"...."+num);
}
}
}