首页 > 代码库 > 多线程编程学习笔记
多线程编程学习笔记
多线程编程
目录
线程概述
线程的创建
创建线程程序
线程同步
守护线程
线程之间的相互通讯
线程池和java.util.concurrent包
一、概述
1.相关概念
进程(Process):程序(任务)执行的过程,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程,共享内存,共享文件。比如在Windows系统中,一个运行的exe就是一个进程。
线程(Thread):进程中的一个执行流程,一个进程中可以运行多个线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。现在的操作系统是多任务操作系统,多线程是实现多任务的一种方式,在线程之间实际上轮换执行。
2.线程编程相关API
Java多线程相关的核心类和接口主要包括Thread类、Runnable类、Object类等,都存放在java.lang包中,java.lang包提供了利用Java编程语言进行程序设计的基础类。
(1)Thread类。线程管理的主要方法定义在Thread类中,包括:
start()方法:
MyThread2 mythread2 = new MyThread2();
mythread2.start();
在单独的路径中启动线程,使该线程开始执行,然后调用该Thread对象上的 run 方法。
run()方法:
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;如果我们编写一个类继承Thread,那么就是子类中被重写的run()方法被调用
setName()方法:
MyThread myThread1 = new MyThread();
myThread1.setName("t1");
改变线程名称,使之与参数 name 相同
setPriority()方法:
设置线程对象的优先级,取值范围为1-10,最好使用Thread.NORMPRIORITY,Thread.MINPRIORITY,Thread.MAX_PRIORITY,这三个值(取值分别为1,5,10)
setDaemon()方法:
mythread1.setDaemon(true);//守护线程方法必须在start()启动前前调用
mythread1.start();
将该线程标记为守护线程或用户线程,如果参数为true,则表示该线程是一个守护线程。当正在运行的线程都是守护线程时,Java 虚拟机退出
join()方法:
当前线程在第二个线程上调用本方法,将导致当前线程阻塞,直到第二个线程停止或运行了指定的微秒数。如果参数为0,那么第一个线程将无限期等待。
isAlive()方法:
测试线程是否处于活动状态,如果处于活动状态,则返回true.
上述方法都是在特定的Thread对象上调用的。以下方法都是静态的,调用静态方法之一,将会在当前运行的线程上执行操作:
yield()方法:
导致当前正在运行的线程休眠至少指定的毫秒数时间
sleep()方法:
导致当前正在运行的线程休眠至少指定的毫秒数时间
currentThread()方法:
Thread.currentThread();
返回对当前正在执行的线程对象的引用
(2)Runnable类。Runnable接口中只定义了一个run()方法。
(3)Object类。该类包含了如下与多线程相关的方法:
wait()方法:
导致当前线程在该对向上无限期等待,直到其他线程调用相同对象的notify()或notifyAll()方法通知它恢复执行为止
notify()方法:
唤醒正在该对象上等待的一个线程,当前线程必须拥有对象的锁以调用该方法。
notifyAll()方法:
类似于notify()方法,但是是唤醒所有等待的线程。
二、线程的创建
1.线程的创建
(1)通过继承Thread类来创建线程步骤:
1)创建一个继承Thread的类 2)在该类中重写run()方法,在方法中写入想要线程运行的代码 3)创建该类的示例 4)调用该实例的start()方法,开始运行线程
示例:
//创建一个继承Thread的类
public class MyThread1 extends Thread{
//重写run()方法,在方法中写入想要线程运行的代码
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("你在哪儿呢?"+i);
}
}
}
public class Test {
public static void main(String[] args) {
//创建该类的示例
MyThread mythread = new MyThread();
//调用该实例的start()方法,开始运行线程
mythread1.start();
}
}
(2)通过实现Runnable借口来创建线程步骤:
1)创建一个类来实现Runnable接口,用于代表需要线程完成的任务 2)在Runnable指定的run()方法内放入需要线程执行的代码 3)创建一个Runnable类的示例 4)创建一个Thread对象,将Runnable的实例作为构造器参数传入进去 5)通过调用Thread类的实例的start()方法执行线程
示例:
//创建一个类来实现Runnable接口
public class MyTask implements Runnable{
@Override
//在Runnable指定的run()方法内放入需要线程执行的代码
public void run() {
// TODO Auto-generated method stub
System.out.println("线程启动了");
}
}
public class Test {
public static void main(String[] args) {
//创建一个Runnable类的示例
MyTask myTask = new MyTask();
//创建一个Thread对象,将Runnable的实例作为构造器参数传入进去
Thread t = new Thread(myTask);
//通过调用Thread类的实例的start()方法执行线程
t.start();
System.out.println("主程序运行结束");
}
}
总结:Thread类实现了Runnable接口,即Thread类是Runnable接口的一个子类;使用Runnable类可以避免Java单继承的局限性;使用Runnable接口可以将虚拟CPU(Thread类)与县城要完成的任务有效分离,较好的体现了面向对象设计的基本原则。
2.线程中的五种状态
(1)New——新建状态
当程序使用 new 关键字创建了一个线程后,该线程就处于新建状态(初始状态),此时线程还未启动,当线程对象调用 start()方法时,线程启劢,迚入 Runnable状态。
(2)Runnable——可运行(就绪)状态
调用start()方法后,线程从 New 状态迚入 Runnable 状态(就绪状态),start()方法是在 main()方法(Running 状态)中调用的当线程处于 Runnable 状态时,表示线程准备就绪,等待获取 CPU
(3)Running——运行(正在运行)状态
假如该线程获取了 CPU,则迚入 Running 状态,开始执行线程体,即 run()方法中的内
注意:
如果系统叧有 1 个 CPU,那么在仸意时间点则叧有 1 条线程处于 Running 状态;如果是双核系统,那么同一时间点会有 2 条线程处于 Running 状态。但是,当线程数大于处理器数时,依然会是多条线程在同一个 CPU 上轮换执行。
当一条线程开始运行时,如果它不是一瞬间完成,那么它不可能一直处于 Running 状态,线程在执行过程中会被中断,目的是让其它线程获得执行的机会,像这样线程调度的策略取决于底层平台。对于抢占式策略的平台而言,系统系统会给每个可执行的线程一小段时间来处理仸务,当该时间段(时间片)用完系统会剥夺该线程所占资源(CPU),让其他线程获得运行机会。
(4)Block——阻塞(挂起)状态
在如下情冴下,线程会迚入阻塞状态:
1)线程调用了 sleep()方法主劢放弃所占 CPU 资源 2)线程调用了一个阻塞式 IO 方法(比如控制台输入方法),在该方法返回前,该线程被阻塞当正在执行的线程被阻塞时,其它线程就获得执行机会了。需要注意的是,当阻塞结束时,该线程将迚入 Runnable 状态,而非直接迚入 Running 状态
(5)Dead——死亡状态
当线程的 run()方法执行结束,线程迚入 Dead 状态,线程结束后,被对象垃圾回收
需要注意的是,不要试图对一个已经死亡的线程调用 start()方法,线程死亡后将不能再次作为线程执行,系统会抛出 IllegalThreadStateException 异常
三、创建多线程程序
示例1:使用继承实现多线程
经典的卖票:
public class MyThread extends Thread {
private int ticket=10;
public void run(){
for(int i=0;i<20;i++){
if(this.ticket>0){
System.out.println(getName()+"出售车票"+this.ticket--);
}
}
}
}
MyThread myThread1=new MyThread();
myThread1.setName("窗口1");
MyThread myThread2=new MyThread();
myThread2.setName("窗口2");
MyThread myThread3=new MyThread();
myThread3.setName("窗口3");
myThread1.start();
myThread2.start();
myThread3.start();
通过运行可以发现:每个窗口都出售了10张票,这很恐怖
示例2:实现多线程第二种方式,实现接口:
在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下好处:
1)避免单继承的局限,一个类可以继承多个接口。 2)适合于资源的共享
public class MyThread implements Runnable {
private int ticket=10;
public void run(){
for(int i=0;i<20;i++){
if(this.ticket>0){
System.out.println(Thread.currentThread().getName()+"出售车票"+this.ticket--);
}
}
}
}
MyThread myThread=new MyThread();
new Thread(myThread,"窗口1").start();
new Thread(myThread,"窗口2").start();
new Thread(myThread,"窗口3").start();
通过运行结果发现在这里实现了了数据共享
1.线程的优先级(资源紧张时候,尽可能优先,取值分别为10,1,5,)
Thread.MAX_PRIORITY 设置为最高优先级
Thread.MIN_PRIORITY 设置为最低级
Thread.NORM_PRIORITY设置为默认级别
默认有10优先级,优先级高的线程获得执行(迚入Running状态)的机会多,机会的多少不能通过代码干预
@Override
public void run() {
// TODO Auto-generated method stub
Thread.currentThread().setPriority(1);
for (int i = 0; i < 100; i++) {
System.out.println("你是谁"+i);
}
}
2.使用join()方法
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行。
示例:1
Thread1 t1=new Thread1();
Thread2 t2=new Thread2();
t1.setName("主线程");
t1.start();
t1.join();
t2.start();
在这个实验中,我们发现这次线程运行是先把t1运行完毕之后再运行的t2从某种意义上来说,可以使线程同步起来
示例2:Thread2 t2=new Thread2(); 这个实验我们也发现了同样的事情 try {
t2.start();
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
3.使用Thread.yield()让出CPU
当前线程让出处理器(离开 Running 状态),使当前线程迚入 Runnable 状态等待如果线程在运行过程中,自己调用了yield()方法,则主动由Running状态迚入Runnable状态
四.同步线程
1.概念:
异步:并发,各干自己的。如:一群人同时上卡车
同步:步调一致的处理。如:一群人排队上公交车
多个线程并发读写同一个临界资源时候会发生”线程并发安全问题“,如果保证多线程同步访问临界资源,就可以解决
常见的临界资源:
1)多线程共享实例变量 2)静态公共变量
1.同步块:使用同步代码块解决线程并发安全问题
synchronized(同步监视器){
}
同步监视器——是一个任意对象实例,是一个多个线程之间的互斥的锁机制,多个线程要使用同一个"监视器"对象,实现同步互斥
示例:
int count = 20;
@Override
public void run() {
for (int i = 0; i < 50; i++) {
synchronized (this) {
if (count > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "号窗口卖出" + count-- + "号票");
}
}
}
}
TicketSouce t=new TicketSouce();
new Thread(t,"t1").start();
new Thread(t,"t2").start();
new Thread(t,"t3").start();
2.同步方法——在使用临界资源的位置加锁,如果方法的全部过程需要同步,可以简单使用synchronized修饰方法,相当于整个方法
int count = 20;
@Override
public void run() {
for (int i = 0; i < 50; i++) {
sale();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public synchronized void sale() {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "号窗口卖出" + count-- + "号票");
}
}
如果去掉修饰符--这里多测试几次会发现有相同的票出现
五、守护线程(精灵线程、后台线程)——是在后台运行的线程
任何一个守护线程都是整个JVM中所有非守护线程的保姆,只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
演示:
public class MyThread1 extends Thread{
public void run(){
Thread.currentThread().setPriority(10);
for (int i = 0; i < 20; i++) {
System.out.println("你在哪儿呢?"+i);
}
}
}
public class MyThread2 extends Thread{
public void run(){
Thread.currentThread().setPriority(1);
for (int i = 0; i < 20; i++) {
System.out.println("你猜猜看?"+i);
}
}
}
public class Test {
public static void main(String[] args) {
MyThread2 mythread2 = new MyThread2();
mythread2.start();
MyThread1 mythread1 = new MyThread1();
mythread1.start();
mythread1.setDaemon(true);//守护线程方法必须在start()前
}
}
Java迚程的结束:当前所有前台线程都结束时,Java 迚程结束,当前台线程结束时,不管后台线程是否结束,都要被停掉!
多线程编程学习笔记