一、 进程、线程的概念
1)、进程:每个运行中的程序都是一个进程。
多个进程可以在单个处理器上并发执行,多个进程之间不会相互影响。注意:并发性和并行性是两个概念,并行性是指在同一时刻,有多条指令在多个处理器上同时执行;并发性是指同一时刻只能有一条指令在单个处理器上执行,但多个进程指令被快速轮换执行,但是在宏观上具有多个进程同时执行的效果。
2)、线程:线程在进程中的地位,如同进程在操作系统中的地位。当一个程序运行时,内部包含了多个顺序执行流,每个顺序执行流就是一个线程。
总结来说:操作系统可以同时执行多个任务,每个任务都是进程;进程可以执行多个任务,每个任务就是线程。一个进程可以包含多个线程,但至少包含一个线程。
a.单线程:单线程的程序只有一个顺序执行流
b.多线程:多线程程序包含多个顺序执行流,多个顺序流之间互不干扰。
单线程的程序如同只雇用一个服务员的餐厅,他必须做完一件事之后才能做另外一件事情;多线程的程序如果雇佣了多个服务员的餐厅,他们可以同时做多件事情,并且互不干扰。
二、 创建线程的两种方式:
1、继承Thread类创建线程类
a.定义Thread类的子类,并重写该类run()方法。run()方法内是线程要执行的代码,因此run()方法称为线程的执行体。
b.创建Thread类的子类实例,即创建线程对象。
c.调用线程对象start()方法,启动线程(底层是让Java虚拟机调用线程的run()方法)。
代码实例:
class Demo extends Thread
{
public void run()
{
for(int i=0; i<100; i++)
System.out.println("Demo run");
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d = new Demo();//创建一个线程
d.start();//开启线程,执行线程的run方法。
//d.run();//仅仅是对象调用方法。而线程创建了,并没有运行。
for(int i = 0; i < 100; i++)
System.out.println("Hello World---"+i);
}
}
2、实现Runnable接口创建线程类
a.定义Runnable接口的实现类,并重写该接口的run()方法。
b.通过Thread类创建线程对象,将实现Runnable接口的实现类对象作为实际参数传递给Thread类的构造函数。原因:让线程对象明确要运行的run()方法所属的对象。
C.调用Thread对象的start()方法。开启线程,并运行Runnable接口实现类的run()方法。
代码示例:
class Ticket implements Runnable
{
private int tick = 100;
public void run()
{
while(true)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
//假设0线程在条件tick=1时,进来sleep了一会。1线程进来,也睡一会,2线程进来,也睡一,3,线程进来也睡会。
//0线程唤醒后,打印1,线程1,2,3醒后,不需要再判断,向下运行打印出0,-1,-2号票,存在安全问题。
System.out.println(Thread.currentThread().getName()+"sale: "+tick--);
}
}
}
}
class TicketDemo1
{
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();
//new Thread(new Ticket()).start();
}
}
注意:
1)、使用继承Thread类的方法创建线程时,多线程之间无法共享线程类的实例变量。如果定义为static静态的话,虽然可行但是周期太长,浪费空间。
2)、使用实现Runnable接口的方法创建线程,虽然可以实现资源共享,但是会出现安全问题。比如在上边代码中,假设0线程在条件tick=1时,进来sleep了一会。1线程进来,也睡一会,2线程进来,也睡一,3,线程进来也睡会。0线程唤醒后,打印1,线程1,2,3醒后,判断过不需要再判断,向下运行打印出0,-1,-2号票,存在安全问题。
三、 线程状态
1、新建:用new关键字创建线程对象,该对象个由Java虚拟机为其分配内存,并初始化其成员变量。
2、就绪:线程对象调用start()方法,该线程就进入了就绪状态。就绪状态,线程拥有知执行资格,但是没有执行权(是指线程调度程序没有把该线程设置为当前线程)。线程从等待或者睡眠中回来后,也处于就绪状态。
3、运行:线程具备执行资格,并获取执行权(是指线程调度程序把该线程设置为当前线程),开始运行run()方法中的代码。
4、阻塞:线程释放了执行权,也不具备执行资格。被阻塞的线程只有在合适的时候重新进入就绪状态(不是运行状态),才获取执行资格。等待线程调度器调度它,才有执行权,进而进入运行状态。
1)、运行——>阻塞状态的几种常见情况:
a.线程调用sleep方法。其实是进入了冻结状态:它占着线程,让线程睡觉,别人无法使用。
b.线程调用一个阻塞式IO方法,read(),readLine(),等,方法返回之前,该线程被阻塞。
c.线程视图获取一个synchronized锁,但是这个锁被其他线程持有。
d.线程调用wait()方法。
2)、阻塞状态——>就绪状态的集合总情况:
a.过了sleep()方法中定义的时间。
b.阻塞式IO方法已经返回。
c.线程成功获取视图获取的同步锁。
d.线程等到了其他线程的通知,如notify、notifyAll。
5、死亡:
a.run()方法执行完成,线程结束。
b.线程抛出一个未捕获的Error,或者抛出Error。
c.调用stop()方法结束线程,该方法容易导致死锁,已过时。
四、 多线程的安全问题
1、多线程出现安全问题的两个因素
a.多个线程操作共享数据。
b.有多条语句对共享数据进行运算。(原因:一个线程对多条语句刚执行了一部分,另一个线程获取执行权,进来执行,导致共享数据错误。)
2、解决多线程的安全问题原理
只要让某一个线程执行共享数据过程中,不让其他线程参与执行。Java提供了同步这个概念——synchronized。使用同步的前提是:首先,必须有两个或者两个以上的线程;其次,多个线程必须使用同一把锁。
3、同步代码块和同步函数
同步代码块的格式:
synchronized(对象)//任意对象都可以,这个对象就是锁
{
需要被同步的代码
}
注意:
1)、如果用多个同步代码块时,他们必须使用同一个锁。
2)、同步函数使用的锁是this。
3)、当同步函数是static静态的话,它使用的锁是:该方法所在类的字节码文件对象—-类名.class。
同步代码块和同步函数代码示例:
class Ticket implements Runnable
{
private int tick = 100;
Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(this)//当改为obj后,打印出0号错票
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+" code...: "+tick--);
}
}
}
}else
while(true)
show();
}
public synchronized void show()//静态同步函数的锁是this
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...show...: "+tick--);
}
}
}
class ThisLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(100);}catch(Exception e){}
t.flag = false;
t2.start();
}
}
4、wait(),sleep()方法在线程中的区别
1)、线程调用wait()时,该线程释放执行权,其他线程可以获取到执行权。同时释放了锁。wait()及唤醒方法只在同步机制中使用。
2)、线程调用sleep()时,该线程进入冻结状态,线程睡着,其他线程可以获取到执行权。但是在同步中它不释放锁。
同步中的等待、唤醒机制代码示例:
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name)
{
while(flag)//1.t1跳过等待
try{this.wait();}catch(Exception e){}//4.t1等待,t2进来也等待 //10.t1首先重新持有锁,重新判断为false,不等待向下执行
this.name = (name+"----"+count++);
System.out.println(Thread.currentThread().getName()+"生产者..."+this.name);//2.生产1号商品,//11.t1生产2号商品
flag = true;//3.t1更改标记 //12.t1改变标记,
this.notifyAll();
//在等待的过程中,线程都释放了锁,当notifyAll时t1和t2都从等待中被唤醒,开始争夺锁的持有权,
//只有一个线程能持有锁,
}
public synchronized void out()
{
while(!flag)//5.t3跳过等待 //9.t3和t4都等待
try{this.wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);//6.正常消费1号商品,
flag = false;//7.t3更改标记
this.notifyAll();//8.t3把t1和t2都唤醒
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.set("商品");
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.out();
}
}
}
class ProduceConsumeDemo_1
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);//t1线程
Thread t2 = new Thread(pro);//t2线程
Thread t3 = new Thread(con);//t3线程
Thread t4 = new Thread(con);//t4线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
之所以wait()、notify()、notifyAll()被定义在Object类中,是因为锁是任意对象,而这些方法又是锁锁要操作的方法,可以被任意对象都调用的方法需要定义在Object类中。
六、 死锁
死锁:当在同步中嵌套同步,但是同步使用的锁不同,两个线程相互等待对方释放同步锁,这个时候就发生了死锁。程序一旦出现死锁,整个程序既不会发生任何异常,也不会有任何提示,只是所有线程出于阻塞状态,无法继续。
死锁代码示例:
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
synchronized(MyLock.lock1)
{
System.out.println("if lock1");
synchronized(MyLock.lock2)
{
System.out.println("if lock2");
}
}
}
else
{
synchronized(MyLock.lock2)
{
System.out.println("else lock2");
synchronized(MyLock.lock1)
{
System.out.println("else lock1");
}
}
}
}
}
class MyLock
{
static Object lock1 = new Object();
static Object lock2 = new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
1、如何避免死锁?
1)、当出现多个同步代码块(或者同步函数)时,尽量使用同一把锁,就可以在一定程度上避免死锁。以上代码把锁改为一样的,就解决了死锁现象。
2)、不使用synchronized关键字保证同步,直接用Lock对象保证同步。
2、多线程中的Lock机制:
1)、Lock接口中的lock()方法加锁,unlock()方法解锁。
2)、通过Condition接口中的await()和signal()方法,实现等待唤醒机制。
代码示例:class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();//创建一个Lock接口的子类实例,赋给接口引用
private Condition condition_pro = lock.newCondition();//lock调用方法返回一个Condition实现类实例,赋给接口引用
private Condition condition_con = lock.newCondition();
public void set(String name)throws InterruptedException
{
lock.lock();//加 锁
try
{
while(flag)
condition_pro.await();//不try,抛出
this.name = name+"----"+count++;
System.out.println(Thread.currentThread().getName()+"生产者..."+this.name);
flag = true;
condition_con.signal();
}
finally
{
lock.unlock();//解 锁————释放锁的动作一定要执行
}
}
public synchronized void out()throws InterruptedException
{
lock.lock();//加 锁
try
{
while(!flag)
condition_con.await();//不try,抛出
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
condition_pro.signal();
}
finally
{
lock.unlock();//解 锁
}
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.set("商品");//抛出异常的set();要catch()
}
catch(InterruptedException e)
{}
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.out();//抛出异常的out();要catch()
}
catch(InterruptedException e)
{}
}
}
}
class ProduceConsumeDemo1
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);//0线程
Thread t2 = new Thread(pro);//1线程
Thread t3 = new Thread(con);//2线程
Thread t4 = new Thread(con);//3线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
七、 控制线程
1)、join线程:当A线程执行到了该线程的join方法时,B线程和其他线程就没有了执行资格。当A线程执行完后,B线程就会和其他线程就获取了执行资格,等待线程调度器调度其中一个,如果此时B线程调用了join方法,其他线程就要等B线程执行完才能执行。 Join线程通常由使用线程的程序调用,以将大问题划分为许多小问题,每个小问题分配一个线程,当多有小问题都得到处理后,再调用主线程来进一步操作。
2)、后台线程:有一种线程它是后台运行的,它的任务是为其他线程提供服务,这种线程被称为“后台线程”,或者“守护线程”、“精灵线程”。垃圾回收机制就是典型的后台线程。我们把非后台线程称为前台线程。后台线程的特点是:如果所有前台线程都死亡,后台线程会自动死亡。调用Thread对象的setDaemon(true)方法可以将指定的线程设置为后台线程。而且将一个线程设置为后台前程,必须在开启这个线程之前设置,否则抛出异常。
3)、线程睡眠:当当前线程调用sleep()静态方法方法进入阻塞状态后,在其失眠的之间段内,该线程不会获得执行额机会,及时系统汇总美誉其他可执行的线程,出于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行。
4)、线程让步:yield,和sleep()方法相似,都是Thread提供的静态方法。但是yield()方法让正在执行的线程暂停,但它不会阻塞该线程,它只是让线程转入就绪状态。让系统的线程调度器重新调度一次。所以当A线程调用yield()方法暂停后,线程调度器重新分配线程执行权,分配给A线程是有可能。
八、 线程的优先级
Thread提供了setPriority(int newPriority)、getPriority()方法来设置和返回线程的优先级,其中setPriority()方法的参数可以是一个整数,范围是1~10之间,也可以使用Thread类的3个静态常量:
MAX_PRIORITY:其值为:10(最高优先级)
MIN_PRIORITY:其值为:1(最低优先级)
NORM_PRIORITY:其值为:5(中等优先级)
为避免误导初学者,本博客如有错误或者不严谨的地方,请在下方给予评论,以便及时修改!谢谢... ...