-- -------android培训、java培训、期待与您交流! ----------
一 多线程的概述:
线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。
每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread
对象时,该新线程的初始优先级被设定为创建线程的优先级,
并且当且仅当创建线程是守护线程时,新线程才是守护程序。
(一):进程是一个正在执行的程序,每一个进程都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
(二):线程就是进程中的一个独立的控制单元,线程在控制进程的执行;一个进程中至少有一个线程。
(三):java 的jvm启动时会有一个进程java.exe;该进程中至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中该线程为主线程。
二 线程创建的两种方式:
(一):一种方法是将类声明为 Thread
的子类。该子类应重写 Thread
类的run
方法,为什么要重写run方法呢?
因为:Thread类用于描述线程:该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法;也就是说Thread类中的run方法用于存储
线程要运行的代码。
使用Thread类 创建线程的步骤:
(1 )创建实现类继承Thread类复写run方法,为什么要重写呢!是因为要将自定的代码存储到run方法中:
(2)创建对象调用run方法,在调用start()启动线程;
如以下实例中的两个for循环的抢执行权,每次的执行循序都不一样是为什么呢!
因为:多个线程都获取cpu的执行权,cpu执行到谁,谁就运行,明确一点,在某个时刻只能有一个程序在运行;(但多核例外)cpu在做着快速的切换
已达到看上去是同时运行的效果;我们可以形容把多线程的运行行为在互相抢夺cpu的执行权。这就是多线程的一个特性:随机性。
(3)线程的名称的获取方式:
1:static Thread currentThread():获取当前线程的对象;
2:getName():获取线程名称:但要通过setName或者构造函数设置线程名称:如示例
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t1=new Test("one");//创建好一个线程
Thread t2=new Test("tow");//创建好第二个线程
t1.start();//调用start()方法启动线程
t2.start();//调用start()方法启动线程
for (int i = 0; i < 60; i++) {
System.out.println("Hello world"+i);
}
}
}
//实现类继承Thread类
class Test extends Thread{
//通过构造函数设置线程的名称
Test(String name){
super(name);
}
//重写run方法
public void run(){
for (int i = 0; i < 60; i++) {
//获取线程的名称方式1:(Thread.currentThread().getName());2:this.getName()
System.out.println((Thread.currentThread().getName())+"....."+this.getName()+"实现类继承Thread类重写run方法启动线程!"+i);
}
}
}
(二):创建线程的另一种方法是声明实现 Runnable
接口的类。该类然后实现 run
方法。
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run
的无参数方法。
(1)使用Runnable接口创建线程的步骤:
1:定义实现类实现接口(Runnable);
2:覆盖Runnable接口中run方法,将线程要运行的代码放到该实现接口的run方法中;
3:通过Thread对象建立对象;
4:将Runnable接口的子类对象作为实际参数传给Thread类的构造函数中,但为什么要传参数呢?
因为,自定义的run方法所属的对象是Runnable的接口的子类对象,所以要让线程去指定对象的run方法,就必须明确该run方法的所属对象。
5:调用Thread类的start方法开启线程并调用Runnable接口子类的run方法;
(2):实现方式和继承方式有什么区别呢?
其区别是:java只支持单继承要是有其他类要用到多线程就不能再继承Thread类了,而Runnable接口可以再实现类继承了其他类后在实现Runnable
接口这样就提高看程序的扩展性和局限性;
如以下示例:
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建Test实现类的对象让int变量 ticket成为线程的共享数据;
Test t=new Test();
//以下创建4个线程并向Thread传入实现Runnable接口的对象,
//通过对象指定让Thread类知道调用的run方法是那个类的
Thread d1=new Thread(t);
Thread d2=new Thread(t);
Thread d3=new Thread(t);
Thread d4=new Thread(t);
d1.start();//调用start()方法启动线程
d2.start();
d3.start();
d4.start();
}
}
//定义实现类来实现Runnable接口
class Test implements Runnable{
//定义int变量让以上4个线程共享一个资源;
private int ticket=100;
//重写Runnable接口中的run方法;
public void run(){
while(true){
if(ticket>0){
//通过Thread.Runnable.getName()方法或取输出int变量的线程名:
System.out.println(Thread.currentThread().getName()+"...."+ticket--);
}else{
break;
}
}
}
}
三 多线程安全问题:
通过刚才的的示例:发现打印的结果又可能会出现Thread-2....0 或 Thread-2....0 和 Thread-0....-1的变量值;这时就会引发出线程的安全问题:
其原因是:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分还没有全部执行完就有可能被另一个线程参与进来执行
所以导致了操作共享数据的错误:那么该怎么解决呢?
解决的办法: 对多条操作共享数据的语句,只能让一个线程执行完了其他的线程在参与执行,就是在执行的过程中其他的线程不能参与执行;因此
java就对多线程的安全问题提供了专门的解决方法就是同步部代码块:格式如下
synchronized(对象){
需要同步的代码块;
}
这里要使用的对象如同锁:持有锁的线程可以同步执行,没有持有的线程即使获取到了cpu的执行权,也进不去,因为没有获取到锁。
注意:
1:同步的前提是要有多个线程或两个线程才使用同步代码块:;
2:必须是多个线程在使用同一个锁;
3:好处是:解决了线程的安全问题;
4:弊端是:因为要判断锁所以就比较消耗资源,运行的速度就变慢了;
如示例:
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建Test实现类的对象让int变量 ticket成为线程的共享数据;
Test t=new Test();
//以下创建4个线程并向Thread传入实现Runnable接口的对象,
//通过对象指定让Thread类知道调用的run方法是那个类的
Thread d1=new Thread(t);
Thread d2=new Thread(t);
Thread d3=new Thread(t);
Thread d4=new Thread(t);
d1.start();//调用start()方法启动线程
d2.start();
d3.start();
d4.start();
}
}
/*
Thread-1....1
Thread-3....66
Thread-0....92
Thread-2....0
在这里出现了0变量就出现了线程的安全问题该这时候就要使用同步代码块来解决安全问题了;如下
*/
//定义实现类来实现Runnable接口
class Test implements Runnable{
//定义int变量让以上4个线程共享一个资源;
private int ticket=100;
//重写Runnable接口中的run方法;
public void run(){
while(true){
synchronized(this){ //这里传的是当前对象this
if(ticket>0){
//通过Thread.Runnable.getName()方法或取输出int变量的线程名:
System.out.println(Thread.currentThread().getName()+"...."+ticket--);
}else{
break;
}
}
}
}
}
(一):如何在线程中找出要处理的安全问题呢?
1:明确哪些代码是多线程运行的代码;
2:明确共享的数据;
3:明确多线程运行代码中哪些语句是操作共享数据的代码;
(二)在线程synchronized使用的是哪一个锁呢?
1:在同步函数中函数调用的锁是哪一个呢:在函数需要被调用时那么函数都有一个所属的对象引用这就是同步函数要调用用的锁this;
2:如果在同步函数中被static修饰后,这时该函数调用的又是那一个锁呢?这时在静态代码块加载到内存时内存中还没有本类对象因此就不能使用
this这个锁了,但是一定有该类对应的字节码文件对象。这时我们就可以使用(类名.class)这个对象了。该对象的类型就是class;
如示例:
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建Test实现类的对象让int变量 ticket成为线程的共享数据;
Test t=new Test();
//以下创建2个线程并并让其调用不同的锁的时候就会有可能产生安全问题会输出0
Thread d1=new Thread(t);
Thread d2=new Thread(t);
d1.start();//调用start()方法启动线程
try {
//调用sleep()方法让线程在此处停留1毫秒
Thread.sleep(1);
} catch (Exception e) {
}
t.feag=false;
d2.start();
}
}
//定义实现类来实现Runnable接口
class Test implements Runnable{
//定义int变量让以上4个线程共享一个资源;
private static int ticket=200;
//定义一个布尔值用来切换线程
public boolean feag=true;
//重写Runnable接口中的run方法;
public void run(){
if(feag){
while(true){
//将锁封装在run方法中该线程用的锁是this锁;如果是使用了静态修饰的就是Test.class
synchronized (Test.class) {
if(ticket>0){
System.out.println(Thread.currentThread().getName()+".....tick............."+ticket--);
}else{
break;
}
}
}
}else{
while(true){
//this.show(); //函数没有用静态修饰时使用的是this锁
show();//使用静态修饰的函数调用
}
}
}
//将锁封装在函数中这时调用的锁则是this;
//要是将该函数使用静态修饰时,那么此时使用 的就不是this这个锁了;而是使用类名.class这个锁
public static synchronized void show(){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+".....show....."+ticket--);
}else{
System.exit(1);
}
}
}
四 延迟加载的单例设计模式:
在单例设计模式中的懒汉式中如果要使用到多线程的话就会产生线程安全问题因此要用锁进行判断但是用了锁的话又造成了新的问题就是线程等待:这个问题
怎么解决呢?这个在延迟加载的单例设计模式面试中可能会有问到------------ 如下示例:
//延迟加载的多线程单例设计模式
class Single{
private static Single s=null;
private Single(){}
public static Single getSingle(){
/*
* 当第一个线程进来时对象为空则就进入synchronized (Single.class)锁中;
* 当一线程在次判断后有有可能就在还没有执行完第二个线程就进入第一if块中但是该锁中的执行权还在线程一的手中
* 所以二线程进不去就在等待中自后就有可能一线程执行当一线程执行完后就已经创建好了对象这样对象就不为空了
* 在后面的线程中就在外面的if块中就做了判断了就不用再去判断锁了这样就能解决等待的问题了;
*
*/
if(s==null){
synchronized (Single.class) {
if(s==null){
s=new Single();
}
}
}
return s;
}
}
五 多线程的死锁,为什么会发生死锁呢?
原因是:在多个线程中出现同步线程嵌套同线程,这使得线程中在相互的抢对方的锁,而线程中的锁都不释放而造成僵持状态;这就像我们在日常生活中打开多个
程序后就僵持不动了的死机;要重启系统一样。死锁也会有和谐的时侯;
如以下示例的死锁小程序:
public static void main(String[] args) {
// TODO Auto-generated method stub
Ticket t=new Ticket();
Thread d=new Thread(t);
Thread d1=new Thread(t);
d.start();
try { Thread.sleep(5); } catch (Exception e) {}
t.fog=false;
d1.start();
}
}
class Ticket implements Runnable{
private int ticket=200;
private Object obj=new Object();
boolean fog=true;
public Ticket(){
}
public void run(){
if(fog){
while(true){
//d线程进来后持有obj锁
synchronized(obj){
//d线程先想要d1线程的锁this
show();
}
}
}else{
while(true){
show();
}
}
}
//d1线程进来后呢就持有this锁
public synchronized void show(){
/*
* d1进来有想要d线程obj锁就这样的两个线程在这僵持着谁也不放锁所以就造成了线程的死锁;
* 这就是同步嵌套多个线程造成的不同线程持有不同的锁,因此要避免死锁就要从这解决死锁的问题
*/
synchronized(obj){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"...show.."+ticket--);
}else{
System.exit(1);
}
}
}
}
六 等待唤醒机制:wait() 、notify() 、notifyAll():
(一)概述:
(1) wait():是在其他线程调用此对象的 notify()
方法或notifyAll()
方法前,导致当前线程等待。换句话说,此方法的行为就好像
它仅执行 wait(0) 调用一样。当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用
notify
方法,或 notifyAll
方法通知在此对象的监视器上等待的线程醒来。
(2) notify 方法是用于:唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,
并在对实现做出决定时发生。线程通过调用其中一个 wait
方法,在对象的监视器上等待直到当前线程放弃此对象上的锁定,才能继续执行被唤
醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。
(3) notifyAll()方法是用于:唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait
方法,在对象的监视器上等待。直到当前线程放弃
此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。
(4)这些方法都是使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才有锁;
(5)为什么这些操作线程的方法都要定义在object类中呢?
因为:这些方法在操作同步中线程是,都必须要标识他们所操作线程只有的锁,只有同一个锁的被等待线程,可以被同一个锁上的notify唤醒,而不能
唤醒其他不同锁中的线程;也就是说等待和唤醒都必须是同一个锁。
(二)如何使用如示例:
注意:如果是只有两个线程在操作的话就可以使用notify()去唤醒下一个代执行的线程,但要是有多个线程在等待的话就要使用notifyAll()来把全部线程唤
醒了;因为在逻辑判断标记处唤醒的是下一线程的话就会出现全部冻结状态:或把while循环判断该为if判断就会出现另一种问题就是数据错乱;可
以改代码看效果就知道了!
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建实体类对象
Factory f=new Factory();
//创建两个赋值线程和取值的线程
new Thread(new Producer(f)).start();
new Thread(new Producer(f)).start();;
new Thread(new Consumer(f)).start();;
new Thread(new Consumer(f)).start();;
}
}
//实体类
class Factory{
private String name;
private int count=1;
private boolean flag=false;
public String getName() {
return name;
}
//创建赋值方法并加上锁
public synchronized void setIn(String name){
//逻辑判断如果是false就让赋值线程wait()冻结
while(flag){
try {
this.wait();
} catch (Exception e) {
}
}
//赋值并加上标记
this.name=name+"----"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag=true;//改变逻辑判断标记
this.notifyAll();//使用notifyAll()唤醒全部待执行的线程;
}
public synchronized void getOut(){
while(!flag){ //逻辑判断如果是!false就让赋值线程wait()冻结
try {
this.wait();
} catch (Exception e) {
// TODO: handle exception
}
}
System.out.println(Thread.currentThread().getName()+".......消费者....."+this.name);
flag=false; //改变逻辑判断标记
this.notifyAll(); //使用notifyAll()唤醒全部待执行的线程;
}
}
class Producer implements Runnable{
private Factory f; //实例化实体类对象
public Producer(Factory f){
this.f=f;
}
//重 写接口Runnable的run方法在调用实体类的赋值方法并使用循环给该方法赋值
public void run(){
int i=50;
while(i>0){
f.setIn("商品");
i--;
}
}
}
class Consumer implements Runnable{
private Factory f;
public Consumer(Factory f){
this.f=f;
}
//重 写接口Runnable的run方法在调用实体类的取值方法并使用循环调用该方法
public void run(){
int i=50;
while(i>0){
f.getOut();
i--;
}
}
}
七: JDK1.5中提供了多线程升级解决方案;将同步synchronized替换成现实Lock操作,将Object中的 wait ontify ontifyAll 替换成了Condition 对象
该对象可以Lock锁进行获取。
(一)Condition
将 Object
监视器方法(wait
、notify
和notifyAll
)分解成截然不同的对象,以便通过将这些对象与任意Lock
实现组合使用,
为每个对象提供多个等待 set(wait-set)。其中,Lock
替代了synchronized
方法和语句的使用,Condition
替代了 Object 监视器方法的使用。
条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即
让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的
主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait
做的那样。
Condition
实例实质上被绑定到一个锁上。要为特定 Lock
实例获得 Condition
实例,请使用其 newCondition()
方法。
void | await() 造成当前线程在接到信号或被中断之前一直处于等待状态。 |
boolean | await(long time, TimeUnit unit) 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 |
long | awaitNanos(long nanosTimeout) 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 |
void | awaitUninterruptibly() 造成当前线程在接到信号之前一直处于等待状态。 |
boolean | awaitUntil(Date deadline) 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。 |
void | signal() 唤醒一个等待线程。 |
void | signalAll() 唤醒所有等待线程。 |
(二)如何使用呢:现将以上示例通过Lock锁来操作实现唤醒对方机制:
public class 使用Lock锁来实现多线程 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Factory f=new Factory();
//创建两个赋值线程和取值的线程
new Thread(new Producer(f)).start();
new Thread(new Producer(f)).start();
new Thread(new Consumer(f)).start();
new Thread(new Consumer(f)).start();
}
}
class Factory{
private String name;
private int count=1;
private boolean flag=false;
public String getName() {
return name;
}
private Lock lock=new ReentrantLock(); //创建lock锁或说是获取ReentrantLock的实例:
private Condition setLock=lock.newCondition();//通过lock获取 newCondition对象赋值的;
private Condition outLock=lock.newCondition();//通过lock获取 newCondition对象取值的;
//会抛出InterruptedException异常
public void setIn(String name) throws InterruptedException{
lock.lock();//标记锁
try {
while(flag){//如果标记为true就等待setLock.await();
setLock.await();//赋值线程冻结
}
this.name=name+"----"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag=true;//改变逻辑判断标记
outLock.signal();//唤醒取值线程
}
finally{
/*
* 在上面如果抛出了异常要是锁没有释放就会造成全线程冻结所以lock.unlock();唤醒线程要放在
* finally里面来释放资源:释放锁:lock.unlock();
*/
lock.unlock();
}
}
public void getOut() throws InterruptedException{
lock.lock();
try{
while(!flag){
outLock.await();//取值线程冻结
}
System.out.println(Thread.currentThread().getName()+".......消费者....."+this.name);
flag=false; //改变逻辑判断标记
setLock.signal();//赋值线程唤醒
}
finally{
lock.unlock();
}
}
}
class Producer implements Runnable{
private Factory f; //实例化实体类对象
public Producer(Factory f){
this.f=f;
}
//重 写接口Runnable的run方法在调用实体类的赋值方法并使用循环给该方法赋值
public void run(){
int i=50;
while(i>0){
try {
f.setIn("商品");
i--;
} catch (InterruptedException e) {
System.out.println("线程异常111");
}
}
}
}
class Consumer implements Runnable{
private Factory f;
public Consumer(Factory f){
this.f=f;
}
//重 写接口Runnable的run方法在调用实体类的取值方法并使用循环调用该方法
public void run(){
int i=50;
while(i>0){
try {
f.getOut();
i--;
} catch (InterruptedException e) {
System.out.println("线程异常");
}
}
}
}
(三) 如何让线程停止呢:
(1)只有一种方式就是让run方法结束;在开启多线程时运行代码通常是循环结构的,只要控制住循环就可以让run方法结束,也就是线程结束;
(2)特殊情况时:当线程处于了冻结状态,就不会读取到标记,那么线程就不会结束,当没有指定的方式让冻结的线程恢复到运行状态时,这时需要
对冻结进行清除,只有强制让线程恢复到运行状态来,这样就可以操作标记让线程结束了:那么怎么强制清除呢?
这是就可以使用Thread类提供的方法 interrupt():
如果线程在调用 Object
类的wait()
、wait(long)
或wait(long, int)
方法,或者该类的join()
、join(long)
、join(long, int)
、
sleep(long)
或 sleep(long, int)
方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException
。
(四)守护线程:
public final void setDaemon(boolean on)
-
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
-
(五)join概述:当A线程执行到了B线程的.join()方法时,A就会等待,等B线程都执行完了A才会执行;join可以用来临时加入线程执行。
小结: 什么时候用多线程呢:在程序中的某些代码要同时被执行时就要使用单独的多线程进行封装;
---------android培训、java培训、期待与您交流! ----------