--------- android培训、java培训、期待与您交流! ----------
一、概述
1、进程:是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
2、线程:进程中的一个独立的控制单元,线程在控制着进程的执行。
如:
Java VM 启动时会有一个进程的java.exe,该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中,该线程这被称之为主线程。其实它又不止一个线程,还有负责垃圾回收机制的线程。
二、创建线程
1、继承Thread类。
2、复写Thread类中run方法。
复写原因:在API中可参考到,Thread类是用于描述线程的,其内部就定义了一个功能,即用于存储线程要运行的代码,该存储功能就是run方法,如果不复写,则run方法的初始值就为run(){},换句话说,Thread类中的run方法,用存储线程要运行的代码。
3、调用线程的start方法,该方法有两个作用:启动线程、调用run方法。
class MyThread extends Thread { public void run() { //为了看到打打印效果,可以写个for循环。 for(int = 0; x < 200; x++) { System.out.println("MyThread:"x); } } } class ThreadTest { public static void main(String[] args) { //创建一个线程 MyThread mt = new MyThread(); //开启并执行已经创建好的线程的run方法 mt.start(); //仅仅是对象调用方法,而线程创建了,并没有运行。 //mt.run(); for(int x = 0; x < 200; x++) { System.out.println("main:"+x); } } /* 发现运行结果每次都不同,因为多个线程都获取cpu的执行权,cpu 执行到谁,谁就运行。明确一点,在某一时刻,只能有一个程序运行 。(多核除外),而实际上CPU是在做着快速的切换,以达到看上去是同 时运行的效果,我们可以形象把多线程的运行行为在互相抢夺CPU的执 行权,这就是多线程的特性:随机性,由CPU决定执行和执行的时长。 */ }
三、线程运行状态
请参考以下图例:
四、获取线程对象以及名称
1、Thread-编号,该编号从0开始
2、Thread类中提供了如下一些方法:
静态方法:currentThread():获取当前线程对象。
对象方法:getName():获取线程名称。
设置线程名称:setName()或者构造函数(具体参阅API文档)。
五、创建线程另一种方式--实现Runnable接口
1、定义类实现Runnable接口
2、覆盖Runnable接口中的run方法。将线程要运行的代码存入在该run方法中。
3、通过Thread类建立线程对象。
4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
5、思考:
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。
6、调用Thread类的start方法,开启线程并调用Runnable接口子类的run方法。
class Ticket implements Runnable { private static int ticket = 100; //重写该接口的run方法。 public void run() { while(ticket > 0) { /* 当前线程类实现Runnable接口时,如果想获取当前线程,只能用 Thread.currentThread()方法。 */ System.out.println(Thread.currentThread().getName()+"----sale----"+ticket--); } } } //测试类 class TreadTest2 { public static void main(String[] args) { //创建一个实现了Runnable接口的Ticket实例 Ticket t = new Ticket(); //创建线程,并关联对象 Thread t1 = new Thread(t); Thread t2 = new Thread(t); //开启线程 t1.start(); t2.start(); /* 通过匿名对象的方式,可以把上面简化成如下代码: new Thread(t,"可以指定线程名称1").start(); new Thread(t,"可以指定线程名称2").start(); */ } }
六、实现方式和继承方式有什么区别呢?(面试常题)
1、实现方式好处:避免了单继承的局限性。所以在定义线程时,建议使用实现方式。
2、继承Thread:线程代码存放在Thread的子类run方法中。
实现Runnable:线程代码存放在接口的子类的run方法中。
七、线程的安全--同步代码
1、原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没
有执行完,另一个线程参与进来执行,导致共享数据的错误。
2、解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不
可以参与执行。
3、Java中对于线程的安全问题提供了专业的解决方式:同步代码
格式:synchronized(对象)
{
需要被同步的代码
}
4、同步的前提
①必须要有两个或两个以上的线程。
②必须是多个线程共享一个锁。
5、保证同步中只能有一个线程在运行
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
八、线程的安全--同步函数
1、三个明确:
①明确哪些代码是多线程运行代码。
②明确共享数据。
③明确多线程运行代码中哪些语句是操作共享数据的。
2、同步有两种表现形式:
①同步代码块:需要定义对象,多了一层缩进。
Object obj = new Object(); public void method(parameterType pt) { synchronized(obj) { //被操作的共享数据 } }
②同步函数:写法要比同步代码块要好些。
public synchronized void method(parameterType pt) { //被操作的共享数据 }
3、同步函数用的锁
①函数需要被对象调用,那么函数都有一个所属对象引用 ,就是this,所以同步函数使用的锁是this
②如果同步函数被静态修饰后,使用的锁又是什么呢?
被静态修饰后,内存中没有本类对象,但是一定有该类对应的字节码文件对象,所以静态的同步方法,使用的锁是该方法所在字节码文件对象,即:类名.class(在内在中是唯一的,因为字节码文件是唯一的);
4、关于懒汉式和饿汉式
①懒汉式和饿汉式有什么不同?
懒汉式的特点是实例的延时加载。
②懒汉式有没有问题?
有,如果多线程并发访问时,会出现安全隐患。
③怎么解决?
可以加同步来解决,加同步的方式有两种方式:同步函数,同步代码块,但是稍微有点低效。可用双重判断的形式可以解决效率上的问题。
④加同步的时使用的锁是哪一个?
该类所属的字节码文件对象。
九、死锁
当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有监测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁出现。
十、传统的线程通信
1、线程间通信,其实就是多个线程在操作同一个资源。但是操作的动作不同。
2、对于使用synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器。所以可以在同步方法中直接调用以下3个方法。
对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里
的对象,所以必须使用该对象调用这以下3个方法。
①wait():导致当前线程等待,直到其线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。调用wait()方法的当前线程会释放对该同步监视器的锁定。
②notify():唤醒在此同步监视器上等待的单个线程(如果有多个,会唤醒任意中的一个)。
只有当前线程放弃对同步监视器的锁定后(使用wait()方法),其它被唤醒的线程才可以执行。
③唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对同步监视器的锁定后(使用wait()方法),其它被唤醒的线程才可以执行。
3、思考:为什么这些操作线程的方法被定义在Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程才可以被同一个锁上notify唤醒,不可以对不同锁中的线程进行唤醒,也就是说,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
十一、使用Condition控制线程通信
JDK1.5中提供了多线程升级解决方案:
将同步synchronized替换成现实的Lock操作。
await;sinal();signalAll();