黑马程序员——多线程
-----------android培训、java培训、java学习型技术博客、期待与您交流!------------
一、进程与线程
进程:是一个正在执行的程序,每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行
一个进程中至少有一个线程。
例:JVM启动的时候会有一个进程java.exe。该进程中至少一个线程负责java程序执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。
扩展:其实更细节说明jvm启动不止一个线程,还有负责垃圾回收机制的线程。
二、多线程的意义
多线程提高了cpu的使用率。从而提高应用程序的使用率。线程和线程共享“堆内存和方法区内存”,栈是独立的,一个进程一个栈。
三、线程的创建方式
1.如何在自定义代码中,自定义一个线程?
通过对API的查找,java已经提供了对线程这类事物的描述,就是Thread类。
创建多线程的第一种方式:继承Thread类
步骤:
1.定义类继承Thread。
2.复写Thread类中的run方法
目的:将自定义的代码存储在run方法,让线程运行
3.调用线程的start方法
class ThreadDemo{
public static void main(String[] args) {
//创建好一个线程
Create td = new Create();
//启动线程,而不是直接调用run方法。线程的执行是由java线程调度机制完成的。
td.start();
td.run();//仅仅是对象的调用方法,而线程创建了,并没有运行。
for(int x = 0; x < 200; x++)
{
System.out.println("Demo " + x);
}
}
}
//创建一个类,继承之Thread类
class Create extends Thread
{
//复写Thread类中的run方法。定义自己的内容
public void run(){
for(int x = 0; x < 200; x++){
System.out.println( "Create " + x);
}
}
}
最后发现运行结果每次都不同。因为多个线程都获取cpu的执行权。Cup执行倒水,谁就运行。明确一点,在某一时刻,只能有一个程序在运行,CPU在做着快速切换,已达到貌似同时运行的效果。
2.为什么要覆盖run方法?
Thread类用于描述线程。该类就定义一个功能,用于存储线程要运行的代码。该存储功能就是run方法。
3.线程的5中状态
sleep
sleep结束
wait();
notify()
stop()
run方法结束
4.线程对象以及名称
线程都有自己的默认名称:Thread-编号该编号从0开始。
线程的几个方法:
1.static Thread currentThread():获取当前线程对象。
2.getName():获取线程的名称。
3.设置线程名称:setName或者构造函数。
5. 创建线程的第二种方式:实现Runnable接口。
步骤:
1.定义类实现Runnable接口;
2.覆盖Runnable接口中的run方法;
3.通过Thread类建立线程对象;
4.将Runable接口的子类对象作为实际参数传递给Thread类的构造函数;
为什么要将Runnable接口的子类对象传递给Thread的构造函数。
因为自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定指定对象的run方法,就必须明确该run方法所属的对象
5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
class RunnableThreadDemo {
public static void main(String[] args) {
//创建卖票类实例对象
Ticket t = new Ticket();
//将通过Thread类建立线程对象;
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
//启动线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//实现Runnable接口
class Ticket implements Runnable
{
private int ticket = 100;
public void run() {
while(true)
{
if(ticket > 0)
{
System.out.println(Thread.currentThread().getName() + "票号" + ticket--);
}
}
实现方式和继承方式有什么区别?
实现方式的好处:避免了单继承的局限性,在定义线程时,建议使用实现方式
两种方法的区别:
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法
四、多线程的特性
1.多线程的安全问题:
通过分析,发现上述程序打印出0,-1,-2等错票。说明多线程运行出现了安全问题。
问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另外一个线程参与进来执行,导致共享数据的错误。
解决方法:
对多条操作共享数据的语句,只能让一个线程执行完,在执行过程中,其他线程不可以参与运行。
Java对于多线程的安全问题提供了解决方式:同步代码块
synchronized(对象)
{
需要被同步的代码
}
class Ticket implements Runnable
{
private int ticket = 100;
Object obj=new Object();
public void run() {
while(true)
{
synchronized(obj){
if(ticket > 0)
{
System.out.println(Thread.currentThread().getName() + "票号" + ticket--);
}
}
}
同步代码块如同给对象加锁,持有锁的线程可以在同步中执行,
没有持有锁的线程即使获取了cpu的执行权,也进不到同步代码块中,因为没有获取锁。
同步的前提:
1.必须要有两个或者两个以上的线程
2.必须是多个线程使用同一个锁
必须保证同步中只能有一个线程在运行
好处和弊端:
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源。
同步有两种表示:同步代码块和同步函数。
如何找同步:
1.明确哪些代码是多线程运行代码。
2.明确共享数据。
3.明确多线程运行代码中那些语句是操作共享数据的。
2.单例设计模式-懒汉式
Class Single{
Private static Single s=null;
Private Single(){}
Public static synchronized Single getInstance(){
if(s==null){
Synchronized(Single.class){
if(s=null)
s=new Single();
}
}
return s;
3.停止线程
停止方法1:run方法结束。开启多线程运行,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:
当线程处于了冻结状态,就不会读取到标记。那么线程就不会结束。
当没有指定的方式让冻结的线程恢复带运行状态时,这需要对冻结进行清除。
停止方法2:使用interrupt()方法。结束线程的冻结状态(sleep、wait、join方法),使线程回到运行状态中来。也就是靠异常机制来结束线程。
4.等待唤醒机制
1. wait();notify();notifyall();都是用在同步中,因为要对持有监视器(对象锁)的线程操作,而同步中才有锁。
2.这些操作方法定义在Object类中。因为这些方法在操作同步中线程时,都必须要标识他们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。