Java基础笔记:Day_14 多线程与synchronized

本文深入探讨Java中进程与线程的基本概念,详细介绍通过继承Thread类和实现Runnable接口创建线程的方法,以及解决多线程安全性问题的三种策略:同步代码块、同步方法和锁机制。

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

一、基本概念

1.进程与线程
进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少一个线程。
线程:堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间是可以影响的,又称之为轻型进程或者进程元。

线程的先后取决于JVM,程序员无法控制。JVM采用的是抢占式调度,没有分时调度,因此可能造成多线程执行结果的随机性。

2.创建线程
最传统的两种方式:
1.使用继承手段:继承Thread类
2.使用接口手段:实现Runnable接口

对于法1步骤:
①定义一个类A继承于java.lang.Thread类
②在A类中覆盖Thead类中的run方法
③我们在run方法中编写需要执行的操作—>run方法里的线程执行体。
④在我们的main方法(main线程)中创建一个线程对象并且启动。

A类 a =new A();//创建一个线程
a.start();//启动线程。

注意:千万不用调用run方法。否则依然还是只有一个线程。
代码实例:

class MusicThread extends Thread
{
	@Override
	public void run() {
		for(int i = 0; i < 50 ;i++) {
			System.out.println("Music" + i);
		}
	}
}

public class Demo {
	public static void main(String[] args) {
		for(int i = 0; i < 50 ; i++){
			System.out.println("Game" + i);
			if(i == 10) {
				MusicThread mt = new MusicThread();
				mt.start();
			}	
		}
	}
}

对于法2的步骤:
①定义一个类A实现于java.lang.Runnable接口
②在A类中覆盖Runnable接口中的run方法。
③在run方法中编写需要执行的操作
④在我们的main方法(main线程)中创建一个线程对象并且启动。

Thread a =new Thread(new A());//创建一个线程,注意:A类此时不是线程类
a.start();//启动线程。

此刻,Thread构造器需要一个Runnable对象/Runnable实现类的对象。
代码实例:

class MusicThread implements Runnable
{
	@Override
	public void run() {
		for(int i = 0; i < 50 ;i++) {
			System.out.println("Music" + i);
		}
	}
}

public class Demo {
	public static void main(String[] args) {
		for(int i = 0; i < 50 ; i++){
			System.out.println("Game" + i);
			if(i == 10) {
				Runnable target = new MusicThread();
				Thread mt = new Thread(target);
				mt.start();
			}	
		}
	}
}

对于两种方式:
继承方式:
①java中类是单继承的,如果继承了Thread了,该类就不能再有其他父类。
②从操作上来说,继承方式更为简单,操作也十分简单。
③从多线程共享同一个分析,继承方式不能做到
实现方式:
①java中类可以实现多接口,此时还可以继承其他类。
②从操作上分析,实现方式稍微复杂,获取线程的名字也比较复杂。
③从多线程共享同一个资源上讲,实现方式可做到。
建议多用实现方式创建线程。

3.线程的安全性问题

案例:

class Apple implements Runnable
{
	private int num = 10;
	
	@Override
	public void run() {
		for(int i = 0 ; i < 10 ; i++) {
			if(num > 0) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"吃了"+ (num--) + "号苹果");
			}
		}
	}
}

public class Demo {
	public static void main(String[] args) {
		Apple a = new Apple();
		//三个线程共用同一个Apple对象
		new Thread(a,"A").start();
		new Thread(a,"B").start();
		new Thread(a,"C").start();
	}
}

在输出后,可以看到一个不正常的现象:
在这里插入图片描述
8,9号苹果都被吃了两次。C和A都拿到了编号为9的苹果,打印出来,还没来得及数num–的时候,A和C已经做了-1的操作,线程进入了睡眠,num还剩8个,B线程来了打印其值。此时A线程醒来做打印操作。
解决方案:A线程进入操作的时候,B和C都只能在外等着。保证打印和苹果减一操作必须同步完成。方式有3:
①同步代码块
②同步方法
③锁机制

法1:同步代码块

语法:

synchronized(同步锁)
{
	//需要同步操作的代码块
}

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。Java程序运行使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的同步资源作为同步资源作为同步监听对象。
注意:在任何时候都只运行一个线程拥有同步锁。(谁拿到锁就进入代码块,其他的线程只能在外等着)

将上面的部分代码修改一下:

class Apple implements Runnable {
	private int num = 10;

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			//同步代码块
			synchronized (this) {//this表示属于多线程共享资源
				if (num > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "吃了" + (num--) + "号苹果");
				}
			}
		}
	}
}

法2:同步方法
使用synchronized修饰的方法,就叫同步方法。保证了A线程执行该方法的时候,其他线程只能在外等着。

synchronized public void doWork()
{
	//CODE
}

同步锁是谁?对于非static方法,同步锁就是this,对于static方法,我们使用当前方法所在类 的字节码对象(Apple.class)
但是,不要使用synchronized修饰run方法,因为修饰后某一线程就执行完了。就好比是多个线程出现了串行。正确操作是:把需要同步的代码块写在一个新的方法中,在run方法中去调用它。

class Apple implements Runnable {
	private int num = 10;

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			testMethod();
		}
	}

	synchronized private void testMethod() {
		if (num > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "吃了" + (num--) + "号苹果");
		}
	}
}

synchronized的优劣性:
优点:保证了多线程并发访问时候的同步操作,避免了线程的安全性问题。
缺点:使用synchronized的方法/代码块性能会有所下降。
建议:尽量减少synchronized的作用域。

法3:锁机制

Lock机制提供了比synchronized更为广泛的锁定操作,同步代码块/同步方法具有的功能lock都有,更能体现面向对象。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Apple implements Runnable {
	private int num = 10;

	private final Lock lock = new ReentrantLock();

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			testMethod();
		}
	}

	private void testMethod() {
		//进入方法,立即加锁
		lock.lock();//获取锁
		try {
			if (num > 0) {
				Thread.sleep(10);
				System.out.println(Thread.currentThread().getName() + "吃了" + (num--) + "号苹果");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();//释放锁
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值