核心内容:
1、Java中线程的概念以及注意事项
2、Java中创建线程的两种方式
3、Java中创建线程两种方式的比较
4、线程同步的问题、线程同步问题的由来、如何解决线程同步的问题
1、Java中线程的概念以及注意事项 |
线程的概念:
①线程是一个程序里的不同执行路径;
②以前所编写的程序,每个程序都只有一个入口、一个出口以及一个顺序执行的序列,在程序执行过程中的任何指定时刻,都只有一个单独的执行点;
③事实上,在单个程序的内部是可以在同一时刻进行多种运算的,这就是所谓的多线程。
线程的注意事项:
①aa.start()具有两层含义:向下开辟(创建)一个新的线程,并执行本线程所对应的run方法;
②执行一个线程实际上就是执行本线程所对应的run方法中的代码;
③执行完aa.start()之后并不表示aa对象所对应的线程立即得到执行,aa.start()执行完之后只是表明aa对象所对应的线程具有了可以被cpu执行的资格,但由于想抢占cpu执行的线程很多,cpu并不一定会立即去执行aa对象所对应的线程,此时aa对象对应的线程将处于阻塞状态。一个线程对应三种不同的状态:阻塞状态、就绪状态、运行状态
④一个Thread对象能且只能代表一个线程
2、Java中创建线程的两种方式 |
Java中创建线程有两种方式:
①继承Thread类,并重写run方法
②实现Runnable接口,并实现run方法
实例程序1:继承Thread类,并重写run方法的方式创建线程
public class App1
{
public static void main(String[] args) throws InterruptedException
{
A aa = new A();
aa.start(); //aa.start具有两层含义:向下开辟(创建)一个新的线程,并执行本线程所对应的run方法
while(true)
{
System.out.println("Hadoop");
}
}
}
class A extends Thread
{
@Override
public void run()
{
while(true)
{
System.out.println("Spark");
}
}
}
运行结果:hadoop与spark交替的输出
Spark
Spark
Spark
Spark
Spark
Spark
Spark
Spark
Spark
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
实例程序2:实现Runnable接口,并实现其中的run方法的方式创建线程
public class App1
{
public static void main(String[] args) throws InterruptedException
{
Thread aa = new Thread(new A());
aa.start(); //aa.start具有两层含义:向下开辟(创建)一个新的线程,并执行本线程所对应的run方法
while(true)
{
System.out.println("Hadoop");
}
}
}
class A implements Runnable
{
public void run()
{
while(true)
{
System.out.println("Spark");
}
}
}
运行结果:hadoop与spark交替的输出
Spark
Spark
Spark
Spark
Spark
Spark
Spark
Spark
Spark
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
从上面创建线程的两种方式可以看出,无论通过哪种方式创建线程,必须调用Thread类中的start方法才能开辟一个新的线程。
3、Java中创建线程两种方式的比较 |
为什么Java要提供两种方法来创建线程呢?它们都有哪些区别?相比而言,哪一种方法更好呢?在给出具体结论之前,我们先用Java中的线程编程实现生活中的几个具体场景:
场景1:假设一个影院有三个售票口,分别用于向儿童、成人和老人售票。影院为每个窗口放有100张电影票,分别是儿童票、成人票和老人票。三个售票口各自售票,互不影响。
①、通过继承Thread类的方式来完成这个程序
public class App1
{
public static void main(String[] args) throws InterruptedException
{
A t1 = new A();
A t2 = new A();
A t3 = new A();
t1.setName("儿童窗口");
t2.setName("成人窗口");
t3.setName("老人窗口");
//同时开辟三个子线程
t1.start();
t2.start();
t3.start();
}
}
class A extends Thread
{
public int tickets = 100;
@Override
public void run()
{
while(true)
{
if(tickets > 0)
{
System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
tickets--;
}
}
}
}
②、通过实现Runnable接口的方式来完成这个程序
public class App1
{
public static void main(String[] args) throws InterruptedException
{
Thread t1 = new Thread(new A());
Thread t2 = new Thread(new A());
Thread t3 = new Thread(new A());
t1.setName("儿童窗口");
t2.setName("成人窗口");
t3.setName("老人窗口");
//同时开辟三个子线程
t1.start();
t2.start();
t3.start();
}
}
class A implements Runnable
{
public int tickets = 100;
public void run()
{
while(true)
{
if(tickets > 0)
{
System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
tickets--;
}
}
}
}
运行结果(部分截取):
老人窗口正在售第:53张票
成人窗口正在售第:12张票
老人窗口正在售第:52张票
成人窗口正在售第:11张票
老人窗口正在售第:51张票
成人窗口正在售第:10张票
老人窗口正在售第:50张票
成人窗口正在售第:9张票
老人窗口正在售第:49张票
成人窗口正在售第:8张票
老人窗口正在售第:48张票
成人窗口正在售第:7张票
老人窗口正在售第:47张票
成人窗口正在售第:6张票
老人窗口正在售第:46张票
成人窗口正在售第:5张票
老人窗口正在售第:45张票
成人窗口正在售第:4张票
老人窗口正在售第:44张票
成人窗口正在售第:3张票
在上面的这个实例场景中, 不同的售票口相当于不同的线程,不同种类的票相当于不同的资源。
可以总结出:若多个线程处理的是不同的资源,两种创建线程的方式均可。
场景2:有3个售票口,共同售出电影院的1000张票,3个售票口相当于3个线程,处理的是相同的资源(1000张电影票)
①、通过继承Thread类的方式来完成这个程序
public class App1
{
public static void main(String[] args) throws InterruptedException
{
A t1 = new A();
A t2 = new A();
A t3 = new A();
t1.setName("窗口1");
t1.start();
t2.setName("窗口2");
t2.start();
t3.setName("窗口3");
t3.start();
}
}
class A extends Thread
{
public static int tickets = 1000; //静态的属性和方法属于类本身的,由操作系统只分配一块内存空间
public static String str = "Java";
public void run()
{
while(true)
{
synchronized(str) //创建同步代码块
{
if(tickets > 0)
{
System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
tickets--;
}
}
}
}
}
运行结果:(部分截图)
窗口1正在售第:202张票
窗口1正在售第:201张票
窗口1正在售第:200张票
窗口1正在售第:199张票
窗口2正在售第:198张票
窗口2正在售第:197张票
窗口2正在售第:196张票
窗口2正在售第:195张票
窗口2正在售第:194张票
窗口2正在售第:193张票
②、通过实现Runnable接口的方式来完成这个程序
public class App1
{
public static void main(String[] args) throws InterruptedException
{
A target = new A(); //三个售票口处理的是相同的资源,所以我们new出一个对象
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
t1.setName("窗口1");
t1.start();
t2.setName("窗口2");
t2.start();
t3.setName("窗口3");
t3.start();
}
}
class A implements Runnable
{
public int tickets = 1000;
public void run()
{
while(true)
{
synchronized(this) //创建同步代码块
{
if(tickets > 0)
{
System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
tickets--;
}
}
}
}
}
运行结果:(部分截图)
窗口2正在售第:966张票
窗口2正在售第:965张票
窗口2正在售第:964张票
窗口2正在售第:963张票
窗口2正在售第:962张票
窗口2正在售第:961张票
窗口1正在售第:960张票
窗口1正在售第:959张票
窗口1正在售第:958张票
窗口1正在售第:957张票
窗口1正在售第:956张票
窗口1正在售第:955张票
窗口1正在售第:954张票
可以总结出:若多个线程处理的是相同的资源,最好通过实现Runnable接口的方式创建线程,不然的话通过继承Thread的方式显得过于繁琐。
4、Java中线程同步的问题,线程同步问题的由来,如何解决线程同步的问题/td> |
什么是线程同步?
所谓线程同步就是多个线程在处理相同资源的时候,保证共享数据的数据一致性和变化一致性
线程同步问题的由来?
导致线程同步的原因共有两个:
①多个线程彼此之间处理的是相同的资源(比如3个窗口共同售出1000张票)
②多个线程彼此之间在处理相同关键步骤的时候,在这些关键的步骤没有执行完毕的时候,CPU会切换到另外一个线程去执行这些关键的步骤,导致共享数据的一致性出现问题
实例程序:(线程同步出错)
public class App1
{
public static void main(String[] args) throws InterruptedException
{
A target = new A(); //三个售票口处理的是相同的资源,所以我们new出一个对象
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
t1.setName("窗口1");
t1.start();
t2.setName("窗口2");
t2.start();
t3.setName("窗口3");
t3.start();
}
}
class A extends Thread
{
public int tickets = 1000;
public void run()
{
while(true)
{
if(tickets > 0)
{
System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
tickets--;
}
}
}
}
运行结果:(部分截取)
窗口2正在售第:10张票
窗口2正在售第:9张票
窗口2正在售第:8张票
窗口2正在售第:7张票
窗口2正在售第:6张票
窗口2正在售第:5张票
窗口2正在售第:4张票
窗口2正在售第:3张票
窗口2正在售第:2张票
窗口2正在售第:1张票
窗口1正在售第:113张票
窗口3正在售第:111张票
从上面的程序可以看出,3个线程在处理相同资源的时候(1000张票)的时候,共享数据的一致性出现了问题,其原因在于:
if(tickets > 0)
{
System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
tickets--;
}
这个代码块并不是一个不可分割的整体。
在Java当中,如何解决线程同步的问题?
在Java当中是通过synchronized语法机制来解决线程同步的问题的,通过synchronized语法机制,保证这些关键的步骤在被某一个线程访问或者执行的时候,其余线程不能在执行这些关键的步骤(尽管CPU仍在多个线程之间来回切换),直到这个线程将这些关键的步骤执行完毕,其余线程才能执行这些关键的步骤;
Java中的synchronized语法机制类似于数据库中的事务性处理机制。
实例程序:通过synchronized语法机制来解决上面的线程同步问题
public class App1
{
public static void main(String[] args) throws InterruptedException
{
A target = new A(); //三个售票口处理的是相同的资源,所以我们new出一个对象
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
t1.setName("窗口1");
t1.start();
t2.setName("窗口2");
t2.start();
t3.setName("窗口3");
t3.start();
}
}
class A extends Thread
{
public int tickets = 1000;
public void run()
{
while(true)
{
synchronized(this) //创建同步代码块
{
if(tickets > 0)
{
System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
tickets--;
}
}
}
}
}
运行结果:(截取部分)
窗口1正在售第:48张票
窗口1正在售第:47张票
窗口1正在售第:46张票
窗口1正在售第:45张票
窗口1正在售第:44张票
窗口1正在售第:43张票
窗口3正在售第:42张票
窗口3正在售第:41张票
窗口3正在售第:40张票
窗口3正在售第:39张票
窗口3正在售第:38张票
窗口3正在售第:37张票
窗口3正在售第:36张票
在上面的程序中,通过Java中的synchronized语法机制,保证了下面的代码块变成了一个不可分割的整体。
synchronized(this) //创建同步代码块
{
if(tickets > 0)
{
System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
tickets--;
}
}
对于上面的讲解,如有问题,欢迎留言指正!