第一章:线程锁synchronized学习

2022-08-05 09:18:03 浏览数 (3)

大家好,又见面了,我是你们的朋友全栈君。

代码语言:javascript复制
package com.xiaoyexinxin.ThreadLearn;

public class MyThread extends Thread{
	private int count=5;
	
	//synchronized加锁
	public synchronized void run(){
		count --;
		System.out.println(this.currentThread().getName() "count=" count);
	}
	
        public static void main(String args[]{
		MyThread myThread=new MyThread();
		Thread t1=new Thread(myThread,"t1");
		Thread t2=new Thread(myThread,"t2");
		Thread t3=new Thread(myThread,"t3");
		Thread t4=new Thread(myThread,"t4");
		Thread t5=new Thread(myThread,"t5");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}

}

执行结果:

代码语言:javascript复制
t1count=4
t2count=3
t4count=2
t5count=1
t3count=0

每次的运行的结果走的线程顺序都不一样,说明是通过cpu分配的先后顺序。

下面如果我们把synchronized去掉,就会出现线程不安全的情况,多次执行程序会出现下面的结果:

代码语言:javascript复制
t1count=3
t5count=1
t4count=2
t2count=3
t3count=0

我们分析下为什么会出现这种情况:因为没有加锁,从结果可以看出,可能是t1线程先抢到执行资源,执行run方法,count减一,count为4,然后在他还没执行system输出语句时,资源被另外一个线程夺取了,这个线程开始执行run方法,执行了count减一,此时count变为3了。然后先前那个资源这时又重新夺取了资源,执行system输出语句,可是它此时再去取count值时,取到的是3,已经不是它应该得到4了。现实中,我们想到的的数据结果是43210都有,可是由于线程不安全的影响会出现数据缺失。所以这就是所谓的线程不安全。

另外可以看出执行结果的顺序比较混乱,说明先夺取资源执行方法体不一定先输出,先输出也不一定先打印到控制台。

下面我们分析加锁的影响:当多个线程访问myThread的run方法是,以排队的方式进行处理(这里排队是按照CPU分配的先后顺序而定的),一个线程想要执行synchronized修饰的方法里的代码,首先是尝试获得锁,如果得到锁,执行synchronized代码体内容,直到整个方法执行完才会释放锁让别的线程进来,这样就不会出现上面数据缺失的现象,拿不到锁,这个线程就会不断的尝试获得这把锁,直到拿到为止,而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题,锁竞争问题对于线程比较少的情况倒是影响不大,但是如果线程很多的话,同一时间有很多线程要抢夺一把锁,CPU会瞬间达到很高的占用率,很有可能就宕机了,其实这和我们双十一抢购商品是一样的,在0点0分0秒瞬间有几十万几百万的请求过来,这对于一般的服务器来说,瞬间就崩溃了,因此我们尽量避免 锁竞争的问题)

示例二:多个线程多个锁

多个线程多个锁:多个线程,每个线程都可以拿到自己指定的锁,分别获得锁之后,执行synchronized方法体的内容。

代码语言:javascript复制
package com.xiaoyexinxin.ThreadLearn;

public class MultiThread2 {

	private int num=0;
	public synchronized void printNum(String tar){
		if(tar.equals("a")){
			num=100;
			System.out.println("线程a");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else{
			num=200;
			System.out.println("线程b");

		}
		System.out.println("输出的线程是:" tar ",num:" num);
	}
	
	public static void main(String[] args) {
		final MultiThread2 mThread1=new MultiThread2();
		final MultiThread2 mThread2=new MultiThread2();//设为final表示此对象不能别继承,没有子类。如果是方法被定义成final,则此方法不能被重写
		Thread t1=new Thread(new Runnable() {
			
			public void run() {
				// TODO Auto-generated method stub
				mThread1.printNum("a");
			}
		});
		Thread t2=new Thread(new Runnable() {
			
			public void run() {
				// TODO Auto-generated method stub
				mThread2.printNum("b");
				
			}
		});
		t1.start();
		t2.start();

	}

}

执行结果:

代码语言:javascript复制
线程a
线程b
输出的线程是:b,num:200
输出的线程是:a,num:100

我们运行该类,会看到如下结果,可以看到线程t1和t2互不影响,各自运行各自的,我们让t1线程休息了1秒钟,这样t2线程执行完了,t1线程才执行完。synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁,所以上面代码中哪个线程先执行synchronized关键字的方法,那个线程就持有该方法所属对象的锁(Lock),两个对象,线程获得的就是两个不同的锁,他们互不影响。

有一种情况则是相同的锁,那就是在静态方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)。为了看效果,我们在变量num前面加static关键字, 给printNum方法加上static关键字,如下图所示。

结果:

代码语言:javascript复制
线程a
输出的线程是:a,num:100
线程b
输出的线程是:b,num:200

这样情况就跟单个线程单个锁执行的效果一样了。要等先进来的线程把run方法执行完了才能执行另外的线程。

下面我们聊聊对象锁的同步和异步

同步:synchronized

同步的概念就是共享,我们要牢牢记住”共享”这两个字,如果不是共享的资源,就没有必要进行同步(举个简单例子,我们存款和取款,一定要同步,因为不同步的 话,资金就乱了,这是不能忍受的)。

异步:asynchronized

异步的概念就是独立,相互之间不受到任何制约。就好像我们学习http的时候,在页面发起的Ajax请求,我们还可以继续浏览或操作页面的内容,二者之间没有任何关系。

同步的目的就是为了线程安全,其实对于线程安全来说,需要满足两个特性:原子性(同步)和可见性

异步的目的及时快了,能同时干多件事。

异步的例子:

代码语言:javascript复制
package com.xiaoyexinxin.ThreadLearn;

public class MultiThreadAsyn {
	
	public  synchronized void method1(){
		System.out.println(Thread.currentThread().getName());
		try {
			Thread.sleep(3000);
			System.out.println("method1结束");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	 public void method2(){  
	        System.out.println(Thread.currentThread().getName());  
	    }  

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		final MultiThreadAsyn mta=new MultiThreadAsyn();
		Thread t1=new Thread(new Runnable() {
			
			public void run() {
				// TODO Auto-generated method stub
				mta.method1();
			}
		});
		
		Thread t2=new Thread(new Runnable() {
			
			public void run() {
				// TODO Auto-generated method stub
				mta.method2();
			}
		});
		
		t1.start();
		t2.start();

	}

}

执行结果是:

代码语言:javascript复制
Thread-0
Thread-1
method1结束

运行上面的main方法,我们会看到t1和t2同时打印出来了,这说明此时method1和method2是异步执行的。

下面我们给method2方法也加上synchronized,看看执行结果

代码语言:javascript复制
Thread-0
method1结束
Thread-1

再运行main方法,这回我们看到t1信息输出4秒后才会看到t2信息,出现这种情况的原因是,我们只new了一个对象,而synchronized关键字锁的便是对象,由于method1和method2现在都被synchronized关键字修饰,因此只要是访问该对象的这两个方法的线程都会进行同步操作,也就是说谁先拿到对象的锁谁便先执行,执行完之后,释放锁,这时下一个线程才能执行。

A线程先持有object对象的Lock锁,B线程如果在这个时候调用对象中的同步(synchronized)方法则需要等待,也就是同步。

A线程先持有object对象的Lock锁,B线程可以以异步的方式调用对象中的非synchronized修饰的方法。

4,使用继承Thread实例:

代码语言:javascript复制
package client.cfca;
/**
 * 
 * @author liuxin
 * @date   2018年8月1日
 */
public class ad {

	public static void main(String[] args) {
		Dog t1=new Dog();
		Dog t2=new Dog();
		Dog t3=new Dog();
		Dog t4=new Dog();
		t1.setName("t1");
		t2.setName("t2");
		t3.setName("t3");
		t4.setName("t4");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

class Dog extends Thread{
	private int count=3;
	public  void run(){
			try {
				synchronized (this) {
					for(int i=0;i<3;i  ){
						System.out.println(Thread.currentThread().getName() "    count=" count--);
						sleep(5000);
					}
					System.out.println(Thread.currentThread().getName());
				}
				
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}
}

结果:每隔5s打印4条记录

代码语言:javascript复制
t1    count=3
t3    count=3
t4    count=3
t2    count=3
t1    count=2
t3    count=2
t4    count=2
t2    count=2
t1    count=1
t3    count=1
t4    count=1
t2    count=1
t1
t3
t4
t2

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/106151.html原文链接:https://javaforall.cn

0 人点赞