java中的多线程 // 基础
java 中的多线程
简介
进程 : 指正在运行的程序,并具有一定的独立能力,即 当硬盘中的程序进入到内存中运行时,就变成了一个进程
线程 : 是进程中的一个执行单元,负责当前程序的执行。线程就是CPU通向程序的路径
一个进程中只有一个线程,单线程程序
一个进程中是可以有多个线程的,这个应用程序是多线程程序
程序的运行分类
分时调度
所有线程轮流使用CPU 的使用权,平均分配每个线程占用CPU 的时间
抢占式调度
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么就会随机选择一个线程(线程的随机性)。
java 使用的为抢占式调度
抢占式调度简介:
现在的操作系统都支持多进程并发运行,比如:一边用office ,一边使用QQ,一边看着视频 等等,
看着好像这些程序都在同一时刻运行,实际上是 CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。
对于CPU的一个核而言,某个时刻只能执行一个线程,而CPU 在多个线程之间切换的速度相对我们而言感觉要快,看上去就是在同一时刻运行。
注意:
多线程并不能提高程序的运行速度,但能够提高程序的运行效率,让CPU 的使用效率更高。
多线程的由来
jvm启动后,必然有一个执行线程(路径)从main方法开始的,一直执行到main方法结束,这个线程在java中称之为主线程(main线程)。
若主线程遇到循环,并循环次数较多,则导致程序在指定的位置停留时间过长,无法马上执行下面的程序,则需要等待循环结束后才能够执行代码。效率慢。
主线程负责执行其中的一个循环,由另一个线程执行另一个循环,最终实现多部分代码同时执行。多线程之间互不影响。
多线程的创建方式
1、继承 Thread 类
创建一个类,继承 Thread 类,并重写Thread 类的 run 方法
创建对象,调用 run 方法 ,就相当于执行其他线程的 main 方法。
步骤:
1、自定义一个类,继承Thread 类
2、重写Thread 类中的 run 方法 ,设置线程任务
3、创建自定义类的实例
4、实例化 Thread 类,并传入自定义的类
4、调用start ,开启进程。
示例:
1 1、自定义类 2 // 创建一个类,继承Thread 3 public class Thread_01 extends Thread{ 4 // 重写run 方法,在run方法中定义线程任务 5 public void run(){ 6 System.out.println(Thread.currentThread().getName()); 7 } 8 } 9 2、main方法 10 public class ThreadDemo { 11 public static void main(String[] args) { 12 // 创建线程对象 t1 13 Thread_01 t1 = new Thread_01(); 14 // 为了启动Thread_01 这个线程,需要实例化Thread,并传入自己的Thread_01实例 15 Thread thread = new Thread(t1); 16 // 通知CPU 要启动线程 17 thread.start(); 18 System.out.println(Thread.currentThread().getName()); 19 } 20 }
2、实现 Runnable 接口
创建一个类,实现 Runnable 接口,重写 run 方法
步骤:
1、自定义一个类,实现 Runnable 接口
2、重写run 方法,在run方法中设置线程任务
3、在main 方法中,创建自定义类的实例化
4、实例化Thread 类,并传入自定义类的实例化
5、调用start 方法,开启进程
1 1、自定义类,实现runnable 接口 2 // 自定义类,实现Runnable 接口 3 public class Runnable_01 implements Runnable{ 4 // 重写run 方法,在run方法中设置线程任务 5 @Override 6 public void run() { 7 System.out.println(Thread.currentThread().getName()); 8 } 9 10 } 11 12 2、在main方法中,调用 13 public class ThreadDemo { 14 public static void main(String[] args) { 15 // 创建线程对象 t1 16 Runnable_01 t1 = new Runnable_01(); 17 // 为了启动Runnable_01 这个线程,需要实例化Thread,并传入自己的Runnable_01实例 18 Thread thread = new Thread(t1); 19 // 通知CPU 要启动线程 20 thread.start(); 21 System.out.println(Thread.currentThread().getName()); 22 } 23 }
注意:
调用 start 方法,开启新线程。若没有调用start 方法,只调用run 方法,只是调用了一个方法而已,并没有开启新线程
一个线程只能调用一次start 方法,若线程执行结束后,不能再调用。
常用API
-
voidstart() 使该线程开始执行;Java 虚拟机调用该线程的run方法。staticThreadcurrentThread() 返回对当前正在执行的线程对象的引用string getName() 返回该线程的名称long getId() 返回该线程的标识符int getPriority() 返回线程的优先级Thread.State getState() 返回该线程的状态void interrupt() 中断线程void setName(String name) 改变线程名称,使之与参数name相同void setPriority(int newPriority)更改线程的优先级static voidsleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)static voidyield() 暂停当前正在执行的线程对象,并执行其他线程
1 public class Demo { 2 public static void main(String[] args) { 3 // 调用currentThread().getName(),获取当前线程的名称 4 System.out.println(Thread.currentThread().getName() + "123"); 5 // 调用currentThread().getId(),获取当前线程的标识符 6 System.out.println(Thread.currentThread().getId()); 7 // 调用currentThread().getPriority(),获取当前线程的优先级 8 System.out.println(Thread.currentThread().getPriority()); 9 // 调用currentThread().setName() 给当前线程设置新名称 10 Thread.currentThread().setName("线程新名称"); 11 // 调用currentThread().getName(),获取当前线程的名称 12 System.out.println(Thread.currentThread().getName() + "123"); 13 /** 14 * 打印结果 :main123 15 * 1 16 * 5 17 * 线程新名称123 18 */ 19 20 } 21 }
线程安全
若多线程调用全局变量时,会出现线程安全问题。
即:使用java 模拟窗口卖票时,一个窗口就是一个线程,若同时卖票,可能会出现几个窗口同时卖一张票,或者卖出不存在的票(就剩一张票时,两个窗口同时卖出)
所以,使用多线程时,要注意线程安全问题,解决线程安全问题有三种方式,
方式一:同步代码块
同步代码块:就是在方法块声明上加上 synchronized
synchronized (锁对象) {
可能会产生线程安全问题的代码
}
注意:
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
对象可以是this, 哪个对象调用,方法中的this就是哪个对象
示例:
1 /* 2 * 开启3个线程,同时卖100张票 3 */ 4 public class Demo01PayTicket { 5 public static void main(String[] args) { 6 //创建接口的实现类对象 7 RunnableImpl r = new RunnableImpl(); 8 //创建线程对象 9 Thread t0 = new Thread(r); 10 Thread t1 = new Thread(r); 11 Thread t2 = new Thread(r); 12 //开启多线程 13 t0.start(); 14 t1.start(); 15 t2.start(); 16 } 17 } 18 19 /* 20 * 发现程序出现了安全问题:卖出了重复的票和不存在的票 21 * 22 * 多线程安全问题的第一种解决方案:使用同步代码块 23 * 24 * 注意: 25 * 代码块中传递的锁对象必须保证唯一,多个线程使用的是同一个锁对象 26 * 锁对象可以是任意的对象 27 * 28 */ 29 public class RunnableImpl implements Runnable{ 30 31 //定义一个共享的票源 32 private int ticket = 100; 33 //创建一个锁对象 34 Object obj = new Object(); 35 36 @Override 37 public void run() { 38 //让卖票重复执行 39 while(true){ 40 //同步代码块 41 synchronized (obj) { 42 //判断是否还有票 43 if(ticket>0){ 44 45 //提高安全问题出现的概率,增加一个sleep 46 try { 47 Thread.sleep(10); 48 } catch (InterruptedException e) { 49 e.printStackTrace(); 50 } 51 52 //进行卖票 53 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); 54 ticket--; 55 } 56 } 57 } 58 } 59 }
方式二:同步方法
1、同步方法:在方法声明上加上 synchronized
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步方法中的锁对象是 this,哪个对象调用,方法中的this就是哪个对象
2、静态同步方法:在方法声明上加上 static synchronized
public static synchronized void method(){
可能会产生线程安全问题的代码
}
静态同步方法中的锁对象不是对象,是本类的 .class 文件
示例:
1 /* 2 * 开启3个线程,同时卖100张票 3 */ 4 public class Demo01PayTicket { 5 public static void main(String[] args) { 6 //创建接口的实现类对象 7 RunnableImpl r = new RunnableImpl(); 8 //创建线程对象 9 Thread t0 = new Thread(r); 10 Thread t1 = new Thread(r); 11 Thread t2 = new Thread(r); 12 //开启多线程 13 t0.start(); 14 t1.start(); 15 t2.start(); 16 } 17 } 18 19 /* 20 * 发现程序出现了安全问题:卖出了重复的票和不存在的票 21 * 22 * 多线程安全问题的第二种解决方案:使用同步方法 23 * 24 * 实现步骤: 25 * 1.把访问了共享数据的代码提取出来放在一个方法中 26 * 2.在方法上添加一个synchronized修饰符 27 * 28 * 格式: 29 * 修饰符 synchronized 返回值类型 方法名(参数){ 30 * 访问了共享数据的代码; 31 * } 32 * 33 * 把选中的代码提取到方法中快捷键:alt+shift+m 34 * 35 */ 36 public class RunnableImpl implements Runnable{ 37 38 //定义一个共享的票源 39 private static int ticket = 100; 40 41 @Override 42 public void run() { 43 //让卖票重复执行 44 while(true){ 45 payTicketStatic(); 46 } 47 48 } 49 50 /* 51 * 静态的同步方法,锁对象不是this 52 * 静态优先于非静态加载到内存中,this是创建对象之后才有的 53 * 锁对象是本类的class属性(反射-->class文件对象) 54 */ 55 public static synchronized void payTicketStatic() { 56 synchronized (RunnableImpl.class) { 57 //判断是否还有票 58 if(ticket>0){ 59 //提高安全问题出现的概率,增加一个sleep 60 try { 61 Thread.sleep(10); 62 } catch (InterruptedException e) { 63 e.printStackTrace(); 64 } 65 //进行卖票 66 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); 67 ticket--; 68 } 69 } 70 } 71 72 /* 73 * 定义一个卖票的方法 74 * 使用synchronized修饰 75 * 使用锁对象把方法锁住 76 * 这个锁对象是谁? 77 * 创建的实现类对象new RunnableImpl(); 78 * 也就是this,哪个对象调用的方法,方法中的this就是哪个对象 79 */ 80 public synchronized void payTicket() { 81 //System.out.println(this);//cn.itcast.demo08.RunnableImpl@bcda2d 82 synchronized (this) { 83 //判断是否还有票 84 if(ticket>0){ 85 //提高安全问题出现的概率,增加一个sleep 86 try { 87 Thread.sleep(10); 88 } catch (InterruptedException e) { 89 e.printStackTrace(); 90 } 91 92 //进行卖票 93 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); 94 ticket--; 95 } 96 } 97 } 98 }
方式三:使用lock 锁
Lock 是接口, ReentrantLock 是Lock 的实现类
API

调用
1、创建ReentrantLock 对象
2、在可能产生安全问题代码前调用 lock() 方法,获得锁
3、调用unlock()方法,解锁
ReentrantLock rl = new ReentrantLock();
//获得锁
rl.LOCK
可能会产生线程安全问题的代码
Rl.unlock
示例:
1 /* 2 * 开启3个线程,同时卖100张票 3 */ 4 public class Demo01PayTicket { 5 public static void main(String[] args) { 6 //创建接口的实现类对象 7 RunnableImpl r = new RunnableImpl(); 8 //创建线程对象 9 Thread t0 = new Thread(r); 10 Thread t1 = new Thread(r); 11 Thread t2 = new Thread(r); 12 //开启多线程 13 t0.start(); 14 t1.start(); 15 t2.start(); 16 } 17 } 18 19 /* 20 * 发现程序出现了安全问题:卖出了重复的票和不存在的票 21 * 22 * 多线程安全问题的第三种解决方案:使用Lock锁 23 * java.util.concurrent.locks.Lock接口 24 * Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。 25 * JDK1.5之后出现的新特性 26 * 27 * 实现步骤: 28 * 1.在成员位置创建一个ReentrantLock对象 29 * 2.在访问了共享数据的代码前,调用lock方法,获取锁对象 30 * 3.在访问了共享数据的代码后,调用unlock方法,释放锁对象 31 * 32 */ 33 public class RunnableImpl implements Runnable{ 34 35 //定义一个共享的票源 36 private int ticket = 100; 37 //1.在成员位置创建一个ReentrantLock对象 38 Lock l = new ReentrantLock(); 39 40 @Override 41 public void run() { 42 //让卖票重复执行 43 while(true){ 44 //2.在访问了共享数据的代码前,调用lock方法,获取锁对象 45 l.lock(); 46 try { 47 //可能会出现安全问题的代码 48 //判断是否还有票 49 if(ticket>0){ 50 //提高安全问题出现的概率,增加一个sleep 51 Thread.sleep(10); 52 //进行卖票 53 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); 54 ticket--; 55 } 56 57 } catch (Exception e) { 58 //异常的处理逻辑 59 System.out.println(e); 60 } finally { 61 //一定会执行的代码,资源释放 62 //3.在访问了共享数据的代码后,调用unlock方法,释放锁对象 63 l.unlock();//无论是否异常,都会释放掉锁对象 64 } 65 } 66 67 } 68 }
多线程线程图
线程的五种状态:
新建状态-->运行状态-->死亡(结束)状态
阻塞状态
冻结状态(休眠/无限等待)
新建 :刚创建出来的线程,即 new Thread();
阻塞 :没有抢到CPU,在等待CPU调用
运行 :调用run 方法,在运行状态
死亡 :方法结束,调用完成
休眠 :调用sleep() 方法,进入休眠状态
sleep 是Thread 的一个函数
sleep 指占用CPU 不工作,其他线程无法进入。即:sleep不会让出系统资源;
无限等待 : 调用wait() 方法,未被唤醒
wait 是object 的一个函数,需要调用notify() 来唤醒
wait 指不占用CPU 不工作,其他线程可以进入。即:wait是进入线程等待池中等待,让出系统资源。

浙公网安备 33010602011771号