多线程

来源:互联网 发布:有个活好的男朋友知乎 编辑:程序博客网 时间:2024/06/05 15:04

一、多线程概述

1、进程:操作系统中同时运行的程序

是一个正在执行的程序。
每一个进程执行都有一个执行顺序。该顺序是一个执行路径。
或者叫一个控制单元。
每个进程都有自己独立的一块内存空间,一个进程中可以有多个线程。

2、线程:在同一个进程中同时运行的任务

就是进程中的一个独立的(执行任务)控制单元。
线程在控制着进程的执行。

一个进程中至少有一个线程。多个线程可以共享数据。如:多线程下载软件

Java VM启动的时候会有一个进程java.exe。

该进程中至少一个线程负责java程序的执行。
而且这个线程运行的代码存在于main方法中。
该线程称为主线程

3、扩展

其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。

4、线程与进程的比较

线程具有许多传统进程所具有的特征,故又称为轻型线程(Light-Weight Process)或进程元;而把传统的进程称为重型进程(Heavy-Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少需要一个线程。

 

进程与线程的区别:

1)进程有独立的进程空间,进程中的数据存放空间(堆空间和栈空间)是独立的。

2)线程的堆空间是共享的,栈空间是独立的,现场称消耗的资源也比进程小,相互之间可以影响的。

二、创建线程

1、如何在自定义的代码中,自定义一个线程呢?

通过对API的查找,java已经提供䠾对线程的这类事物的描述,就
Thread类,创建线程的第一种方式,继承Thread类。

步骤:
a.定义继承Thread。
b.复写Thread类中的run方法。
目的:将自定义代码存储在run方法,让线程运行。
c.调用线程的start方法,该方法有两个作用:启动线程和调用run方法。

2、随机性

当多线程程序运行时,发现运行结果每一次都不同。
因为多个线程都获取cpu的执行权,cpu执行到谁,谁就执行。
明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)
cpu在做着快速的切换,以达到看上去是同时运行的效果。
我们可以形象地把多线程的运行行为视为在互相抢夺cpu的执行权。

这就是多线程的特性:随机性。谁抢到谁执行,至于执行多长,cpu说了算。

3、为什么要覆盖run方法呢?

Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码。
例1
class Demo extends Thread//继承线程,覆盖run方法{public void run(){for (int x=0;x<60 ;x++ )//创建个循环打印看效果{System.out.println("demo run-----"+x);}}}class ThreadDemo{public static void main(String[] args){Demo d=new Demo();//创建好一个线程//d.start();//开启线程并执行该线程的run方法d.run();//仅仅是对象调用方法,而线程创建了,并没有运行。for (int x=0;x<60 ;x++ )//创建个循环打印看效果{System.out.println("Hello world---"+x);}}}

小练习:
/*需求:创建两个线程和主程序交替运行思路:1.创建一个类继承线程,  2.主函数中开启两条线程*/class Test extends Thread//Test继承线程{private String name;Test(String name)//创建名字方法比较好分别出来{this.name=name;}public void run()//覆盖父类run方法{for (int x=0;x<60 ;x++ ){System.out.println(name+"run---"+x);}}}class TestDemo{public static void main(String[] args){Test t1=new Test("one");//创建第一条线程Test t2=new Test("two");//创建第二条线程t1.start();//开启线程,运行run方法t2.start();//开启线程,运行run方法for (int x=0;x<60 ;x++ ){System.out.println("main"+x);}}}
结果输出:

4、线程运行状态




5、获取线程对象及名称

线程都有自己的名称
Thread-编号 该编号从0开始。

局部的变量在每一个线程中都有一份。

statiic Thread current Thread( );获取当前线程对象。
getName();获取线程名称

设置线程名称:setName或者构造函数。

6、创建线程的第二种方式

实现从Runnable接口
步骤:
1,定义类实现Runnable接口
2,覆盖Runnable接口中的run方法。
将要运行的代码存放在该run方法中。
3,通过Thread类建立线程对象。
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
       为什么要将Runnable接口的子类对象传递给Thread的构造函数。
        因为,自定义的run方法所属的对象时Runnable接口的子类对象。
所以要让线程去指定指定对象的run方法,就必须明确该run方法所属对象。
5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

 

例1
思路:1.定义类实现接口Runnable  2.存放要运行的代码在run方法中,覆盖Runnable中的run方法  3.在主函数中建立线程对象  4.将Runable的子类对象作为实际参数传递给Thread  5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法*/class Ticket implements Runnable//实现Runnable接口{private int tick=100;public void run()//存放要运行的代码在run方法中,覆盖Runnable中的run方法{while(true){if (tick>0){System.out.println(Thread.currentThread().getName()+"ticket---"+tick--);//获取当前线程名和票数自减}}}}class TicketDemo{public static void main(String[] args){Ticket t=new Ticket();//创建Runnable的子类对象Thread t1 =new Thread(t);//开启线程并将Runable的子类对象作为实际参数传递给ThreadThread 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();}}
结果输出:


7、多线程的安全问题

通过对上例分析,发现有可能打印出0,-1,-2等错票。

多线程的运行出现了安全问题。

问题的原因:
当多条语句在操作一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完。
里一个线程就参与进来执行,导致共享数据的错误。

解决办法:
对多条操作共享数据的语句,在让一个线程都执行完之前,
在执行过程中,其他线程不可以参加执行。

Java对于多线程的安全问题提供了专业的解决方式。
就是同步代码块。

synchronized(对象)
{
需要被同步的代码(找共享数据)
}

例2
/*需求:简单的卖票程序多个窗口同时卖票思路:1.定义类实现接口Runnable  2.存放要运行的代码在run方法中,覆盖Runnable中的run方法  3.在主函数中建立线程对象  4.将Runable的子类对象作为实际参数传递给Thread  5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法*/class Ticket implements Runnable//实现Runnable接口{private int tick=100;Object obj=new Object();public void run()//存放要运行的代码在run方法中,覆盖Runnable中的run方法{while(true){synchronized(obj)//同步代码块{if (tick>0){try{Thread.sleep(10);//中断异常}catch (Exception e){}System.out.println(Thread.currentThread().getName()+"ticket---"+tick--);//获取当前线程名和票数自减}}}}}class TicketDemo{public static void main(String[] args){Ticket t=new Ticket();//创建Runnable的子类对象Thread t1 =new Thread(t);//开启线程并将Runable的子类对象作为实际参数传递给ThreadThread 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();}}

8、多线程同步代码块

对象如同锁。持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去。因为没有锁
例:同一节火车上的卫生间

同步的前提:
a.必须要有两个或者两个以上的线程
b.必须是多个线程使用同一个锁。

必须保证同步中只有一个线程在运行

好处:解决了多线程的安全问题。
弊端:多个线程都需要判断锁,较为消耗资源。

9、同步函数

格式:在函数上加synchronized即可
例3
/*需求:银行有一个金库有两个储户分别存300,每次存100,存3次。目的:该程序是否有安全问题,如果有,如何解决?如何找问题:1,明确哪些代码是多线程运行代码。2,明确共享数据。3,明确多线程运行代码中哪些语句是操作共享数据的。*/class Bank{private int sum;Object obj=new Object();public synchronized void add(int n)//找到问题后,同步函数{sum=sum+n;try{Thread.sleep(10);//中断异常,需要对其进行处理}catch (Exception e){}System.out.println("sum="+sum);}}class Cus implements Runnable//类实现Runnable{private Bank b=new Bank();public void run(){for (int x=0;x<3 ;x++ )//累加循环{b.add(100);}}}class BankDemo{public static void main(String[] args){Cus c=new Cus();Thread t1=new Thread(c);//创建线程Thread t2=new Thread(c);t1.start();//启动线程t2.start();}}
结果输出:


a.同步函数用的是哪一个锁呢?
函数需要被对象调用,那么函数都有一个所属对象引用,就是this。
所以同步函数使用的锁是this。

通过程序进行验证。

使用两个线程来买票。
一个线程在同步代码块中。
一个线程在同步函数中。
都在执行买票动作。


结果证明:若同步代码块锁的对象仍是obj,则运行时它们是交替运行,但是会出现安全问题。
当把锁的的对象改为this时,能解决安全问题。

b.如果同步函数被静态修饰后,使用的锁是什么呢?

通过验证,发现不再是this,因为静态方法中不可以定义this。

静态进内存中,内存中没有本类对象,但是一定有该类对应的字节码文件对象(锁)。
类名.class 该对象的类型是class

静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class

例4
//加同步的单例设计模式——懒汉式 //一般多用饿汉式,因为懒汉式存在安全隐患class Single{private Single s=null;private Single(){}public static Single getInstance(){if (s==null)//双重判断,提高效率{synchronized(Single.class)//静态的同步方法锁为该类对应的字节码文件对象{if (s==nul){s=new Single();}}}return s}}

10、死锁

当同步中嵌套同步时,就有可能出现死锁现象。如:两个人各有一根筷子,想夹菜得需要另一根。
相互借就处于和谐状态,不借就死磕,谁也吃不到。

例5
class Test implements Runnable//实现Runnable接口{private boolean flag;Test(boolean flag){this.flag=flag;}public void run(){if (flag){while(true){synchronized(MyLock.locka)//a锁{System.out.println("if locka");synchronized(MyLock.lockb)//b锁{System.out.println("if lockb");}}}}else{synchronized(MyLock.lockb)//b锁{System.out.println("else lockb");synchronized(MyLock.locka)//a锁{System.out.println("else locka");}}}}}class MyLock//创建个类来装两个锁{static Object locka=new Object();static Object lockb=new Object();}class DeadLockTest{public static void main(String[] args){Thread t1=new Thread(new Test(true));//创建线程Thread t2=new Thread(new Test(false));t1.start();//开启线程t2.start();}}
结果输出:

三、线程间的通信

1、使用同步操作同一资源

多线程操作同一资源,但线程间的运行代码不一致
例1
/*线程间通讯:其实就是多个线程在操作同一个资源,但是操作的动作不同。*/class Res//操作的资源{String name;String sex;}class Input implements Runnable{private Res r;Input(Res r){this.r=r;}public void run()//复写run方法{int x=0;while(true){synchronized(r)//用唯一的资源作锁{if(x==0)//操作同一资源{r.name="Mike";r.sex="man";     }else{r.name="丽丽";r.sex="女女女女女";}x=(x+1)%2;//创造0,1互换的循环体}}}}class Output implements Runnable{private Res r;Output(Res r){this.r=r;}public void run()//复写run方法{while (true){synchronized(r)//用唯一的资源作锁{System.out.println(r.name+"----"+r.sex);//操作同一资源}}}}class InputOutputDemo{public static void main(String[] args){Res r=new Res();//创建资源对象Input in=new Input(r);//操作同一资源Output out=new Output(r);Thread t1=new Thread(in);//与线程相关联Thread t2=new Thread(out);t1.start();t2.start();}}
结果输出:

2、等待唤醒机制

wait();
notify();
notifyAll();

都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。

为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。

也就是说,等待和唤醒必须是同一个锁。

而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
例2
class Res//操作的资源{String name;String sex;boolean flag=false;//创建布尔型变量帮助判断}class Input implements Runnable{private Res r;Input(Res r){this.r=r;}public void run()//复写run方法{int x=0;while(true){synchronized(r)//用唯一的资源作锁{if(r.flag)try{r.wait();//如果有资源,等待资源取出}catch (Exception e){}if(x==0)//操作同一资源{r.name="Mike";r.sex="man";     }else{r.name="丽丽";r.sex="女女女女女";}x=(x+1)%2;//创造0,1互换的循环体r.flag=true;//表示有资源r.notify();//唤醒等待}}}}class Output implements Runnable{private Res r;Output(Res r){this.r=r;}public void run()//复写run方法{while (true){synchronized(r)//用唯一的资源作锁{if(!r.flag)try{r.wait();如果没有资源,等待存入资源}catch (Exception e){}System.out.println(r.name+"----"+r.sex);//操作同一资源r.flag=false;//没有资源r.notify();//唤醒等待}}}}class InputOutputDemo{public static void main(String[] args){Res r=new Res();//创建资源对象Input in=new Input(r);//操作同一资源Output out=new Output(r);Thread t1=new Thread(in);//与线程相关联Thread t2=new Thread(out);t1.start();t2.start();}}
结果输出:

代码优化:

class Res//操作的资源{String name;String sex;boolean flag=false;//创建布尔型变量帮助判断public synchronized void set(String name,String sex){if(flag)try{this.wait();//如果有资源,等待资源取出}catch (Exception e){}this.name=name;this.sex=sex;flag=true;//表示有资源this.notify();//唤醒等待}public synchronized void out(){if(!flag)try{this.wait();//如果没有资源,等待存入资源}catch (Exception e){}System.out.println(this.name+"----"+this.sex);//操作同一资源flag=false;//没有资源this.notify();//唤醒等待}}class Input implements Runnable{private Res r;Input(Res r){this.r=r;}public void run()//复写run方法{int x=0;while(true){if(x==0)//操作同一资源{r.set("Mike","man");}else{r.set("丽丽","女女女女女");}x=(x+1)%2;//创造0,1互换的循环体}}}class Output implements Runnable{private Res r;Output(Res r){this.r=r;}public void run()//复写run方法{while (true){r.out();}}}class InputOutputDemo{public static void main(String[] args){Res r=new Res();//创建资源对象new Thread(new Input(r)).start();new Thread(new Output(r)).start();}}

思考1:wait(),notify(),notifyALL(),用来操作线程为什么定义在了Object类中?
a.这些方法存在于同步中。
b.使用这些方法时必须要标识所属的同步的锁。
c.锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

思考2:wait(),sleep()有什么区别?
wait():释放资源,释放锁。
sleep():释放资源,不释放锁。

例3
//消费者和生产者class Res//操作的资源{private String name;private int count=1;boolean flag=false;//创建布尔型变量帮助判断public synchronized void set(String name){while(flag)try{this.wait();//如果有资源,等待资源卖出}catch (Exception e){}this.name=name+"---"+count++;//在命名的同时编号System.out.println(Thread.currentThread().getName()+"........生产者"+this.name);//显示线程名和本类名flag=true;//表示有资源this.notifyAll();//唤醒等待}public synchronized void out(){while(!flag)try{this.wait();//如果没有资源,等待存入生产}catch (Exception e){}System.out.println(Thread.currentThread().getName()+"消费者"+this.name);//显示线程名和本类名flag=false;//没有资源this.notifyAll();//唤醒等待}}class Producer implements Runnable{private Res r;Producer(Res r){this.r=r;}public void run()//复写run方法{int x=0;while(true){r.set("+商品+");}}}class Consumer implements Runnable{private Res r;Consumer(Res r){this.r=r;}public void run()//复写run方法{while (true){r.out();}}}class ProducerConsumerDemo{public static void main(String[] args){Res r=new Res();//创建资源对象new Thread(new Producer(r)).start();//多线程生产new Thread(new Producer(r)).start();new Thread(new Consumer(r)).start();new Thread(new Consumer(r)).start();//多线程消费}}
结果输出:

如图:


升级解决方案

JDk1.5 中提供了多线程升级解决方案。
将同步synchronized替换成显示Lock操作。
将Object中的wait,notify,notifyAll,替换成了Condition对象。
该对象可以Lock锁 进行获取。
该示例中,实现了本方只唤醒对方操作。


例4
import java.util.concurrent.locks.*;class Res//操作的资源{private String name;private int count=1;boolean flag=false;//创建布尔型变量帮助判断private Lock lock=new ReentrantLock();//创建锁private Condition condition_pro=lock.newCondition();//创建锁的对象private Condition condition_con=lock.newCondition();//创建锁的对象public void set(String name)throws InterruptedException{lock.lock();//安锁try{while(flag)condition_pro.await();//生产者等待this.name=name+"---"+count++;//在命名的同时编号System.out.println(Thread.currentThread().getName()+"........生产者"+this.name);//显示线程名和本类名flag=true;//表示有资源condition_con.signal();//唤醒消费者}//释放资源finally{lock.unlock();//释放锁的动作一定要执行}}public void out()throws InterruptedException{lock.lock();try{while(!flag)condition_con.await();System.out.println(Thread.currentThread().getName()+"消费者"+this.name);//显示线程名和本类名flag=false;//没有资源condition_pro.signal();//唤醒生产者}//释放资源finally{lock.unlock();//释放锁的动作一定要执行}}}class Producer implements Runnable{private Res r;Producer(Res r){this.r=r;}public void run()//复写run方法{int x=0;while(true){try{r.set("+商品+");}catch (InterruptedException e){}}}}class Consumer implements Runnable{private Res r;Consumer(Res r){this.r=r;}public void run()//复写run方法{while (true){try{r.out();}catch (InterruptedException e){}}}}class ProducerConsumerDemo{public static void main(String[] args){Res r=new Res();//创建资源对象new Thread(new Producer(r)).start();//多线程生产new Thread(new Producer(r)).start();new Thread(new Consumer(r)).start();new Thread(new Consumer(r)).start();//多线程消费}}

四、停止线程

1、如何停止线程?

stop方法已经过时。

只有一种,run方法结束。
开启多线程运行,运行代码通常是循环结构。

只要控制住循环,就可以让run方法结束,也就是线程结束。

特殊情况:
当线程处于䠾冻结状态(wait或sleep)
就不会读取到标记,那么线程就不会结束。

当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。

Thread类提供该方法interrupt()

例1
class StopThread implements Runnable{private boolean flag=true;//帮助判断public synchronized void run(){while(flag){try{wait();}catch (InterruptedException e)//解决中断异常{System.out.println(Thread.currentThread().getName()+"....Exception");flag=false;}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.start();//开启线程t2.start();int num=0;while(true){if (num++==60){//st.changeFlag();t1.interrupt();//清除冻结状态t2.interrupt();break;//跳出}System.out.println(Thread.currentThread().getName()+"....."+num);}System.out.println("OVER");}}
结果输出:

2、守护线程

格式:线程名.setDaemon  将该线程标记为守护线程或用户线程

当正在运行的线程都是守护线程或用户线程时,JAVA虚拟机自动退出。
该方法必须在启动线程前调用。

若设置主函数创建的两条线程为守护线程,则当主函数运行完时,
两线程停止。
在其中主线程为前台线程,创建的两线程为后台线程,前台线程一结束,后台线程也随之关闭。

3、join方法(个人戏称:插队方法)

当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行。

join可以用来临时加入线程执行。
如:
Thread t1=new Thread(st); //创建线程
Thread t2=new Thread(st);


t1.start();//开启线程
t1.join()://表示0线程拥有优先执行权,1线程和主线程等它运行完后才能执行。
t2.start();

4、setPriority()方法

用来设置优先级
MAX_PRIORITY 优先级为10,最大
NORM_PRIORITY 优先级为5,分配给线程默认优先级
MIN_PRIORITY 优先级为1,最小

5、yield()方法

暂停当前线程,让其他线程执行

6、什么时候写多线程?

当某些代码需要被执行时,就需要用单独的线程进行封装。
例2
class ThreadDemo{public static void main(String[] args){new Thread()//运用匿名内部类开启线程{public void run(){for (int x=0;x<60 ;x++ ){System.out.println(Thread.currentThread().getName()+"...."+x);}}}.start();Runnable r=new Runnable()//另一种开启线程方法{public void run(){for (int x=0;x<60 ;x++ ){System.out.println(Thread.currentThread().getName()+"...."+x);}}}for (int x=0;x<60 ;x++ ) //主线程跑起{System.out.println(Thread.currentThread().getName()+"...."+x);}}}


0 0
原创粉丝点击