----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------
由于以前工作不曾使用过多线程技术,而通过最近一段时间的学习,可能学的东西还很片面。使用起来也很生疏,为了能更好的记忆和理解,我来把我最近一段时间学习的东西做一些整理。
首先,对于多线程技术,我们要明白如下的定义:
1.什么是进程?
进程就是正在进行中的程序,也就是对应一个应用程序在内存中所使用的空间。
2. 什么是线程?
线程就是进程中用于控制程序执行的控制单元(执行路径,执行情景),而一个进程中至少要有一个线程。
3.什么是多线程
多线程就是一个进程中有多个(两个或两个以上)执行路径(控制单元)。
注意:对于JVM,启动时,就有两个线程:
1.jvm的主线程,也就是执行mian函数的线程。
2.jvm的垃圾回收线程。
创建线程的目的:就是开启一条线程,去运行指定的代码(线程要执行的代码),并且和其他代码同时运行,以达到多个线程来实现多任务并发,提高程序的执行效率。
那么,我们怎么来创建线程呢?在Java中提供了两种线程的方式:
一.继承Thread类
注:java提供的对线程描述的类是Thread类, 创建线程对象的方法是构造函数,提供了要被线程执行的代码存储的位置是在run()里面。
步骤:
1.继承Thread类。
2.覆盖run方法,将线程要运行的代码定义其中。(要覆盖run方法的原因是,定义线程要运行的代码存储在run()里面)
3.创建Thread类的子类对象,其实就是在创建线程,调用start方法(其中,start()有两个功能:1.开启线程 2.执行run()方法)。
class ThreadDemo extends Thread// 方法局限性,不能再继承其他父类.
{
private String name;
ThreadDemo(String name){
//super(name);Thread中的构造函数,可以给线程自定义名字。
this.name=name;
}
publicvoid run() {
System.out.println(this.name+" Thread run...");
}
publicstaticvoid main(String[]args){
ThreadDemo t1=new ThreadDemo("线程一");
ThreadDemo t2=new ThreadDemo("线程二");
t1.start(); //开启线程,调用run方法
t2.start();
}
}
二.实现Runnable接口
注:下面的3种情况,就不可以再使用继承Thread类。
1.自定义的类中有多线程要运行的代码。但是该类有自己的父类。
2.要求程序具有功能的可扩展性。
3.多个功能使用一个共享数据。
那么,程序就提供了一个规则。通过实现Runnable接口。并将多线程要运行的代码存放在实现了Runnable类的子类的run方法中,然后调用对应的线程,进行处理。
步骤:
1,定义了实现Runnable接口。
2,覆盖接口的run方法。将多线程要运行的代码存入其中。
3,创建Thread类的对象(创建线程),并将Runnable接口的子类对象作为参数传递给Thread的构造函数。
为什么要传递?因为线程要运行的代码都在Runnable子类的run方法中存储。所以要将该run方法所属的对象传递给Thread。让Thread线程去使用该对象调用其run方法。
4,调用Thread对象的start方法。开启线程。
class ThreadDemo implements Runnable//实现Runnable接口
{
publicvoid run(){
//Thread.currentThread() 获取当前线程的对象,相当于this
//Thread类的getName() 获取当前线程的名称
System.out.println(Thread.currentThread().getName()+" Runnablerun...");
}
publicstaticvoid main(String[] args){
ThreadDemo demo=new ThreadDemo();
Thread t1=new Thread(demo);
Thread t2=new Thread(demo);
t1.start(); //开启线程,调用run方法
t2.start();
}
}
通过上面的知识点,我们可以知道:
1. 线程都有自己默认的名称,格式是:Thread-编号,该编号是从0开始。
2. 使用多线程的好处是:解决了多部分同时运行的问题。
3. 继承Thred类和实现Runnable接口这两种方式,都能创建线程,但是综合比较,创建线程建议使用实现Runnable接口这种方式,原因如下:
A:通过Thread继承的类,有局限性,不能再继承其他类.,这是java单继承的特性。
B:实现Runnable接口实现多线程,可以实现数据共享,根据接口可以多个实现,并且可以继承其他类,所以功能的扩展性能好。
线程的生命周期
线程可以划分为5个状态:创建、运行、阻塞(临时状态)、冻结、消亡。线程启动后,并不是一直占用cpu独自运行,而是多个线程在cpu中进行切换运行,所以多线程具备随机性。
临时状态的特点:具备了执行资格,但不具备执行权。
冻结状态的特点:放弃了执行资格。
注:启动线程使用的start方法,而不是run方法,调用start方法启动线程,系统会把该run方法当成线程执行体来处理。如果直接调用线程对象的run方法,就相当在执行普通的方法。
多线程运行有什么问题么?
由于多线程的运行具备随机性,就有可能会产生多线程的安全问题。
问题的产生的原因:
几个关键点:
1,多线程代码中有操作共享数据。
2,多条语句操作该共享数据。
当出现上面的情况时,就有可能出现下面问题:
有一个线程对多条操作共享数据的代码执行的一部分。还没有执行完,另一个线程开始参与执行。就会发生数据错误。
解决的办法:
当一个线程在执行多条操作共享数据代码时,其他线程即使获取了执行权,也不可以参与操作。
根据上面的思路,那么可以使用Java中提供的同步的原理:就是将部分操作功能数据的代码进行加锁,就可以保证在同一时间只允许一个线程进行操作。
使用同步的前提是:
1,必须是两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。
同步可以分为两种表现形式:
一.同步代码块
synchronized(对象) //该对象可以是任何类的对象.如Object类的对象
{
//...操作共享数据的语句
}
二.同步函数
函数类型前+synchronized关键字,如下:
publicstaticsynchronizedvoid func(){
//...方法体
}
它们的区别是:
1,同步代码块使用的锁是任意对象。
2,同步函数使用的锁是this。
注意:对于static的同步函数,使用的锁不是this。是 类名.class 是该类的字节码文件对象。
同步的优点和缺点:
优点:解决了线程的安全问题
缺点:要判断同步锁,较为消耗资源;并且同步嵌套后,容易死锁。
死锁:当两个线程相互等待对方释放同步监视器时就会发生死锁。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
注:同步中可以有多条线程,但是只有一个线程在执行,谁拿锁谁执行
class Ticket implements Runnable
{
private inttick = 100;
Object obj = new Object();
publicvoid run()
{
//show(); //同步方法
while(tick<=100)
{
synchronized(obj) //同步代码块
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
publicsynchronizedvoid show(){//同步方法
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"....同步函数_"+i);
}
}
}
class TicketDemo2
{
publicstaticvoid main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
线程间通讯:其实就是多个线程在操作同一个资源,但是操作的动作不同。
为了让线程协调运行,Object类提供了如下三个方法:
wait():导致当前线程处于冻结状态,被wait的线程,会被存储到线程池中,可以通过其他线程使用notify()方法或notifyAll()方法来唤醒该线程。
notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。
notifAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可能执行被唤醒的线程。
注意:wait()和notify()等待和唤醒的锁,必须是同一个锁。
为什么这些操作线程的方法要定义Object类中呢?
上面的方法只能在持有监视器(锁)的线程中操作,而监视器(锁)可以是任意对象,所以可以被任意对象调用的方法,要定义在Object类中。
其中上面的三个方法都使用在同步中,因为要在持有监视器(锁)的线程操作,但是同步才具有锁,所以只能使用在同步中。
/*
对于多个生产者和消费者。
*/
class Resource{
private String name;
privateintcount = 1;
privatebooleanflag = false;
publicsynchronizedvoid set(String name){
/*为什么要定义while判断标记。
原因:让被唤醒的线程再一次判断标记。*/
while(flag)
try{this.wait();}catch(Exception e){}
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
flag = true;
/*为什么定义notifyAll,
因为需要唤醒对方线程。
因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。*/
this.notifyAll();
}
publicsynchronizedvoid out(){
while(!flag)
try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
flag = false;
this.notifyAll();
}
}
class Producer implements Runnable{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
publicvoid run()
{
while(true)
{
res.set("+商品+");
}
}
}
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res){
this.res = res;
}
publicvoid run(){
while(true){
res.out();
}
}
}
class ProducerConsumerDemo {
publicstaticvoid main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con= new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
t1.start();
t2.start();
}
}
其他常用方法:
interrupt():强制让线程恢复到运行状态中,清除冻结状态。
setDaemon():当正在运行的线程都是守护线程时,虚拟机退出,如果所有的前台线程都结束,后台线程无论处于何种状态都会自动结束。
join():哪个线程执行到join方法,哪个线程会释放执行资格,处于冻结状态。
setPriority(级别):设置线程的优先级,默认优先级是5。其中:线程优先级在1到10之间10个数。优先级越大被执行到的几率越大。
yield():可以使线程暂时释放执行权。