深入理解Java中的线程
2016-09-13 14:04
169 查看
核心内容:
1、Java中线程的概念以及注意事项
2、Java中创建线程的两种方式
3、Java中创建线程两种方式的比较
4、线程同步的问题、线程同步问题的由来、如何解决线程同步的问题
线程的概念:
①线程是一个程序里的不同执行路径;
②以前所编写的程序,每个程序都只有一个入口、一个出口以及一个顺序执行的序列,在程序执行过程中的任何指定时刻,都只有一个单独的执行点;
③事实上,在单个程序的内部是可以在同一时刻进行多种运算的,这就是所谓的多线程。
线程的注意事项:
①aa.start()具有两层含义:向下开辟(创建)一个新的线程,并执行本线程所对应的run方法;
②执行一个线程实际上就是执行本线程所对应的run方法中的代码;
③执行完aa.start()之后并不表示aa对象所对应的线程立即得到执行,aa.start()执行完之后只是表明aa对象所对应的线程具有了可以被cpu执行的资格,但由于想抢占cpu执行的线程很多,cpu并不一定会立即去执行aa对象所对应的线程,此时aa对象对应的线程将处于阻塞状态。一个线程对应三种不同的状态:阻塞状态、就绪状态、运行状态
④一个Thread对象能且只能代表一个线程
Java中创建线程有两种方式:
①继承Thread类,并重写run方法
②实现Runnable接口,并实现run方法
实例程序1:继承Thread类,并重写run方法的方式创建线程
运行结果:hadoop与spark交替的输出
实例程序2:实现Runnable接口,并实现其中的run方法的方式创建线程
运行结果:hadoop与spark交替的输出
从上面创建线程的两种方式可以看出,无论通过哪种方式创建线程,必须调用Thread类中的start方法才能开辟一个新的线程。
为什么Java要提供两种方法来创建线程呢?它们都有哪些区别?相比而言,哪一种方法更好呢?在给出具体结论之前,我们先用Java中的线程编程实现生活中的几个具体场景:
场景1:假设一个影院有三个售票口,分别用于向儿童、成人和老人售票。影院为每个窗口放有100张电影票,分别是儿童票、成人票和老人票。三个售票口各自售票,互不影响。
①、通过继承Thread类的方式来完成这个程序
②、通过实现Runnable接口的方式来完成这个程序
运行结果(部分截取):
在上面的这个实例场景中, 不同的售票口相当于不同的线程,不同种类的票相当于不同的资源。
可以总结出:若多个线程处理的是不同的资源,两种创建线程的方式均可。
场景2:有3个售票口,共同售出电影院的1000张票,3个售票口相当于3个线程,处理的是相同的资源(1000张电影票)
①、通过继承Thread类的方式来完成这个程序
运行结果:(部分截图)
②、通过实现Runnable接口的方式来完成这个程序
运行结果:(部分截图)
可以总结出:若多个线程处理的是相同的资源,最好通过实现Runnable接口的方式创建线程,不然的话通过继承Thread的方式显得过于繁琐。
什么是线程同步?
所谓线程同步就是多个线程在处理相同资源的时候,保证共享数据的数据一致性和变化一致性
线程同步问题的由来?
导致线程同步的原因共有两个:
①多个线程彼此之间处理的是相同的资源(比如3个窗口共同售出1000张票)
②多个线程彼此之间在处理相同关键步骤的时候,在这些关键的步骤没有执行完毕的时候,CPU会切换到另外一个线程去执行这些关键的步骤,导致共享数据的一致性出现问题
实例程序:(线程同步出错)
运行结果:(部分截取)
从上面的程序可以看出,3个线程在处理相同资源的时候(1000张票)的时候,共享数据的一致性出现了问题,其原因在于:
这个代码块并不是一个不可分割的整体。
在Java当中,如何解决线程同步的问题?
在Java当中是通过synchronized语法机制来解决线程同步的问题的,通过synchronized语法机制,保证这些关键的步骤在被某一个线程访问或者执行的时候,其余线程不能在执行这些关键的步骤(尽管CPU仍在多个线程之间来回切换),直到这个线程将这些关键的步骤执行完毕,其余线程才能执行这些关键的步骤;
Java中的synchronized语法机制类似于数据库中的事务性处理机制。
实例程序:通过synchronized语法机制来解决上面的线程同步问题
运行结果:(截取部分)
在上面的程序中,通过Java中的synchronized语法机制,保证了下面的代码块变成了一个不可分割的整体。
对于上面的讲解,如有问题,欢迎留言指正!
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中创建线程的两种方式 |
①继承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中创建线程两种方式的比较 |
场景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--; } }
对于上面的讲解,如有问题,欢迎留言指正!
相关文章推荐
- JAVA线程的深入理解
- Java线程中断的本质深入理解(转)
- 深入理解java线程
- 深入理解Java并发机制(5)--线程、中断、Runnable、Callable、Future
- Java—线程与进程的深入理解
- Java 线程深入理解(草稿)
- 深入理解Java虚拟机JVM高级特性与最佳实践阅读总结—— 第十三章 线程安全与锁优化
- 深入理解JVM-Java线程-实现方式,线程调度,状态
- Java线程的生命周期的深入理解
- 深入理解 Java 虚拟机--Java 内存模型与线程
- 深入理解Java并发机制(5)--线程、中断、Runnable、Callable、Future
- java中的线程(二)——线程的同步和synchronized深入理解
- 深入学习理解java:高效的解决死锁问题的线程通讯方式:Semaphore 和 BlockingQueue
- 【线程】-Java线程中断的本质深入理解
- Java线程中断的本质深入理解
- 深入理解JAVA多线程之线程间的通信方式
- Java线程中断的本质深入理解
- 深入理解Java之线程池
- 深入理解Java并发机制(5)--线程、中断、Runnable、Callable、Future
- 探索深入理解java虚拟机之线程安全与锁优化(8)