------- android培训、java培训、期待与您交流! ----------
一、多线程的概述
1、基本定义
(1)进程:一个正在执行的程序。每一个进程执行都有一个执行顺序(控制单元)。
线程:就是进程中的一个控制单元。一个进程中至少有一个线程。
(2)主线程
JVM启动时会有一个进程(java.exe)被开启,该进程中至少有一个线程负责java程序的运行,这个线程执行的代码都存在于main方法中(主线程).注意:jvm启动时不止一个线程,还有一个垃圾回收机制线程。
2、如何定义一个线程
(1)在java中,提供了一种对线程这类事物描述的类(Thread)。
(2)创建现成的步骤:
定义一个类并继承Thread;
复写Thread类中的run方法;(线程代码都存放在run方法中)
调用线程的start方法。(开启线程,并调用run方法)没有start,线程不能开启。
3、多线程的运行状态
(1)运行状态有五种:创建,运行,冻结,消亡和阻塞状态
(2)这五种状态的关系如下图
4、创建线程的第二种方法
(1)在卖票例子中出现了多个窗口卖同一张票的现象,可以将票静态来解决这个问题,但是同时也加重了内存的负担,因此,线程的第二种创建方法也就出现了
(2)第二种创建线程的方法
自定义一个类,让该类去实现Runnable接口;
覆盖Runable接口中的run方法;(线程代码的存放位置)
通过Thread类建立线程对象;
将Runable接口建立对象作为实际参数传给Thread类的构造方法;(目的是为了让线程去运行指定对象的run方法)
调用Thread类的start方法并运行Runable接口中的run方法。
格式:new Thread(new 类(实现过Runable)).start();
(3)两种创建线程方法的区别
第二种方法避免了单继承的局限性。(创建线程时建议使用第二种方法)
线程代码的存放位置不同:第一种的代码存放在Thread子类的run方法中;第二种的代码存放在实现Runable子类的run方法中。
5、同步代码块
(1)在卖票的例子中,可能存在这样一种风险,当零线程进来以后(只剩一张票了),还没有出票,它就进入阻塞状态,这时候其他的线程同样可以进来(假设进来后都处于阻塞状态),等它们的阻塞状态解除后就会打印出0号票,负号票。为了解决这类问题java中引进了同步代码块。
(2)同步代码块的格式:synchronized(对象){要被同步的代码}
(3)同步代码块的特点:代码块中的对象就是一个锁,持有锁的线程才可以执行代码块中的代码,执行完以后会自动释放锁;没有(4)同步代码块的前提:必须具有两个或两个以上的线程;必须是多个线程使用同一个锁。
(5)好处:解决了多线程的安全隐患;弊端:每个线程都要判断锁,比较消耗资源。
(6)同步函数就是用synchronized来修饰要同步的函数,此函数的锁是this。如果同步函数被静态修饰后,同步函数的锁就不再是this,而是类对应的字节码文件对象。(原因:静态进内存时,内存中还没有本类的对象,但是一定有该类对应的字节码文件对象。类名.class ,该对象的类类型是Class)。
6、单例设计模式(多线程)
class Single
{
private static Single s=null;
private single(){}
public static Single getIntance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s=new Single();
}
}
return s;
}
}
7、死锁
死锁:就是同步中嵌套着同步,程序中尽量避免出现死锁的现象(面试中可能会让写个死锁的程序)
class Test implements Runnable
{
private boolean flag;//建立标记
Test(boolean flag)
{
this.flag=flag;
}
//run方法,要运行的代码(死锁)
public void run()
{
if(flag)
{
synchronized(MyLock.lockb)
{
System.out.println("if b");
synchronized(MyLock.locka)
{
System.out.println("if a");
}
}
}
else
{
synchronized(MyLock.locka)
{
System.out.println("else a");
synchronized(MyLock.lockb)
{
System.out.println("else b");
}
}
}
}
}
//锁
class MyLock
{
static Object locka=new Object();
static Object lockb=new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
new Thread(new Test(true)).start();
new Thread(new Test(false)).start();
}
}
注意:
class Demo
{
public static void main(String[] args)
{
new Thread(new Runnable()
{
public void run()
{
System.out.println("1");
}
})
{
public void run()
{
System.out.println("2");
}
}.start();
}
}
运行结果:2
8、线程间的通讯
(1)线程间的通讯其实就是多个线程操作同一个资源但是操作的动作不同。(拉煤的例子和生产消费的例子)
/*定义一个资源类*/
class Res
{
private String sex;
private String name;
boolean flag=false;//定义一个标记,于切换输入输出
//提供一个输入的同步方法set()
public synchronized void set(String name,String sex)
{
if(flag)
try
{
wait();
}
catch(Exception e){}
this.name=name;
this.sex=sex;
flag=true;
notify();
}
//提供一个输入的同步方法get()
public synchronized void get()
{
if(!flag)
try
{
wait();
}
catch(Exception e){}
System.out.println(name+".............."+sex);
flag=false;
notify();
}
}
class Input implements Runnable
{
//给输入初始化资源
private Res r;
Input(Res r)
{
this.r=r;
}
public void run()
{
int x=0;//为不同的资源做标记
while(true)
{
if(x==0)
r.set("丽丽","女");
else
r.set("乐乐","男");
x=(x+1)%2;
}
}
}
class Output implements Runnable
{
//给输入初始化资源
private Res r;
Output(Res r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.get();
}
}
}
class OutputInputDemo
{
public static void main(String[] args)
{
Res r=new Res();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}
注意:为了操作同一个资源,在线程初始化时,将资源初始化,然后建立一个引用。
(2)wait和sleep的区别
wait():释放cpu执行权的同时也会释放锁;sleep():只是放cpu执行权,不释放锁。
(3)在多生产者消费者的例子中定义while的原因是让被唤醒的线程再进行一次标记的判断
定义notifyAll的原因是避免线程都处于等待的状态。
9、JDK升级以后的新特性(1.5)
(1)Lock接口(显示)代替了synchronized(隐式)
(2)将Object中的wait、notify和notifyAll换成了Condition对象。(该对象可以通过Lock进行获取)
(3)新特性中还提供了只唤醒对方的方法。
拉煤例子
import java.util.concurrent.locks.*;
/*定义一个资源类*/
class Res
{
private String sex;
private String name;
boolean flag=false;//定义一个标记,于切换输入输出
private Lock lock=new ReentrantLock();//建立锁对象
private Condition con=lock.newCondition();//通过锁对象获取Condition
//提供一个输入的同步方法set()
public void set(String name,String sex)throws Exception
{
lock.lock();
try
{
if(flag)
con.await();//相当于wait
this.name=name;
this.sex=sex;
flag=true;
con.signal();//想当于notify();
}
finally
{
lock.unlock();//释放锁是一定要执行的代码.
}
}
//提供一个输入的同步方法get()
public void get()throws Exception
{
lock.lock();
try
{
if(!flag)
con.await();
System.out.println(name+".............."+sex);
flag=false;
con.signal();
}
finally
{
lock.unlock();//释放锁是一定要执行的代码.
}
}
}
class Input implements Runnable
{
//给输入初始化资源
private Res r;
Input(Res r)
{
this.r=r;
}
public void run()
{
int x=0;//为不同的资源做标记
while(true)
{
if(x==0)
{
try
{
r.set("丽丽","女");
}
catch(Exception e){}
}
else
{
try
{
r.set("呵呵","男");
}
catch(Exception e){}
}
x=(x+1)%2;
}
}
}
class Output implements Runnable
{
//给输入初始化资源
private Res r;
Output(Res r)
{
this.r=r;
}
public void run()
{
while(true)
{
try
{
r.get();
}
catch(Exception e){}
}
}
}
class OutputInputDemo1
{
public static void main(String[] args)
{
Res r=new Res();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}
10、停止线程
如何停止线程:因stop方法已过时,所以线程的停止只能是run方法结束。(多线程中的run的方法体大多数都是循环结构,控制循环体就可以结束线程)
注意:当线程处于冻结状态时,线程不可以通过控制循环结束,(如果没有指定的方法让冻结状态恢复到运行状态,这时就要对冻结状态进行清除)Thread类中提供了interrupt()方法用来清除这种状态。
11、守护线程(后台线程)
格式:线程对象.setDaemon(true);
作用:前台程序都结束以后,后台程序也会自动结束。
12、join方法
join方法的作用:等待该线程结束。(某些线程必须运行时就在此线程开启以后再加上join方法)。