1、多线程概述
进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或叫一个控制单元。
线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行。
一个进程中至少有一个线程。
2、如何在自定义的代码中,自定义一个线程呢?
Java中提供了对线程这类事物的描述,就是Thread类。
1)创建线程的第一种方式:继承Thread类。
A、定义类继承Thread。
B、覆写Thread类中的run方法。
目的:将自定义代码存储在run方法中,让线程运行。
C、调用线程的start方法。
作用:启动线程,调用run方法。
多线程有一个特性:随机性。多线程都获取cpu执行权,但是在某一刻,只能有一个程序运行,在快速切换,互相抢夺cpu执行权。
2)为什么要覆盖run方法?
Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法,也就是说Thread类中的run方法用于存储线程要执行的代码。
3)创建线程的第二种方式:实现Runnable接口
开发时用这种方法多。
步骤:
A、定义类实现Runnable接口;
B、覆盖Runnable接口中的run方法;
C、通过Thread类建立线程对象;
D、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数;
E、调用Thread类中的start方法开启线程并调用Runnable接口子类中的run方法;
4)实现方式和继承方式区别:
实现方式避免了单继承的局限性,在定义线程时,建议使用实现方式。
继承Thread:线程代码存放在Thread子类run方法中;
实现Runnable:线程代码存放在接口子类的run方法中。
3、多线程的安全问题
一个线程对多条语句还没执行完,若另一个线程参与进来执行,会导致共享数据的错误。
1)解决办法:同步代码块
synchronized(对象)
{
需要被同步的代码;
}
这个对象就如同一把锁。
同步前提:
A、必须要有两个或者两个以上的线程;
B、必须是多个线程是哟个同一个“锁”;
好处:解决了多线程安全问题。
弊端:多个线程需要判断,较为消耗资源。
2)同步函数
A、在函数上加上synchronized修饰即可,函数需要被对象调用,函数都有一个所属对象引用,所以同步函数的锁是this(非静态)。
B、静态同步函数的锁是class对象。因为静态方法中不可以定义this,静态进内存时没有本类对象,但是一定有该方法所在类的字节码文件对象。
3)如何寻找多线程中的安全问题
A、明确哪些代码是多线程运行代码;
B、明确共享数据;
C、明确多线程运行代码中哪些语句是操作共享数据的。
4)死锁
当同步中嵌套同步时,就有可能出现死锁现象。
class Test implements Runnable
{
private boolean flag ;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
synchronized(Lock.a)
{
System.out.println("iflocka");
synchronized(Lock.b)
{
System.out.println("iflockb");
}
}
}
else
{
synchronized(Lock.b)
{
System.out.println("elselockb");
synchronized(Lock.a)
{
System.out.println("elselocka");
}//a锁中有b,b锁中有a。
}
}
}
}
class Lock
{
static Object a = newObject();
static Object b = newObject();//静态,可以通过类调用。如:Lock.b
}
class DeadLockTest
{
public static voidmain(String[] args)
{
Thread t1 = newThread(new Test(true));
Thread t2 = newThread(new Test(false));//创建两个线程及子类对象作实际参数传递给Thread构造函数。
t1.start();
t2.start();
}
}
4、线程间通信
1)wait()与sleep()区别
wait():释放资源,释放锁;
sleep():释放资源,不释放锁。
2)线程间通讯:
其实就是多个线程在操作同一个资源,但是操作的动作不同。
3)等待唤醒机制
A、wait();notify();notifyAll();
都使用在同步中。因为要对持有监视器(锁)的线程操作,所以要使用在同步中,只有同步才具有锁。
B、为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识他们所操作线程中的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。即等待和唤醒必须是同一个锁。不可以对不同锁中的线程进行唤醒,而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
C、为什么定义notifyAll?
因为需要唤醒对方线程,只用notify容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待。
5、JDK升级版(1.5版本以后)
提供了多线程升级解决方案,将同步synchronized替换成现实Lock操作,将Object中的wait,notify,notifyAll替换成condition对象,该对象可以Lock锁进行获取,实现了本方只唤醒对象操作。
6、停止线程
只有一种方法,那就是run方法结束。
开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,也就是线程结束。处于冻结状态的线程要强制恢复要运行状态,才可以操作标记让其结束,可通过Thread类interruput()方法恢复。
7、守护线程(setDaemon(Boolean on))
将线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,虚拟机退出。记住:该方法爱启动线程前调用。