自学内容网 自学内容网

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

1,什么是多线程

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

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

2,多线程的创建

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

//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);
}
}
}
//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是交替进行打印:

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的情况,如下:

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已经出现了:

....
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,继续对上面的例子进行修改:

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出现:

....
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。

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

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);
}

}
}

加上锁之后,打印:

100
200
300
400
500
600

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

200
300
200
500
600

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

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

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

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

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,饿汉式

class Single
{
private static final Single s = new Single();
private Single(){}
public static Single getInstance(){
return s;
}
}

2,懒汉式
下面的写法并不安全

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;
}
}

加上锁:

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

return s;
}
}

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

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 都没有释放自己的锁,导致运行卡住
在这里插入图片描述
运行结果,都在等对方的锁,导致运行卡住:
在这里插入图片描述


原文地址:https://blog.csdn.net/qq_34966875/article/details/140579367

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!