Java多线程基础

本文深入探讨Java多线程的基础知识,涵盖线程创建、互斥、通信、中断及内存模型,通过示例代码讲解线程同步、锁机制、wait/notify通信及JMM原理,帮助读者理解多线程编程的关键概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。“同时”执行是人的感觉,在线程之间实际上轮换执行。

一、创建线程

  1、继承Thread

  public class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            System.out.println("我是线程--->"+Thread.currentThread().getName());
        }
    }

   @Test
    public void   test(){
        MyThread  myThread=new MyThread();
        MyThread  myThread1=new MyThread();
        myThread.start();
        myThread1.start();
    }  

       执行结果:

       我是线程--->Thread-1
       我是线程--->Thread-0

2、实现runnable接口

  public class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("我是线程--->" + Thread.currentThread().getName());
        }
    }


    @Test
    public void test1() {
        MyRunnable myRunnable = new MyRunnable();
        Thread  thread=new Thread(myRunnable);
        Thread  thread1=new Thread(myRunnable);
        thread.start();
        thread1.start();
    }

        执行结果:

       我是线程--->Thread-1
       我是线程--->Thread-0

3、Callable创建线程

       Callable 、Future创建带返回值的线程


	public static void main(String[] args) {
		ExecutorService executorService = Executors.newSingleThreadExecutor();
		Future<String> future = executorService.submit(new Callable<String>() {
			@Override
			public String call() throws Exception {
				return "通过Callable方式创建线程的返回结果";
			}
		});

		try {
			String res = future.get();
			System.out.println(res);
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}

	}

二、多线程互斥

1、互斥

       同一个资源类被多线程操作,临界资源在某一时刻被多个线程访问,会造成数据脏读,因此 多线程互斥技术势在必行,以经典的银行取款为例。

public class Account {
	
	private SimpleDateFormat  sdf=new SimpleDateFormat("YYYY-MM-DD HH:mm:ss");
	
	///账户余额
	private  int  account=1000;
	
	public  void  take(int  m) {
		account-=m;
		System.out.println(getTime()+"********取款:"+m+"元");
	}
	
	public  void   save(int  m) {
		account+=m;
		System.out.println(getTime()+"--------存款:"+m+"元");
	}
	
	public  void getAccount() {
		System.out.println(getTime()+"########余额:"+account+"元");
	}

	
	String   getTime() {
		return  sdf.format(new Date());
	}
}

         模拟多线程:

public static void main(String[] args) {

		account = new Account();
			
			for (int i = 0; i < 3; i++) {
				new Thread(new Runnable() {
					@Override
					public void run() {
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						account.take(100);
					}
				}).start();

				new Thread(new Runnable() {
					@Override
					public void run() {
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						account.save(100);
						account.getAccount();
					}
				}).start();
			
		}
	}

        看看执行结果,余额度尽然变成900了

2019-01-25 17:13:08********取款:100元
2019-01-25 17:13:08********取款:100元
2019-01-25 17:13:08--------存款:100元
2019-01-25 17:13:08########余额:900元
2019-01-25 17:13:08--------存款:100元
2019-01-25 17:13:08########余额:900元
2019-01-25 17:13:08********取款:100元
2019-01-25 17:13:08--------存款:100元
2019-01-25 17:13:08########余额:900元

         为了解决多线程下共享数据脏读问题,java提供了线程锁。

2、线程锁

    (1)锁的分类:  自旋锁、阻塞锁、可重入锁、读写锁、互斥锁、悲观锁、乐观锁、公平锁、偏向锁   对象锁、线程锁、锁粗化、锁消除、轻量级锁、重量级锁、独享锁、分段锁。

    (2)java常见的锁有:Synchronized  、ReentrantLock 、  ReentrantReadWriteLock。

  Synchronized是一个非公平,悲观,独享,互斥,可重入的重量级锁。ReentrantLock 是一个默认非公平但可实现公平的,悲观,独享,互斥,可重入轻量级锁。ReentrantReadWriteLocK是一个,默认非公平但可实现公平的,悲观,写独享,读共享,读写,可重入的轻量级锁。

3、synchronized

       使用synchronized来体验一下互斥效果

private static Account account;

	public static void main(String[] args) {

		account = new Account();

		for (int i = 0; i < 3; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					synchronized (account) {
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						account.take(100);
						account.getAccount();
					}
				}
			}).start();

			new Thread(new Runnable() {
				@Override
				public void run() {
					synchronized (account) {
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						account.save(100);
						account.getAccount();
					}
				}
			}).start();
		}

	}

          打印结果

 2019-01-25 17:30:43********取款:100元
 2019-01-25 17:30:43########余额:900元
 2019-01-25 17:30:44********取款:100元
 2019-01-25 17:30:44########余额:800元
 2019-01-25 17:30:45--------存款:100元
 2019-01-25 17:30:45########余额:900元
 2019-01-25 17:30:46--------存款:100元
 2019-01-25 17:30:46########余额:1000元
 2019-01-25 17:30:47********取款:100元
 2019-01-25 17:30:47########余额:900元
 2019-01-25 17:30:48--------存款:100元
 2019-01-25 17:30:48########余额:1000元

三、多线程通信

      多线程通信有很多方式,如Object的wait/notify、condition的 await/signal 、LockSupport的park/unpark,本文以Object自带的wait和notify方法实现线程间的通信。

      现一个需求,子线程循环20次,主线程循环10次,子线程执行2次,主线程执行1次,按顺序交替执行

public class ThreadTest {

	public static void main(String[] args) {

		ThreadSubMain threadSubMain = new ThreadSubMain();

		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					threadSubMain.subExcute(i);
				}
			}
		}).start();

		threadSubMain.mainExcute();
	}

	static class ThreadSubMain {

		private boolean isWait = true;

		/// 子线程循环20次
		synchronized void subExcute(int i) {
			while (!isWait) {
				try {
					this.wait();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			
			for (int j = 0; j < 2; j++) {
				System.out.println("第" + i + "个,第" + j + "次子线程--->" + Thread.currentThread().getName());
			}
			//当子线程执行两次后,改变标识符,唤醒主线程执行;
			isWait = false;
			this.notify();
		}

		/// 主线程循环10次
		synchronized void mainExcute() {
			for (int i = 0; i < 10; i++) {
				while (isWait) {
					try {
						this.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("第" + i + "个主线程--->" + Thread.currentThread().getName());
				}
				//当主线程执行一次后,改变标识符,唤醒主子线程
				isWait = true;
				this.notify();
			}
		}
	}

}
第0个,第0次子线程--->Thread-0
第0个,第1次子线程--->Thread-0
第0个主线程--->main
第1个,第0次子线程--->Thread-0
第1个,第1次子线程--->Thread-0
第1个主线程--->main
第2个,第0次子线程--->Thread-0
第2个,第1次子线程--->Thread-0
第2个主线程--->main
第3个,第0次子线程--->Thread-0
第3个,第1次子线程--->Thread-0
第3个主线程--->main
第4个,第0次子线程--->Thread-0
第4个,第1次子线程--->Thread-0
第4个主线程--->main
第5个,第0次子线程--->Thread-0
第5个,第1次子线程--->Thread-0
第5个主线程--->main
第6个,第0次子线程--->Thread-0
第6个,第1次子线程--->Thread-0
第6个主线程--->main
第7个,第0次子线程--->Thread-0
第7个,第1次子线程--->Thread-0
第7个主线程--->main
第8个,第0次子线程--->Thread-0
第8个,第1次子线程--->Thread-0
第8个主线程--->main
第9个,第0次子线程--->Thread-0
第9个,第1次子线程--->Thread-0
第9个主线程--->main

四、线程中断

       Thread类中有3个关于中断的方法,成员方法interrupt和isInterrupted,以及静态方法interrupted。下面简要的介绍一下这三个方法。

1、interrupt

     给当前线程设置中断标志位,不会立即中断线程。如果当前线程正在中断,则设置标志位可以成功,否会调用checkAcess方法抛出SecurityException异常,比如当前线程阻塞调用wait 、join、sleep方法,中断标志位将被清除,中断不活动的线程也是无效的。

2、isInterrupted

      获取当前线程的中断状态。

 Thread thread = Thread.currentThread();
 System.out.println("1---->"+thread.getName()+"  " + thread.isInterrupted());
 thread.interrupt();
 System.out.println("2---->"+thread.getName()+"  " + thread.isInterrupted());
1---->main  false
2---->main  true

3、interrupted

       检查当前线程的中断标志,测试当前线程是否被中断,如果当前线程设置了中断标志,则返回一个boolean并清除中断状态,第二次调用的时候中断状态已经清除并返回false。

System.out.println("1---->"+Thread.currentThread().getName()+"  " + Thread.interrupted());
System.out.println("2---->"+Thread.currentThread().getName()+"  " + Thread.interrupted());
Thread.currentThread().interrupt();
System.out.println("3---->"+Thread.currentThread().getName()+"  " + Thread.interrupted());
System.out.println("4---->"+Thread.currentThread().getName()+"  " + Thread.interrupted());
 1---->main  false
 2---->main  false
 3---->main  true
 4---->main  false

        由运行结果可以看出,和结论完全吻合。

五、内存模型JMM

      Java(Java Memory Model,JMM)是Java虚拟机规范的一部分,也就是说JMM本质是一系列的规范,这些规范用于定义Java程序中多线程之间共享内存的行为。它描述了变量(包括实例字段、静态字段和数组元素)在内存中的存储和读取方式,以及在多线程环境中如何确保可见性和有序性。

     共享变量:在多个线程之间可见的变量,例如对象的字段、静态变量等;
     主内存:是线程间共享的内存区域所有线程都可以访问。主内存存储了共享变量的原始副本;
     工作内存:是线程私有的内存区域,每个线程有自己的工作内存。工作内存中存储了主内存中共享变量的副本。

     借用一下网上先贤总结好的图如下

1、JMM的作用

      保证多线程运行代码执行的原子性、可见性、有序性。

      ★ 原子性:

      确保在多线程环境中的执行结果与在单线程环境中的执行结果一致。一个操作一气呵成,要么完整地执行,要么不执行,不会出现部分执行的情况。

       在多线程编程中,使用 synchronized 关键字可以确保代码块或方法在同一时刻只能被一个线程执行,避免多线程并发访问导致的数据竞争问题,从而保证操作的原子性。另外CAS通过调用native方法操作机器码也可以保证原子性。

      ★ 内存可见性:

      可见性指的是一个线程对共享变量的修改能够及时对其他线程可见。

     ★有序性:

     有序性指的是程序执行的顺序与代码的顺序一致。

2、Happens-Before原则

       先行(Happens-Before)原则规定了一系列操作之间的先后顺序关系。如果一个操作Happens-Before于另一个操作,那么前一个操作对于后一个操作的结果是可见的。

     ★ 程序顺序规则: 在一个线程内,按照程序代码的先后顺序执行的操作,会产生Happens-Before的关系。

    ★监视锁定规则: 对于一个锁的解锁操作Happens-Before于后续对于该锁的加锁操作。

    ★ volatile变量规则: 对一个volatile变量的写操作Happens-Before于后续对这个变量的读操作。

    ★ 传递性规则: 如果操作A Happens-Before操作B,操作B Happens-Before操作C,则操作A Happens-Before操作C。

     ★ 线程启动规则:线程的启动操作Happens-Before于新线程的所有操作。

     ★ 线程终止规则:线程的所有操作Happens-Before于其他线程检测到该线程终止。

       对于程序员来说,编写Java的多线程程序,只要按照happens-before规则来,就能保证变量的可见性和有序性。

3、volatile 关键字

  volatile 关键字修饰变量,保证变量的修改对其他线程是可见的,防止编译器和处理器对指令进行重排序优化。在多线程编程中使用volatile 可以保证可见性和有序性。

      JMM通过内存屏障(memory barrier)来禁止重排序的。对于即时编译器来说,它会针对前面提到的每一个  happens-before关系,向正在编译的目标方法中插入相应的读读、读写、写读以及写写内存屏障。

      在访问olatile 修饰的变量时,所插入的内存屏障,将禁止 volatile 字段写操作之前的「内存访问被重排序至其之后;也将不允许 volatile 字段读操作之后的内存访问被重排序至其之前。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值