进程与线程
在学习 Java 多线程之前, 先简单复习一下进程与线程的知识
进程: 进程是系统进行资源分配和调度的基本单位, 可以将进程理解为一个正在执行的程序, 比如一款游戏
线程: 线程是程序执行的最小单位, 一个进程可由一个或多个线程组成, 在一款运行的游戏中通常会有界面
更新线程游戏逻辑线程等, 线程切换的开销远小于进程切换的开销
图 1
在图 1 中, 蓝色框表示进程, 黄色框表示线程进程拥有代码数据等资源, 这些资源是共享的, 3 个线程都可
以访问, 同时每个线程又拥有私有的栈空间
Java 线程状态图
线程的五种状态:
1)新建状态(New): 线程对象实例化后就进入了新建状态
2)就绪状态 (Runnable): 线程对象实例化后, 其他线程调用了该对象的 start() 方法, 虚拟机便会启
动该线程, 处于就绪状态的线程随时可能被调度执行
3)运行状态(Running): 线程获得了时间片, 开始执行只能从就绪状态进入运行状态
4)阻塞状态(Blocked): 线程因为某个原因暂停执行, 并让出 CPU 的使用权后便进入了阻塞状态
等待阻塞: 调用运行线程的 wait()方法, 虚拟机会把该线程放入等待池
同步阻塞: 运行线程获取对象的同步锁时, 该锁已被其他线程获得, 虚拟机会把该线程放入锁定池
其他线程: 调用运行线程的 sleep()方法或 join()方法, 或线程发出 I/O 请求时, 进入阻塞状态
5)结束状态(Dead): 线程正常执行完或异常退出时, 进入了结束状态
Java 线程实现
Java 语言提供了两种实现线程的方式:
1)通过继承 Thread 类实现线程
- public class ThreadTest {
- public static void main(String[] args){
- Thread thread = new MyThread(); // 创建线程
- thread.start();// 启动线程
- }
- }
- // 继承 Thread 类
- class MyThread extends Thread{
- @Override
- public void run() {
- int count = 7;
- while(count>0){
- System.out.println(count);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- count--;
- }
- }
- }
2)通过实现 Runnable 接口实现线程
- public class ThreadTest {
- public static void main(String[] args){
- Runnable runnable = new MyThread();
- // 将 Runnable 对象传递给 Thread 构造器
- Thread thread = new Thread(runnable);
- thread.start();
- }
- }
- // 实现了 Runnable 接口
- class MyThread implements Runnable{
- @Override
- public void run() {
- int count = 7;
- while(count>0){
- System.out.println(count);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- count--;
- }
- }
- }
两种方式都覆写了 run()方法, run()方法内定义了线程的执行内容, 我们只能通过线程的 start()方法来
启动线程, 且 start()方法只能调用一次, 当线程进入执行状态时, 虚拟机会回调线程的 run()方法直
接调用线程的 run()方法, 并不会启动线程, 只会像普通方法一样去执行
其实, Thread 类本身也实现了 Runnable 接口这两种方式都可以实现线程, 但 Java 语言只支持单继
承, 如果扩展了 Thread 类就无法再扩展其他类, 远没有实现接口灵活
线程常用方法
1)Thread 类
Thread(): 用于构造一个新的 Thread
Thread(Runnable target): 用于构造一个新的 Thread, 该线程使用了指定 target 的 run 方法
Thread(ThreadGroup group,Runnable target): 用于在指定的线程组中构造一个新的 Thread, 该
线程使用了指定 target 的 run 方法
currentThread(): 获得当前运行线程的对象引用
interrupt(): 将当前线程置为中断状态
sleep(long millis): 使当前运行的线程进入睡眠状态, 睡眠时间至少为指定毫秒数
join(): 等待这个线程结束, 即在一个线程中调用 other.join(), 将等待 other 线程结束后才继续本线程
yield(): 当前执行的线程让出 CPU 的使用权, 从运行状态进入就绪状态, 让其他就绪线程执行
2)Object 类
wait(): 让当前线程进入等待阻塞状态, 直到其他线程调用了此对象的 notify()或 notifyAll()方法后, 当
前线程才被唤醒进入就绪状态
notify(): 唤醒在此对象监控器上等待的单个线程
notifyAll(): 唤醒在此对象监控器上等待的所以线程
注: wait()notify()notifyAll()都依赖于同步锁, 而同步锁是对象持有的, 且每个对象只有一个, 所以
这些方法定义在 Object 类中, 而不是 Thread 类中
3)yield()sleep()wait()比较
wait(): 让线程从运行状态进入等待阻塞状态, 并且会释放它所持有的同步锁
yield(): 让线程从运行状态进入就绪状态, 不会释放它锁持有的同步锁
sleep(): 让线程从运行状态进入阻塞状态, 不会释放它锁持有的同步锁
线程同步
先看一个多线程模拟卖票的例子, 总票数 7 张, 两个线程同时卖票:
- public class ThreadTest{
- public static void main(String[] args){
- Runnable r = new MyThread();
- Thread t1 = new Thread(r);
- Thread t2 = new Thread(r);
- t1.start();
- t2.start();
- }
- }
- class MyThread implements Runnable{
- private int tickets = 7; // 票数
- @Override
- public void run(){
- while(tickets>0){
- System.out.println("tickets:"+tickets);
- tickets--;
- try{
- Thread.sleep(100);
- }catch(InterruptedException e){
- e.printStackTrace();
- }
- }
- }
- }
运行结果:
运行结果不符合我们的预期, 因为两个线程使用共享变量 tickets, 存在着由于交叉操作而破坏数据的可能性,
这种潜在的干扰被称作临界区, 通过同步对临界区的访问可以避免这种干扰
在 Java 语言中, 每个对象都有与之关联的同步锁, 并且可以通过使用 synchronized 方法或语句来获取或释放
这个锁在多线程协作时, 如果涉及到对共享对象的访问, 在访问对象之前, 线程必须获取到该对象的同步
锁, 获取到同步锁后可以阻止其他线程获得这个锁, 直到持有锁的线程释放掉锁为止
- public class ThreadTest{
- public static void main(String[] args){
- Runnable r = new MyThread();
- Thread t1 = new Thread(r);
- Thread t2 = new Thread(r);
- t1.start();
- t2.start();
- }
- }
- class MyThread implements Runnable{
- private int tickets = 7;
- @Override
- public void run(){
- while(tickets>0){
- synchronized(this){ // 获取当前对象的同步锁
- if(tickets>0){
- System.out.println("tickets:"+tickets);
- tickets--;
- try{
- Thread.sleep(100);
- }catch(InterruptedException e){
- e.printStackTrace();
- }
- }
- }
- }
- }
- }
运行结果:
synchronized 用法:
1)synchronized 方法: 如果一个线程要在某个对象上调用 synchronized 方法, 那么它必须先获取这个对象的
锁, 然后执行方法体, 最后释放这个对象上的锁, 而与此同时, 在同一个对象上调用 synchronized 方法的其他
线程将阻塞, 直到这个对象的锁被释放为止
- public synchronized void show(){
- System.out.println("hello world");
- }
2)synchronized 静态方法: 静态方法也可以被声明为 synchronized 的, 每个类都有与之相关联的 Class 对象,
而静态同步方法获取的就是它所属类的 Class 对象上的锁, 两个线程不能同时执行同一个类的静态同步方法, 如
果静态数据是在线程之间共享的, 那么对它的访问就必须利用静态同步方法来进行保护
3)synchronized 语句: 静态语句可以使我们获取任何对象上的锁而不仅仅是当前对象上的锁, 也能够让我们定
义比方法还要小的同步代码区, 这样可以让线程持有锁的时间尽可能短, 从而提高性能
- private final Object lock = new Object();
- public void show(){
- synchronized(lock){
- System.out.println("hello world");
- }
- }
来源: https://www.cnblogs.com/brave7/p/8505354.html