JAVA多线程基础

简介: 一:线程与进程 1 线程:进程中负责程序执行的执行单元 线程本身依靠程序进行运行 线程是程序中的顺序控制流,只能使用分配给程序的资源和环境 2 进程:执行中的程序 一个进程至少包含一个线程 3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程 4 多线程:在一个程序中运行多个任...

一:线程与进程

1 线程:进程中负责程序执行的执行单元
线程本身依靠程序进行运行
线程是程序中的顺序控制流,只能使用分配给程序的资源和环境

2 进程:执行中的程序
一个进程至少包含一个线程

3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程

4 多线程:在一个程序中运行多个任务
目的是更好地使用CPU资源

 

二:线程的实现

1:继承Thread类

  

 1 package Threads;
 2 
 3 public class Thread_one extends Thread{
 4     
 5     @Override
 6     public void run() {
 7         // TODO Auto-generated method stub
 8         System.out.println(Thread.currentThread().getName());
 9     }
10 }

我们继承Thread类,然后重写run()方法,写入你要执行的操作;

线程了类是创建好了,下面就是使用这个线程类:

1 package Threads;
2 
3 public class Test1 {
4     public static void main(String[] args) {
5         for (int i = 0; i < 10; i++) {
6             new Thread_one().start();
7         }
8     }
9 }

正确的启动线程的方法是start()方法,而不是使用run()方法,下面具体说明 ;

运行的结果:

  

我们可以发现多次运行结果是不一样的:

下面我们把启动改为run()试一试;

1 package Threads;
2 
3 public class Test1 {
4     public static void main(String[] args) {
5         for (int i = 0; i < 10; i++) {
6             new Thread_one().run();
7         }
8     }
9 }

 

 多次运行发现结果都是一样的,发现运行的都是main这个主线程,而没有创建新的线程:

  总结 run()和start()的区别:

  ①:首次正确启动一个线程是使用start()方法;

  ②:run()方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。

  ③:start()在调用start()方法后,线程就进入了就绪的状态,其实也就是一个可运行的状态,什么时候运行是根据cpu的调度的,看这个线程是否抢到了cpu的资源,所以说我们执行多个线程多次执行的顺序结果是不一样的;

 

 2:实现Runnable

 1 package Threads;
 2 
 3 public class Thread_two implements Runnable{
 4 
 5     @Override
 6     public void run() {
 7         // TODO Auto-generated method stub
 8         System.out.println(Thread.currentThread().getName());
 9     }
10 
11 }

继承Thread和实现Runnable实现一个线程过程都差不多;

调用启动有些区别:

 1 package Threads;
 2 
 3 public class Test2 {
 4 
 5     public static void main(String[] args) {
 6         // TODO Auto-generated method stub
 7         for (int i = 0; i < 10; i++) {
 8             new Thread(new Thread_two()).start();
 9         }
10     }
11 }

我们查看Thread源码发现,他需要一个Runnable类型的对象;

 

我们附给他我们实现Runnable接口的对象

运行发现实现了效果:

 

总结:

实现Runnable接口比继承Thread类所具有的优势:

1):可以避免java中的单继承的限制

 四:线程的不同状态及转换:

 

 

 

 

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()方法,该线程结束生命周期。

解析:

新建状态:

 

只是创建了这个线程对象。

 

 就绪状态:

该状态的线程位于可运行线程池中,变得可运行,等争抢到cpu资源后就才会真正的进入到可运行的状态;

 

 运行状态:就绪状态的线程获取了CPU,执行程序代码。

 阻塞状态:

 ①:wait();

 

只能用 notify唤醒 或者notifyall

②:sleep

定义毫秒数,到时间自动唤醒,继续执行任务;

 

 ③:yield

 线程的礼让,让正在执行的线程变成可运行状态,然后在重新争抢cpu。

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

 ④:join加入主线程;

让正在运行的主线程停止,然后先运行这个线程。

 

 1 package Threads;
 2 
 3 public class Test3 {
 4     public static void main(String[] args) {
 5             System.out.println("开始:");
 6             Thread3 t2=new Thread3();
 7             Thread t=new Thread(t2);
 8             t.start();
 9             System.out.println("结束:");
10     }
11 }

 

 

 

这明显不是我们想要的结果:

 使用join加入主线程:

 1 package Threads;
 2 
 3 public class Test3 {
 4     public static void main(String[] args) throws InterruptedException {
 5             System.out.println("开始:");
 6             Thread3 t2=new Thread3();
 7             Thread t=new Thread(t2);
 8             t.start();
 9             t.join();
10             System.out.println("结束:");
11     }
12 }

 在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

 

总结:

sleep和wait的区别:

1. 所属的层面不同:Thread类的方法:sleep() Object的方法:wait()。

2.wait停止的线程则需要使用notifyall()和notifi()唤醒;
3. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。 sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

4. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 ;

5. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

 

五:线程常用的方法:

  sleep(): 强迫一个线程睡眠N毫秒。 
  isAlive(): 判断一个线程是否存活。 
  join(): 等待线程终止。 
  activeCount(): 程序中活跃的线程数。 
  enumerate(): 枚举程序中的线程。 
  currentThread(): 得到当前线程。 
  isDaemon(): 一个线程是否为守护线程。 
  setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) 
  setName(): 为线程设置一个名称。 
  wait(): 强迫一个线程等待。 
  notify(): 通知一个线程继续运行。 
  setPriority(): 设置一个线程的优先级。

 

六:线程的同步:

常用的同步方法就是使用synchronized关键字

为什么要使用线程同步,因为当一个资源可能会有多个线程访问的时候,当第一个线程进来之后对资源进行了修改,但是在修改的过程中第二个线程也进来了,那第二个线程取到的数据就不是最新的数据,是个虚假的数据,那么这个线程所执行的操作都没有意义了;

看个小例子:

 1 package Threads;
 2 
 3 public class Thread3 implements Runnable{
 4     private static int a=10;
 5     @Override
 6     public void run() {
 7         // TODO Auto-generated method stub
 8         try {
 9             Tong();
10         } catch (InterruptedException e) {
11             // TODO Auto-generated catch block
12             e.printStackTrace();
13         }
14     }
15     public static  void Tong() throws InterruptedException{
16         for (int i = 0; i < 10; i++) {
17             System.out.println(Thread.currentThread().getName()+"||||"+(--a));
18             Thread.sleep(1000);
19     }
20     }
21 }
22 
23 
24 
25 
26 package Threads;
27 
28 public class Test3 {
29     public static void main(String[] args) throws InterruptedException {
30             System.out.println("开始:");
31             Thread3 t2=new Thread3();
32             Thread t=new Thread(t2);
33             Thread t3=new Thread(t2);
34             t.start();
35             t3.start();
36             t.join();
37             t3.join();
38             System.out.println("结束:");
39     }
40 }

 

里面的sleep就是模拟让两个线程同时访问一个资源

运行结果为:

我们可以看到是有重复数据的,也就是虚假的数据   thread-0和thread-1同时访问了count:7这个数据;

解决办法就是使用同步方法:

 1 package Threads;
 2 
 3 public class Thread3 implements Runnable{
 4     private static int a=10;
 5     @Override
 6     public void run() {
 7         // TODO Auto-generated method stub
 8         try {
 9             Tong();
10         } catch (InterruptedException e) {
11             // TODO Auto-generated catch block
12             e.printStackTrace();
13         }
14     }
15     public static synchronized void Tong() throws InterruptedException{
16         for (int i = 0; i < 10; i++) {
17             System.out.println(Thread.currentThread().getName()+"||||"+(--a));
18             Thread.sleep(1000);
19     }
20     }
21 }

 

加上synchronized 关键字

总结:其实同步就是对这个资源对象就行加锁,只能同时有一个线程能操作这个资源,如果有第二个线程要访问,那只有等第一个线程执行结束之后才能进行访问;

 

 七:实现经典多线程例子生产者,消费者:

首先创建商品类

 1 package Threads;
 2 
 3 public class Piao {
 4     private int count=5;
 5     
 6     public synchronized void sheng() throws InterruptedException{
 7         if(count>=10){
 8             System.out.println("商品过多:"+count);
 9             wait();
10         }else{
11             System.out.println("商品生产成功:"+(++count));
12             notify();
13         }
14     }
15     public synchronized void mai() throws InterruptedException{
16         if(count<=0){
17             System.out.println("商品库存不够:"+count);
18             wait();
19         }else{
20             System.out.println("商品卖出成功:"+(--count));
21             notify();
22         }
23     }
24 }

 

 在创建生产者和消费者:

 1 package Threads;
 2 
 3 public class Sheng implements Runnable{
 4     private Piao p;
 5     
 6     public Sheng(Piao p) {
 7         super();
 8         this.p = p;
 9     }
10     @Override
11     public void run() {
12         // TODO Auto-generated method stub
13         for (int i = 0; i < 10; i++) {
14             try {
15                 p.sheng();
16             } catch (InterruptedException e) {
17                 // TODO Auto-generated catch block
18                 e.printStackTrace();
19             }
20         }
21     }
22 }
23 
24 
25 
26 
27 package Threads;
28 
29 public class Mai implements Runnable{
30     private Piao p;
31     
32     public Mai(Piao p) {
33         super();
34         this.p = p;
35     }
36     @Override
37     public void run() {
38         // TODO Auto-generated method stub
39         for (int i = 0; i < 10; i++) {
40             try {
41                 p.mai();
42             } catch (InterruptedException e) {
43                 // TODO Auto-generated catch block
44                 e.printStackTrace();
45             }
46         }
47     }
48 }

 

 测试类:

 1 package Threads;
 2 
 3 public class Test4 {
 4 
 5     public static void main(String[] args) {
 6         // TODO Auto-generated method stub
 7         Piao p=new Piao();
 8         new Thread(new Mai(p)).start();
 9         new Thread(new Sheng(p)).start();
10     }
11 
12 }

 

 运行结果为:

欢迎大家一起说出自己的想法。
目录
相关文章
|
10天前
|
算法 Java 开发者
Java中的多线程编程:概念、实现与性能优化
【4月更文挑战第9天】在Java编程中,多线程是一种强大的工具,它允许开发者创建并发执行的程序,提高系统的响应性和吞吐量。本文将深入探讨Java多线程的核心概念,包括线程的生命周期、线程同步机制以及线程池的使用。接着,我们将展示如何通过继承Thread类和实现Runnable接口来创建线程,并讨论各自的优缺点。此外,文章还将介绍高级主题,如死锁的预防、避免和检测,以及如何使用并发集合和原子变量来提高多线程程序的性能和安全性。最后,我们将提供一些实用的性能优化技巧,帮助开发者编写出更高效、更稳定的多线程应用程序。
|
8天前
|
安全 算法 Java
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第11天】 在Java中,高效的并发编程是提升应用性能和响应能力的关键。本文将探讨Java并发的核心概念,包括线程安全、锁机制、线程池以及并发集合等,同时提供实用的编程技巧和最佳实践,帮助开发者在保证线程安全的前提下,优化程序性能。我们将通过分析常见的并发问题,如竞态条件、死锁,以及如何利用现代Java并发工具来避免这些问题,从而构建更加健壮和高效的多线程应用程序。
|
1天前
|
安全 Java
java多线程(一)(火车售票)
java多线程(一)(火车售票)
|
2天前
|
安全 Java 调度
Java并发编程:深入理解线程与锁
【4月更文挑战第18天】本文探讨了Java中的线程和锁机制,包括线程的创建(通过Thread类、Runnable接口或Callable/Future)及其生命周期。Java提供多种锁机制,如`synchronized`关键字、ReentrantLock和ReadWriteLock,以确保并发访问共享资源的安全。此外,文章还介绍了高级并发工具,如Semaphore(控制并发线程数)、CountDownLatch(线程间等待)和CyclicBarrier(同步多个线程)。掌握这些知识对于编写高效、正确的并发程序至关重要。
|
2天前
|
安全 Java 程序员
Java中的多线程并发编程实践
【4月更文挑战第18天】在现代软件开发中,为了提高程序性能和响应速度,经常需要利用多线程技术来实现并发执行。本文将深入探讨Java语言中的多线程机制,包括线程的创建、启动、同步以及线程池的使用等关键技术点。我们将通过具体代码实例,分析多线程编程的优势与挑战,并提出一系列优化策略来确保多线程环境下的程序稳定性和性能。
|
2天前
|
缓存 分布式计算 监控
Java并发编程:深入理解线程池
【4月更文挑战第17天】在Java并发编程中,线程池是一种非常重要的技术,它可以有效地管理和控制线程的执行,提高系统的性能和稳定性。本文将深入探讨Java线程池的工作原理,使用方法以及在实际开发中的应用场景,帮助读者更好地理解和使用Java线程池。
|
3天前
|
存储 安全 Java
Java中的容器,线程安全和线程不安全
Java中的容器,线程安全和线程不安全
10 1
|
3天前
|
Java 开发者
Java中多线程并发控制的实现与优化
【4月更文挑战第17天】 在现代软件开发中,多线程编程已成为提升应用性能和响应能力的关键手段。特别是在Java语言中,由于其平台无关性和强大的运行时环境,多线程技术的应用尤为广泛。本文将深入探讨Java多线程的并发控制机制,包括基本的同步方法、死锁问题以及高级并发工具如java.util.concurrent包的使用。通过分析多线程环境下的竞态条件、资源争夺和线程协调问题,我们提出了一系列实现和优化策略,旨在帮助开发者构建更加健壮、高效的多线程应用。
3 0
|
3天前
|
缓存 监控 Java
Java并发编程:线程池与任务调度
【4月更文挑战第16天】Java并发编程中,线程池和任务调度是核心概念,能提升系统性能和响应速度。线程池通过重用线程减少创建销毁开销,如`ThreadPoolExecutor`和`ScheduledThreadPoolExecutor`。任务调度允许立即或延迟执行任务,具有灵活性。最佳实践包括合理配置线程池大小、避免过度使用线程、及时关闭线程池和处理异常。掌握这些能有效管理并发任务,避免性能瓶颈。
|
4天前
|
设计模式 运维 安全
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第15天】在Java开发中,多线程编程是提升应用程序性能和响应能力的关键手段。然而,它伴随着诸多挑战,尤其是在保证线程安全的同时如何避免性能瓶颈。本文将探讨Java并发编程的核心概念,包括同步机制、锁优化、线程池使用以及并发集合等,旨在为开发者提供实用的线程安全策略和性能优化技巧。通过实例分析和最佳实践的分享,我们的目标是帮助读者构建既高效又可靠的多线程应用。