Java多线程创建及同步锁的理解

1,什么是多线程

多线程顾名思义就是多个线程在程序中运行,就比如我们的虚拟机JVM,启动的时候不止启动一个线程,至少有一个是负责Java程序的执行还有一个是垃圾回收机制GC的线程。

一个进程中至少有一个线程

2,多线程的创建

一种是extend Thread类,另一种implement Runnable接口,大部分会使用implement Runnable接口,因为一个类只能继承一个父类,而实现可以实现多个接口,可扩展性比较好

java 复制代码
//extend Thread类
public class ThreadDemo {
	public static void main(String[] args) {
		Demo demo = new Demo();
		demo.start();
	}
}
class Demo extends Thread{
	
	@Override
	public void run() {
		for(int i=0 ; i<10; i++){
			System.out.println("Demo.run() i = "+i);
		}
	}
}
java 复制代码
//implement Runnable
public class ThreadDemo {
	public static void main(String[] args) {
		Demo demo = new Demo();
		Thread thread1 = new Thread(demo);
		Thread thread2 = new Thread(demo);
		thread1.start();
		thread2.start();
	}
}
class Demo implements Runnable{
	private int ticket = 100;
	@Override
	public void run() {
		while (true) {
			if (ticket > 0) {
				System.out.println(Thread.currentThread().getName()+" , sale ticket = "+ticket--);
			}
		}
	}
}

运行结果部分,由于数据太多就贴这么多,可以看到线程1和线程2是交替进行打印:

bash 复制代码
Thread-0 , sale ticket = 100
Thread-1 , sale ticket = 99
Thread-0 , sale ticket = 98
Thread-1 , sale ticket = 97
Thread-0 , sale ticket = 96
Thread-1 , sale ticket = 95
Thread-1 , sale ticket = 93
Thread-1 , sale ticket = 92
Thread-1 , sale ticket = 91
Thread-1 , sale ticket = 90
Thread-1 , sale ticket = 89
Thread-1 , sale ticket = 88
Thread-1 , sale ticket = 87
Thread-1 , sale ticket = 86
Thread-1 , sale ticket = 85
.....
Thread-0 , sale ticket = 2
Thread-1 , sale ticket = 7
Thread-0 , sale ticket = 1

在多线程运行中,其实每一次的运行结果都是不同的,因为多个线程都获取CPU的执行权,CPU执行到谁,谁就运行,明确一点,在某一个时刻,只能有一个程序在运行(多核除外)

CPU在做着快速的切换,已达到看上去是同时运行的效果,我们可以形象把多线程的运行行为在互相抢夺CPU的执行权。

这就是多线程的一个特性:随机性,谁抢到谁执行,至于执行多长,CPU说的算

3,线程的运行状态

线程的运行状态如下图,包含这些方法 start(),sleep(),wait(),notify(),run()

4,多线程的同步

这里由于多线程存在安全问题,所以引出了同步这个概念

在上面的买票例子中我们加一个睡眠就会让线程变得不安全,会出现票为0,-1的情况,如下:

java 复制代码
private int ticket = 100;
	@Override
	public void run() {
		while (true) {
			if (ticket > 0) {
				//当ticket为1时,会出现一种情况就是第一个线程会这里睡一会,
				//而第二个线程走进来时执行打印,ticket就会变为0,
				//而第一个线程睡醒又会执行打印,ticket变为-1
				try {
					Thread.sleep(10);//线程睡眠
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+" , sale ticket = "+ticket--);
			}
		}
	}

部分打印结果,虽然结果没有包含-1,但是 是有这种情况出现的,可以看到0已经出现了:

bash 复制代码
....
Thread-0 , sale ticket = 3
Thread-0 , sale ticket = 2
Thread-1 , sale ticket = 1
Thread-0 , sale ticket = 0

如何解决出现的这个问题呢,就需要synchronized,这里会说三种情况的锁:

1,同步代码块锁object

2,同步函数锁this

3,静态同步函数锁class对象

经典理解锁的现实案例:火车上卫生间

同步的前提:

1,必须要有两个或者两个以上的线程

2,必须是多个线程使用同一个锁

必须保证同步中只能有一个线程在运行

好处:解决了多线程的安全问题

弊端:多个线程需要判断锁,较为消耗资源

1,同步代码块锁object,继续对上面的例子进行修改:

java 复制代码
class Demo implements Runnable{
	Object object = new Object();//定义一个锁对象,不管什么对象都可以
	private int ticket = 100;
	@Override
	public void run() {
		while (true) {
			//同步代码块
			synchronized (object) {//有了同步锁之后,线程1执行完之后,另一个线程2才可以执行
				if (ticket > 0) {
					try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
					System.out.println(Thread.currentThread().getName()+" , sale ticket = "+ticket--);
				}
			}
		}
	}
}

执行结果已经正常,没有0出现:

bash 复制代码
....
Thread-1 , sale ticket = 10
Thread-1 , sale ticket = 9
Thread-1 , sale ticket = 8
Thread-1 , sale ticket = 7
Thread-1 , sale ticket = 6
Thread-1 , sale ticket = 5
Thread-1 , sale ticket = 4
Thread-1 , sale ticket = 3
Thread-1 , sale ticket = 2
Thread-1 , sale ticket = 1

2,同步函数锁this

同步函数用的是哪一个锁呢?函数需要被对象调用,那么函数都有一个所属对象引用,就是this,所以同步班函数使用的锁就是this。

java 复制代码
class Demo implements Runnable{
	private int ticket = 100;
	@Override
	public void run() {
		while (true) {
			this.show();
		}
	}
	private synchronized void show(){
		if (ticket > 0) {
			try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
			System.out.println(Thread.currentThread().getName()+" , sale ticket = "+ticket--);
		}
	}

案例:有两个储户,分别存储300,每次存储100

java 复制代码
public class ThreadDemo {
	public static void main(String[] args) {
		Demo demo = new Demo();
		Thread thread1 = new Thread(demo);
		Thread thread2 = new Thread(demo);
		thread1.start();
		thread2.start();
	}
}
class Demo implements Runnable{
	private int ticket = 100;
	Bank b = new Bank();
	@Override
	public void run() {
		for (int x = 0; x < 3; x++) {
			b.add(ticket);
		}
		
	}
	class Bank{
		private int  sum ;//共享数据
		public synchronized void add(int n){**//同步函数**
			sum = sum+n;//可能出现不安全情况,当sum为100时,1线程执行,睡眠,线程2进来,sum也为100,线程2输出200,线程1醒来也输出200,出现问题。
			try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
			System.out.println(sum);
		}
				
	}
}

加上锁之后,打印:

bash 复制代码
100
200
300
400
500
600

没加锁之前打印,可以看到出现了两次200,说明线程不安全执行了:

bash 复制代码
200
300
200
500
600

3,静态同步函数锁class对象

如果同步函数被静态修饰后,使用的锁是什么呢?通过验证,发现不在是this,因为静态方法中也不可以定义this。

静态进内存是,内存中没有本类对象,但是一定有该类对应的字节码文件对象,类名.class,该对象的类型是Class

静态的同步方法,使用的锁是该方法所在类的字节码文件对象,类名.class

下面代码出现了两个地方同步,一个是静态的同步,一个是同步的代码块用的锁是Class,但是他们都是同一个锁,所以运行结果也是安全的。

java 复制代码
class Demo implements Runnable{
	private static int ticket = 100;
	Boolean flag = true;
	@Override
	public void run() {
		
		if(flag){
		while (true) {
			synchronized (Demo.class) {//同步代码函数,锁对象时class
				if (ticket > 0) {
					try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
					System.out.println(Thread.currentThread().getName()+" , sale ticket = "+ticket--);
				}
			}	
		}
		}else{
			while (true) {
			show()
			}
		}
		
	}
	
	private static synchronized void show(){//静态的同步函数
		if (ticket > 0) {
			try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
			System.out.println(Thread.currentThread().getName()+" , sale ticket = "+ticket--);
		}
	}

5,单例模式

1,饿汉式

java 复制代码
class Single
{
	private static final Single s = new Single();
	private Single(){}
	public static Single getInstance(){
		return s;
	}
}

2,懒汉式

下面的写法并不安全

java 复制代码
class Single
{
	private static Single s = null;
	private Single(){}
	private static Single getInstance()
	{
		if(s==null)
		//---->A  A线程睡醒new第一次
		//---->B  B线程也new第一次
			s = new Single();
		return s;
	}
}

加上锁:

java 复制代码
class Single
{
	private static Single s = null;
	private Single(){}
	private static Single getInstance()
	{
	synchronized(Single.class)//静态方法需要使用静态锁
	{
	if(s==null)
			s = new Single();
	}
		
		return s;
	}
}

为了防止线程多次读锁,也可以再次优化:

java 复制代码
class Single
{
	private static Single s = null;
	private Single(){}
	private static Single getInstance()
	{
	//3. 待A醒来之后,s边不再为空,那么C,D...线程判断s不为空就不会在进去
		if(s == null)
		{
		//2. B 在A睡眠的时候也可以进去,但是没有锁就不能进去
			synchronized(Single.class)
			{//4. A出去之后,B进来,但是s有值了,也进不去
				if(s==null)
				//1. ---->A进来,睡眠
					s = new Single();
			}
		}
		return s;
	}
}

6,多线程的死锁

同步中的嵌套同步。

了解即可,定义了两个静态的对象

线程1拿了locka的锁,进入下一个同步需要lockb的锁,但是线程2拿了lockb的锁,进入下一个同步需要locka的锁,

线程1和线程2 都没有释放自己的锁,导致运行卡住

运行结果,都在等对方的锁,导致运行卡住:

相关推荐
杨充5 分钟前
13.观察者模式设计思想
java·redis·观察者模式
Lizhihao_7 分钟前
JAVA-队列
java·开发语言
喵叔哟17 分钟前
重构代码之移动字段
java·数据库·重构
喵叔哟17 分钟前
重构代码之取消临时字段
java·前端·重构
fa_lsyk19 分钟前
maven环境搭建
java·maven
远望清一色26 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
何曾参静谧34 分钟前
「Py」Python基础篇 之 Python都可以做哪些自动化?
开发语言·python·自动化
Prejudices38 分钟前
C++如何调用Python脚本
开发语言·c++·python
Daniel 大东38 分钟前
idea 解决缓存损坏问题
java·缓存·intellij-idea
wind瑞1 小时前
IntelliJ IDEA插件开发-代码补全插件入门开发
java·ide·intellij-idea