Java多线程
来源:互联网 发布:freebsd vs ubuntu 编辑:程序博客网 时间:2024/06/07 02:55
多线程
一、多线程概述
1、进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。
2、线程:其实就是进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间的标示。线程负责的是应用程序的执行顺序。
3、多线程
在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像种在一个进程中有多个线程执行的方式,就叫做多线程。
一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。
jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。
当产生垃圾时,收垃圾的动作,是不需要主线程来完成,因为这样,会出现主线程中的代码执行会停止,会去运行垃圾回收器代码,效率较低,所以由单独一个线程来负责垃圾回收。
4、随机性的原理:因为cpu的快速切换造成,哪个线程获取到了cpu的执行权,哪个线程就执行。而cpu执行哪个程序,是毫无规律性的。这也是多线程的一个特性:随机性。哪个线程被cpu执行,或者说抢到了cpu的执行权,哪个线程就执行。而cpu不会只执行一个,当执行一个一会后,又会去执行另一个,或者说另一个抢走了cpu的执行权。至于究竟是怎么样执行的,只能由cpu决定。
二、创建线程的方式
创建线程共有两种方式:继承方式和实现方式。
线程的名称是由:Thread-编号定义的。编号从0开始。
线程要运行的代码都统一存放在了run方法中。
返回当前线程的名称:Thread.currentThread().getName()
线程要运行必须要通过类中指定的方法开启。start方法。
start方法:
1、启动了线程;
2、让jvm调用了run方法。
创建线程的第一种方式:继承Thread ,由子类复写run方法。
步骤:
1,定义类继承Thread类;
2,目的是复写run方法,将要让线程运行的代码都存储到run方法中;
3,通过创建Thread类的子类对象,创建线程对象;
4,调用线程的start方法,开启线程,并执行run方法。
创建线程的第二种方式:实现一个接口Runnable。
步骤:
1,定义类实现Runnable接口。
2,覆盖接口中的run方法(用于封装线程要运行的代码)。
3,通过Thread类创建线程对象;
4,将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。
5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。
用实现方式创建线程示例如下:
/*需求:简单的卖票程序。多个窗口卖票。*/class Ticket implements Runnable//extends Thread{private int tick = 100;public void run(){while(true){if(tick>0){//显示线程名及余票数System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);}}}} class TicketDemo{public static void main(String[] args) {//创建Runnable接口子类的实例对象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();}}
为什么要有Runnable接口的出现?
1:通过继承Thread类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承Thread类,因为java单继承的局限性。
可是该类中的还有部分代码需要被多个线程同时执行。这时怎么办呢?
只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。
所以,通常创建线程都用第二种方式。
因为实现Runnable接口可以避免单继承的局限性。
2:其实是将不同类中需要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义到接口中。为其他类进行功能扩展提供了前提。
所以Thread类在描述线程时,内部定义的run方法,也来自于Runnable接口。
实现Runnable接口可以避免单继承的局限性。而且,继承Thread,是可以对Thread类中的方法,进行子类复写的。但是不需要做这个复写动作的话,只为定义线程代码存放位置,实现Runnable接口更方便一些。所以Runnable接口将线程要执行的任务封装成了对象。
三、线程的几种状态:
被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消忙状态:stop()方法,或者run方法结束。
图解如下:
四、线程安全问题
1、导致安全问题的出现的原因:
当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。
2、涉及到两个因素:
1,多个线程在操作共享数据。
2,有多条语句对共享数据进行运算。
原因:这多条语句,在某一个时刻被一个线程执行时,还没有执行完,就被其他线程执行了。
3、解决安全问题的原理:
只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。
如何进行多句操作共享数据代码的封装呢?
java中提供了一个解决方式:就是同步代码块。
格式:
synchronized(对象) { // 任意对象都可以。这个对象就是锁。
需要被同步的代码;
}
五、同步
好处:解决了线程安全问题。
弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。
定义同步的前提
1,必须要有两个或者两个以上的线程,才需要同步。
2,多个线程必须保证使用的是同一个锁。
同步的第二种表现形式:
同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性。
格式:
在函数上加上synchronized修饰符即可
同步函数是用的哪个锁呢?
通过验证,函数都有自己所属的对象this,所以同步函数所使用的锁就是this锁。
示例如下:
class Ticket implements Runnable{private int tick=100;Object obj = new Object();public void run(){while(true){show();}} //直接在函数上用synchronized修饰即可实现同步public synchronized void show(){if(tick>0) {try{//使用线程中的sleep方法,模拟线程出现的安全问题//因为sleep方法有异常声明,所以这里要对其进行处理Thread.sleep(10);}catch (Exception e){}//显示线程名及余票数System.out.println(Thread.currentThread().getName()+"..tick="+tick--);}}}
六、静态函数的同步方式
当同步函数被static修饰时,这时的同步用的是哪个锁呢?
静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。
所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。
这个对象就是 类名.class
典型示例如下:
/*加同步的单例设计模式————懒汉式*/class Single{private static Single s = null;private Single(){}public static void getInstance(){if(s==null){synchronized(Single.class){if(s==null)s = new Single();}}return s;}}
同步代码块和同步函数的区别
同步代码块使用的锁可以是任意对象。
同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。
在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。
七、死锁
同步死锁:通常只要将同步进行嵌套,就可以看到现象。同步函数中有同步代码块,同步代码块中还有同步函数。
八、线程间通信
线程间通信:多个线程在操作同一个资源,但是操作的动作却不一样。
1:将资源封装成对象。
2:将线程执行的任务(任务其实就是run方法。)也封装成对象。
等待唤醒机制:
涉及的方法:
wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程。
notifyAll:唤醒的是线程池中的所有线程。
注意:
1:这些方法都需要定义在同步中。
2:因为这些方法必须要标示所属的锁。
你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。
3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?
因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。
生产者、消费者示例:
public class ProducerConsumerDemo { /**多生产者消费者的情况 * 当出现多个线程时,要用while标识和notifyAll * @param args */ public static void main(String[] args) { Resource r=new Resource(); Produce pro=new Produce(r); Consumer con=new Consumer(r); Thread t1=new Thread(pro); Thread t2=new Thread(pro); Thread t3=new Thread(con); Thread t4=new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); } } class Resource { private String name; private int count=1; private boolean flag=false; public synchronized void set(String name) { while(flag)//用了while出现了全部等待 try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.name=name+"------"+count++; System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name); flag=true; this.notifyAll(); } public synchronized void out() { while(!flag) try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name); flag=false; this.notifyAll(); } } class Produce implements Runnable { private Resource res; Produce(Resource res) { this.res=res; } public void run() { while(true) { res.set("商品++"); } } } class Consumer implements Runnable { private Resource res; Consumer(Resource res) { this.res=res; } public void run() { while(true) { res.out(); } } }
wait和sleep区别: 分析这两个方法:从执行权和锁上来分析:
wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
wait:线程会释放执行权,而且线程会释放锁。
Sleep:线程会释放执行权,但不是不释放锁。
线程的停止:通过stop方法就可以停止线程。但是这个方式过时了。
停止线程:原理就是:让线程运行的代码结束,也就是结束run方法。
怎么结束run方法?一般run方法里肯定定义循环。所以只要结束循环即可。
第一种方式:定义循环的结束标记。
第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。
JDK1.5新特性标准
JdK1.5提供了多线程升级解决方案,将同步synchronized替换成现实LOCK操作
将wait,notify,notifyAll替换了condition对象
线程停止的Stop方法已经过时,新的停止方法有两种
1,定义循环结束标记
2,使用interrupt(中断)方法,该方法是结束线程的冻结状态使线程回到运行状态中
运用LOCK操作代替同步,之前的wait,notify,notifyall方式用lock中的await,signal(),signAll()
示例如下:
class BoundedBuffer { final Lock lock = new ReentrantLock();//生成lock实例对象 final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock();//加锁 try { while (count == items.length) notFull.await();//等待 items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal();//唤醒 } finally { lock.unlock();//解锁 } } public Object take() throws InterruptedException { lock.lock();//加锁 try { while (count == 0) notEmpty.await();//等待 Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal();//唤醒 return x; }finally { lock.unlock();//解锁 } } }
知识扩展:
interrupt():中断线程。
setPriority(int newPriority):更改线程的优先级。
getPriority():返回线程的优先级。
toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。
join:临时加入一个线程的时候可以使用join方法。
当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。
- 【Java多线程】多线程死锁
- Java 多线程
- java 多线程
- java多线程
- JAVA多线程
- java多线程
- JAVA多线程
- java多线程
- JAVA 多线程
- Java多线程
- java多线程
- JAVA 多线程
- Java 多线程
- Java 多线程
- java多线程
- Java 多线程
- Java多线程
- java 多线程
- 【好程序员特训营】Java字符串截取分割
- xshell使用
- SIFT特征提取分析
- 临近毕业,多希望发财
- 输出乘法口诀表
- Java多线程
- 不允许创建临时变量,交换两个数的内容
- android 终于搞定了判断ScrollView是否滚动到底部!
- eyetribe_Developers Guide
- Length of Last Word
- 求10个数中最大值
- JSON解析
- python中values、keys、items与*arg,**arg
- HDU4324 Triangle LOVE【拓扑排序】