一、基本概念
进程:正在进行的程序。
线程:就是进程中一个执行单元或执行情景或执行路径,负责进程中程序执行的控制单元 。
进程和线程的关系: 一个进程中至少要有一个线程,进程在执行过程中拥有一个独立的内存单元,而多个线程共享内存。
多线程:当一个进程中线程有多个时,就是多线程。
多线程解决的问题:可以让多部分代码同时执行。
二、创建线程的两种方式
1、方式一:继承Thread类,覆盖run方法。
步骤:
(1)、定义类继承Thread类。
(2)、覆盖Thread类中的run方法。
(3)、创建Thread类的子类对象创建线程对象。
(4)、调用线程对象的start()方法开启线程。
代码体现:
public static void main(String[] args) {
demo d1=new demo();
demo d2=new demo();
d1.start();
d2.start();
for(int y=0;y<5;y++){
System.out.println(y+"......."+Thread.currentThread());
}
}
}
class demo extends Thread{
public void run(){
show();
}
public void show(){
for(int x=0;x<10;x++){
System.out.println(x+"......."+Thread.currentThread()+this.getName());
}
}
}
2、方式二:实现Runnable接口。
步骤:
(1)、定义一个类实现Runnable接口。
(2)、覆盖Runnable接口中的run方法。将线程要运行的代码存储到run方法中。
(3)、创建该接口的子类对象。
(4)、通过Thread类进行线程的创建,并将Runnable接口的子类对象作为Thread类的构造函数的实参进行传递。
(5)、调用Thread类中的start()方法开启线程。
代码体现:
public static void main(String[] args) {
demo1 d1=new demo1();
Thread t1=new Thread(d1);
Thread t2=new Thread(d1);
Thread t3=new Thread(d1);
Thread t4=new Thread(d1);
t1.start();
t2.start();
t3.start();
t4.start();
for(int x=0;x<6;x++){
System.out.println(x+"*********"+Thread.currentThread().getName());
}
}
}
class demo1 implements Runnable{
public void run(){
show();
}
public void show(){
for(int x=0;x<10;x++){
System.out.println(x+"......."+Thread.currentThread());
}
}
}
Runnable接口的由来其实就是将线程的任务进行对象的封装。
将线程任务封装成对象后,通过Runnable接口可以降低和Thread对象的耦合性。
如果是继承Thread类,覆盖run方法这种情况,
Thread的子类即封装了线程的任务,而且自身还是一个线程对象
这就是任务和对象耦合性过高。
两种方法区别:
1、实现Runnable接口避免了单继承的局限性。
2、继承Thread类线程代码存放在Thread子类的run方法中, 实现Runnable接口线程代码存放在接口的子类的run方法中。
总结:在定义线程时,建议使用实现Runnable接口,因为几乎所有多线程都可以使用这种方式实现。
3、start()方法和run()方法的区别
调用run方法,仅仅是一般对象在调用对象中的方法,并没有开启线程,
还有主线程来完成的run方法的运行。
调用start方法,是开启了一个线程(一个新的执行路径。)
这个线程去执行了run方法。
总结:调用run方法,只是调用对象中的run()方法。
调用start()方法,开启线程并调用run()方法。
三、多线程的安全问题
安全问题产生的原因:
(1)、 多个线程在操作共享数据。
(2)、操作共享数据的代码有多条,一个线程在执行多条操作共享数据的过程中,其他线程参数与了运算,这时就会发生安全问题。
解决方案:只要保证一个线程在执行多条操作共享数据的语句时,其他线程不能参与运算即可。当该线程都执行完后,其他线程才可以执行这些语句。
四、多线程安全问题的解决:同步
1、同步:是用来解决多线程的安全问题的,在多线程中,同步能控制对共享数据的访问。如果没有同步,当一个线程在修改一个共享数据时,而另外一个线程正在使用或 者更新同一个共享数据,这样容易导致程序出现错误的结果。
2、同步的原理:其实就是将需要同步的代码进行封装,并在该代码上加了一个锁。
3、同步的前提:
必须要保证在同步中有多个线程。因为同步中只有一个线程该同步是没有意义。
必须要保证多个线程在同步中使用的是同一个锁。
4、锁 :
(1)、锁就是一个对象。
(2)、锁的作用是保证线程同步,解决线程安全问题。
(3)、持有锁的线程可以在同步中执行,没有锁的线程即使获得cpu执行权,也进不去。
5、同步的利弊:
利:同步解决了多线程的安全问题。
弊:同步需要判断锁,比较消耗资源,同步嵌套会出现死锁。
6、同步的三种形式:(1)、同步代码块:同步代码块的锁是任意对象。
synchronized(对象){
需要同步语句
}
(2)、同步函数:
同步函数的锁是this。
修饰词synchronized 返回值类型 函数名(参数列表)
{
需同步的代码;
}
(3)、静态同步函数
静态同步函数就是将静态方法进行同步。静态同步函数的锁是该方法所在的类的字节码文件对象,即类名·class文件。格式和同步函数差不多,只是锁不同而已。
7、线程间通信:多个线程在处理同一个资源。 但是处理的动作(线程的任务)却不相同。
wait():等待,将正在执行的线程释放其执行资格和执行权,并存储到线程池中。
notify():唤醒,唤醒线程池中被wait的线程,一次唤醒一个,而且是任意的。
notifyAll():唤醒全部,可以将线程池中的所有wait线程都唤醒。
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
JDK1.5后出现的新的接口和类:
将同步synchonized替换成了显示的Lock操作,将Object中的wait、notify、notifyAll替换成了Condition对象。该对象可以Lock锁进行获取。
Condition中提供了监视器的方法:await(),signal(),signalAll()。
五、多线程的单例设计模式
饿汉式。
class Single
{
private static final Single s = new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}
在被多线程并发访问时,会出现多线程安全问题。为了解决,加入了同步,虽然安全问题解决了,但是性能降低了。
为了解决安全问题,而且还可以提高性能,就使用了同步代码块,可以通过双重判断的形式来完成这个过程。
懒汉式(单例延迟的加载模式)
class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s = new Single();
}
}
return s;
}
}
class SingleDemo
{
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}
六、其他
1、停止线程:停止线程就是让run方法结束。
(1)、stop():有固有的不安全性。已过时。
(2)、结束run方法,只要控制住run中的循环即可,控制循环通常需要通过标记来完成。
2、守护线程(后台线程)
setDaemon():将该线程标记为守护线程或者用户线程。当主线程结束,守护线程自动结束。
守护线程的特点:
守护线程开启后和前台线程共同抢夺cpu的执行权,开启、运行两者都没区别,但结束时有区别,当所有前台线程都结束后,守护线程会自动结束。
3、加入线程
join():等待该线程终止。
join(long millis):等待该线程终止的时间最长为millis毫秒。
join方法会抛异常:throws InterruptedException
特点:当A线程执行到B线程的join方法时,A就会等待B都执行完,A才会执行。
作用:join方法可以用来临时加入线程执行。
4、wait()和sleep()方法的异同点:
(1)、这两个方法来自不同的类,sleep()来自Thread类,和wait()来自Object类。
(2)、sleep()是Thread的静态类方法,谁调用的谁就处于睡眠状态,即使在a线程里调用了b的sleep方法,实际上还是a睡眠,要让b线程处于睡眠状态还是要在b的代码中调用sleep方法。而wait方法是Object类的非静态方法。
(3)、sleep()释放资源不释放锁,而wait()释放资源释放锁。
(4)、使用范围:wait()、notify()和notifyAll()只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。