一.线程概述
进程: 是一个正在执行中的程序。
每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元。
线程在控制着进程的执行。
Java VM 启动的时候会有一个进程java.exe.
该进程中至少一个线程负责java程序的执行。
而且这个线程运行的代码存在于main方法中。
该线程称之为主线程。
扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。
Java VM 启动的时候会有一个进程java.exe
一个进程中至少有一个线程。
每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元。
线程在控制着进程的执行。
Java VM 启动的时候会有一个进程java.exe.
该进程中至少一个线程负责java程序的执行。
而且这个线程运行的代码存在于main方法中。
该线程称之为主线程。
扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。
Java VM 启动的时候会有一个进程java.exe
一个进程中至少有一个线程。
多线程的意义:
透过上面的例子,可以看出,多线程有两方面的意义:
1)提高效率。 2)清除垃圾,解决内存不足的问题
-------------------------------------------------------------------------------------------------------------------
二.自定义线程
线程有如此的好处,那要如何才能通过代码自定义一个线程呢?其实,线程是通过系统创建和分配的,java是不能独立创建线程的;但是,java是可以通过调用系统,来实现对进程的创建和分配的。java作为一种面向对象的编程语言,是可以将任何事物描述为对象,从而进行操作的,进程也不例外。我们通过查阅API文档,知道java提供了对线程这类事物的描述,即Thread类。创建新执行线程有两种方法:
一)创建线程方式一:继承Thread类。
1、步骤:
第一、定义类继承Thread。
第二、复写Thread类中的run方法。
第三、调用线程的start方法。分配并启动该子类的实例。
start方法的作用:启动线程,并调用run方法。
eg:
class Demo extends Thread
{
public void run()
{
for (int i=0;i<60;i++)
System.out.println(Thread.currentThread().getName() + "demo run---" + i);
}
}
class Test2
{
public static void main(String[] args)
{
Demo d1 = new Demo();//创建一个对象就创建好了一个线程
Demo d2 = new Demo();
d1.start();//开启线程并执行run方法
d2.start();
for (int i=0;i<60;i++)
System.out.println("Hello World!---" + i);
}
}
运行特点:
A.并发性:我们看到的程序(或线程)并发执行,其实是一种假象。有一点需要明确:;在某一时刻,只有一个程序在运行(多核除外),此时cpu是在进行快速的切换,以达到看上去是同时运行的效果。由于切换时间是非常短的,所以我们可以认为是在并发进行。
B.随机性:在运行时,每次的结果不同。由于多个线程都在获取cpu的执行权,cpu执行到哪个线程,哪个线程就会执行。可以将多线程运行的行为形象的称为互相抢夺cpu的执行权。这就是多线程的特点,随机性。执行到哪个程序并不确定。
3、覆盖run方法的原因:
1)Thread类用于描述线程。该类定义了一个功能:用于存储线程要运行的代码,该存储功能即为run方法。也就是说,Thread类中的run方法用于存储线程要运行的代码,就如同main方法存放的代码一样。
2)复写run的目的:将自定义代码存储在run方法中,让线程运行要执行的代码。直接调用run,就是对象在调用方法。调用start(),开启线程并执行该线程的run方法。如果直接调用run方法,只是将线程创建了,但未运行。
二)创建线程方式二:实现Runnable接口
1、步骤:
第一、定义类实现Runnable接口。
第二、覆盖Runnable接口中的run方法。
第三、通过Thread类建立线程对象。要运行几个线程,就创建几个对象。
第四、将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
第五、调用Thread类的start方法开启线程,并调用Runnable接口子类的run方法。
eg:
多个窗口同时卖票
class Ticket implements Runnable
{
private int tic = 20;
public void run()
{
while(true)
{
if (tic > 0)
System.out.println(Thread.currentThread().getName() + "sale:" + tic--);
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
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();
}
}
实现方式与继承方式有何区别:
1、实现方式:避免了单继承的局限性。
在定义线程时,建议使用实现方式。
2区别:
继承Thread:线程代码存放在Thread子类的run方法中。
实现Runnable:线程代码存在接口的子类run方法中。
需要注意的是:局部变量在每一个线程中都独有一份
-------------------------------------------------------------------------------------------------------------------
三.线程运行状态
A.阻塞状态:具备运行资格,但是没有执行权,必须等到cpu的执行权,才转到运行状态,。
B.冻结状态:放弃了cpu的执行资格,cpu不会将执行权分配给这个状态下的线程,必须被唤醒后,此线程要先转换到阻塞状态,等待cpu的执行权后,才有机会被执行到。
四、多线程的安全问题:
在那个简单的卖票小程序中,发现打印出了0、-1、-2等错票,也就是说这样的多线程在运行的时候是存在一定的安全问题的。
为什么会出现这种安全问题呢?
原因是当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还未执行完,另一线程就参与进来执行了,导致共享数据发生错误。以也就是说,由于cpu的快速切换,当执行线程一时,tic为1了,执行到if (tic > 0)的时候,cpu就可能将执行权给了线程二,那么线程一就停在这条语句了,tic还没减1,仍为1;线程二也判断if (tic> 0)是符合的,也停在这,以此类推。当cpu再次执行线程一的时候,打印的是1号,执行线程二的时候,是2号票,以此类推,就出现了错票的结果。其实就是多条语句被共享了,如果是一条语句,是不会出现此种情况的。
那么该如何解决呢?
对于多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可参与执行,就不会出现问题了。Java对于多线程的安全问题,提供了专业的解决方式,即同步代码块,可操作共享数据。
1、同步代码块
其中的对象如同锁,持有锁的线程可在同步中执行,没有锁的线程,即使获得cpu的执行权,也进不去,因为没有获取锁,是进不去代码块中执行共享数据语句的。
1)同步的前提:
A.必须有两个或两个以上的线程
B.必须保证同步的线程使用同一个锁。必须保证同步中只能有一个线程在运行。
好处与弊端:解决了多线程的安全问题。多个线程需要判断锁,较为消耗资源。
eg:
class Ticket implements Runnable
{
private int tic = 100;
Object obj = new Object();
public void run()
{
while(true)
{
synchronized(obj)//任意的一个对象
{
//此两句为共享语句
if (tic > 0)
System.out.println(Thread.currentThread().getName() + "sale:" + tic--);
}
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t,"1");//创建第一个线程
Thread t2 = new Thread(t,"2");//创建第二个线程
//开启线程
t1.start();
t2.start();
}
}
-------------------------------------------------------------------------------------------------------------------
四.同步函数锁
1、非静态同步函数中的锁---> this
函数需被对象调用,那么函数都有一个所属的对象引用,就是this,因此同步函数使用的锁为this
eg:
class Ticket implements Runnable
{
private int tic = 100;
boolean flog = true;
public void run()
{
if (flog)
{
//线程一执行
while(true)
{
//如果对象为obj,则是两个锁,是不安全的;换成this,为一个锁,会安全很多
synchronized(this)
{
if (tic > 0)
System.out.println(Thread.currentThread().getName() + "--cobe--:" + tic--);
}
}
}
//线程二执行
else
while(true)
show();
}
public synchronized void show()
{
if (tic > 0)
System.out.println(Thread.currentThread().getName() + "----show-----:" + tic--);
}
}
class ThisLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);//创建一个线程
Thread t2 = new Thread(t);//创建一个线程
t1.start();
t.flog = false;//开启线程一,即关闭if,让线程二执行else中语句
t2.start();
}
}
静态同步函数中的锁:
如果同步函数被静态修饰后,经验证,使用的锁不是this了,因为静态方法中不可定义this,所以,这个锁不再是this了。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名.class;该对象的类型是Class。
所以静态的同步方法使用的锁是该方法所在类的字节码文件对象,即类名.class。
eg:
class Ticket implements Runnable
{
//私有变量,共享数据
private static int tic = 100;
boolean flog = true;
public void run()
{
//线程一执行
if (flog)
{
while(true)
{
synchronized(Ticket.class)//不再是this了,是Ticket.class
{
if (tic > 0)
System.out.println(Thread.currentThread().getName() + "--obj--:" + tic--);
}
}
}
//线程二执行
else
while(true)
show();
}
public static synchronized void show()
{
if (tic > 0)
System.out.println(Thread.currentThread().getName() + "----show-----:" + tic--);
}
}
class StaticLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);//创建第一个线程
Thread t2 = new Thread(t);//创建第二个线程
t1.start();
t.flog = false;
t2.start();
}
}
其他状态:
守护线程:---setDaemon()
可将一个线程标记为守护线程,直接调用这个方法。此方法需要在启动前调用守护线程在这个线程结束后,会自动结束,则Jvm虚拟机也结束运行
临时加入线程:--join()
特点:当A线程执行到B线程方法时,A线程就会等待,B线程都执行完,A才会执行。join可用来临时加入线程执行
eg:
class Demo implements Runnable{
public void run(){
for(int x=0;x<90;x++){
System.out.println(Thread.currentThread().getName() + "----run" + x);
}
}
}
class JoinDemo{
public static void main(String[] args)throws Exception{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
t1.join();//等t1执行完了,主线程才从冻结状态恢复,和t2抢执行权。t2执不执行完都无所谓。
int n = 0;
for(int x=0;x<80;x++){
System.out.println(Thread.currentThread().getName() + "----main" + x);
}
System.out.println("Over");
}
}
优先级:
setPriority():
在Thread中,存在着1~10这十个执行级别,最高的是 MAX_PRIORITY 为10,最低是 MIN_PRIORITY 为1,默认优先级是 NORM_PRIORITY 为5;但是并不是优先级越高,就会一直执行这个线程,只是说会优先执行到这个线程,此后还是有其他线程会和此线程抢夺cpu执行权的。
优先级是可以设定的,可通过setPriority()设定,如:setPriority(Thread.MAX_PRIORITY)设优先级为最大。
yield():
此方法可暂停当前线程,而执行其他线程。通过这个方法,可稍微减少线程执行频率,达到线程都有机会平均被执行的效果