线程的概念
线程:是进程中的一个单一的连续控制流程。一个进程可以拥有多个线程。
线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单。
线程调度
在Java程序中,JVM负责线程的调度。线程调度是值按照特定的机制为多个线程分配CPU的使用权。
调度的模式有两种:分时调度和抢占式调度。分时调度是所有线程轮流获得CPU使用权,并平均分配每个线程占用CPU的时间;抢占式调度是根据线程的优先级别来获取CPU的使用权。JVM的线程调度模式采用了抢占式模式。
线程的生命周期
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
线程的常见方法
currentThread 获取当前线程对象
setName 设置线程名称
getName 获取线程名称
setPriority设置线程优先级(线程优先级高并不代表完全先运行,只是在数据量很大的时候可以体现出来)
getPriority获取线程优先级
sleep休眠(休眠状态不释放锁)
wait 等待(等待状态释放锁和CPU资源)
interrupt 中断休眠状态(可以把休眠状态或者等待状态终止然后继续执行下一步代码或者捕获异常)
yield 线程的礼让
join 线程的插队
setDaemon 守护线程(就是等到用户线程全部执行完之后,守护线程自动结束,即使守护线程的任务没有执行完)
线程的实现
在java语言中,线程的实现是两种方式,一种是继承Thread的方式,一种是实现Runnable的方式。
1,Thread继承方式
public class Thread1 {
public static void main(String[] args) {
ThreadDemo thread = new ThreadDemo();
thread.start();
}
}
class ThreadDemo extends Thread{
@Override
public void run() {
for (int i = 0;i < 10;i++){
System.out.println("Thread_i="+i);
}
}
}
2,Runnable实现方式
public class Thread1 {
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
new Thread(runnableDemo).start();
}
}
class RunnableDemo implements Runnable{
@Override
public void run() {
for (int i = 0;i < 10;i++){
System.out.println("Thread_i="+i);
}
}
}
3,举例
我们举一个熟悉的例子,就是火车站买票,大家都知道车站买票是多个窗口,那么一个窗口执行一个任务,既然是多个窗口,那么这就是多任务多线程的程序,既然多线程,我们就应该实现共享资源,什么是共享资源呢,就像我们总共100张票,这就是共享的资源,当窗口A售完了第一张票,那么其余的线程就只剩下99张票,然而线程一般都是抢占式线程(抢占式调度是根据线程的优先级别来获取CPU的使用权。JVM的线程调度模式采用了抢占式模式。)谁先抢到CPU资源谁就执行程序。
(1)Thread实现买票方式
public class Thread1 {
public static void main(String[] args) {
ThreadDemo thread1 = new ThreadDemo();
thread1.start();
ThreadDemo thread2 = new ThreadDemo();
thread2.start();
ThreadDemo thread3 = new ThreadDemo();
thread3.start();
ThreadDemo thread4 = new ThreadDemo();
thread4.start();
}
}
class ThreadDemo extends Thread{
//加上static,进行线程间的共享,只加载一次这个变量
// 如果不加这个的话会出现每次创建一个线程都是100张票
static int tickets = 100;
@Override
public void run() {
while (true){
if (tickets<=0){
System.out.println("票已经售完!");
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余"+(--tickets)+"张票");
}
}
}
大家可以运行几次这个代码,他可以实现总共100张票,但是也会出现一个bug,多运行几次就可以观察出来,这个问题我们先留着,之后再讲。
(2)Runnable实现买票方式
public class Thread1 {
public static void main(String[] args) {
RunnableDemo demo = new RunnableDemo();
new Thread(demo,"窗口A").start();
new Thread(demo,"窗口B").start();
new Thread(demo,"窗口C").start();
new Thread(demo,"窗口D").start();
}
}
class RunnableDemo implements Runnable{
int tickets = 100;
@Override
public void run() {
while (true){
if (tickets<=0){
System.out.println("票已经售完!");
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余"+(--tickets)+"张票");
}
}
}
(3)对比,我们可以看到,Runnable的实现方式和Thread的实现方式是不一样的,Thread中创建了四个对象,然而Runnable中创建了三个对象,所以Runnable中票数没有进行static。
看一下这个Thread类中run方法的源码,当target不为空的时候运行target的run方法,为空的时候就运行自己的run方法,自己的run方法就是Thread实现的run方法。target就是Runnable对象。
@Override
public void run() {
if (target != null) {
target.run();
}
}
线程的同步锁
我们前边提到了会出现票数为负数的情况,这种情况就是因为多线程出现了数据安全问题。
问题的原因:
当多条语句在操作同一个共享数据的时候一条线程对多条语句只执行了一部分,还没有执行完,就出现了另一个线程参与进来执行,导致数据共享的错误。就像卖票那个例子,当窗口A执行到sleep的时候,线程B进入操作,然后进入if语句,此时票数还没有减1,这时B进入sleep语句,然后A进行–tickets操作,所以就导致了线程B获取的数据错误的问题。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中其余线程处于等待状态,不参与进来。
我们都用synchronized关键字和对象配合使用。
同步的局限性,导致程序的执行效率降低。
同步方法(非静态的)的锁是this。
同步方法(静态的)的锁为当前类本身。
要求:多线程使用的锁对象必须是同一个。
我们还用卖票那个系统来演示,加上锁之后就不会出现共享数据的错误问题。锁就是把一个对象给锁住,当这个资源进行执行的时候,其余的进不去这个操作。
public class ThreadTicket {
public static void main(String[] args) {
SellTicket2 sellTicket = new SellTicket2();
new Thread(sellTicket,"窗口A").start();
new Thread(sellTicket,"窗口B").start();
new Thread(sellTicket,"窗口C").start();
}
}
//方式一,使用同步对象
class SellTicket implements Runnable{
int tickets = 100;
@Override
public void run() {
synchronized (this){
while (true){
if (tickets<=0){
System.out.println("票已经售完!");
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余"+(--tickets)+"张票");
}
}
}
}
//使用同步方法
class SellTicket2 implements Runnable{
int tickets = 100;
boolean loop = true;
@Override
public void run() {
while (loop){
sellTicket();
}
}
public synchronized void sellTicket(){
if (tickets<=0){
System.out.println("票已经售完!");
loop = false;
return;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余"+(--tickets)+"张票");
}
}