多线程学习-day-03synchronized内置锁

线程基础、线程之间的共享和协作

(目前会将一些概念简单描述,一些重点的点会详细描述)

线程常用方法和线程的状态

        start():调用start()方法后,使线程从新建状态处于就绪状态。

        sleep():调用sleep()方法后,设置休眠时间,使线程从运行状态处于阻塞(休眠)状态,休眠时间到,线程从阻塞状态转变为就绪状态。

        wait():调用wait()方法后,使线程从运行状态处于阻塞(休眠)状态,只有通过notify()或者notifyAll()方法重新使线程处于就绪状态。(后续补充notify()和notifyAll()方法)。

        interrupt():调用interrupt()方法后,不是强制关闭线程,只是跟线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。

        isInterrypt():线程中断标志位,true/false两个Boolean值,用来判断是否调用interrupt()方法,告诉线程是否中断。

        interrupted():判断线程是否处于中断状态,并将中断标志位改为false。

        run():运行线程的方法。

 

synchronized内置锁

1、用处

        synchronized作为线程同步的关键字,设计到的概念,下面就对锁的概念进行详细介绍。

        Java内置锁是一个互斥锁,这就说明最多只有一个线程能够获得该锁,例如两个线程:线程A和线程B,如果线程A尝试去获得线程B的内置锁,则线程A必须等待或者阻塞,直到线程B释放这个锁为止;如果线程B永不释放这个锁,则线程A则永远处于等待或阻塞状态。

        Java的对象锁和类锁在锁的概念上,与内置锁几乎是一致的,但是对象锁和类锁的区别是非常大的。

2、对象锁

        用synchronized修饰非静态方法、用synchronized(this)作为同步代码块、用synchronized(非this对象)的用法锁的是对象,线程想要执行对应的同步代码,需要先获得对象锁。

3、类锁

        用synchronized修饰静态方法、用synchronized(类.class)的用法锁的是类,线程想要执行对应的同步代码,需要先获得类锁。

 

以下对synchronized关键字的用法,对象锁,类锁用实际代码为例子进行介绍:

1、先看一个非线程安全的实例,看看synchronized的用途

public class SynRun {

	public static void main(String[] args) {
		// 定义HasSelfNum对象
		HasSelfNum hasSelfNum = new HasSelfNum();

		// 定义ThreadZS多线程类
		ThreadZS threadZS = new ThreadZS(hasSelfNum);
		threadZS.start();

		// 定义ThreadLS多线程类
		ThreadLS threadLS = new ThreadLS(hasSelfNum);
		threadLS.start();
	}

}

// 定义一个类HasSelfNum,作为同步设置num的变化
class HasSelfNum {
	// 定义一个变量num
	private int num = 0;

	// 定义一个类的方法addNum,并传一个字符串作为形参
	public void addNum(String name) {
		try {
			if (name.equals("zs")) {
				num = 100;
				System.out.println("zs设置了num参数...");
				Thread.sleep(2000);
			} else {
				num = 200;
				System.out.println("ls设置了num参数...");
			}
			// 将num参数输出
			System.out.println(name + " 设置的 num = " + num);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

// 定义一个多线程类ThreadZS
class ThreadZS extends Thread {
	// 定义HasSelfNum
	private HasSelfNum hasSelfNum;

	// 设置构造函数,将对象赋值
	public ThreadZS(HasSelfNum hasSelfNum) {
		super();
		this.hasSelfNum = hasSelfNum;
	}

	// 定义run方法
	@Override
	public void run() {
		super.run();
		hasSelfNum.addNum("zs");
	}
}

// 定义一个多线程类ThreadLS
class ThreadLS extends Thread {
	// 定义HasSelfNum
	private HasSelfNum hasSelfNum;

	// 设置构造函数,将对象赋值
	public ThreadLS(HasSelfNum hasSelfNum) {
		super();
		this.hasSelfNum = hasSelfNum;
	}

	// 定义run方法
	@Override
	public void run() {
		super.run();
		hasSelfNum.addNum("ls");
	}
}

控制台输出结果:
zs设置了num参数...
ls设置了num参数...
ls 设置的 num = 200
zs 设置的 num = 200

看到输出结果是非线程安全的。

接下来将HasSelfNum类里面的addNum()方法加上synchronized关键字,其余代码不变

// 定义一个类HasSelfNum,作为同步设置num的变化
class HasSelfNum {
	// 定义一个变量num
	private int num = 0;

	// 定义一个类的方法addNum,并传一个字符串作为形参,加上了synchronized关键字
	public synchronized void addNum(String name) {
		try {
			if (name.equals("zs")) {
				num = 100;
				System.out.println("zs设置了num参数...");
				Thread.sleep(2000);
			} else {
				num = 200;
				System.out.println("ls设置了num参数...");
			}
			// 将num参数输出
			System.out.println(name + " 设置的 num = " + num);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

运行后,控制台输出结果:
zs设置了num参数...
zs 设置的 num = 100
ls设置了num参数...
ls 设置的 num = 200

由此可看出,两个线程访问同一个对象中的同步方法一定是线程安全的。因为对象的方法加上了synchronized关键字成为同步方法,所以先把zs打印出来,再把ls打印出来。

 

2、不同线程调用不同对象

只修改了main方法里面的写法,其余代码不变

public static void main(String[] args) {

    // 定义2个HasSelfNum对象
    HasSelfNum hasSelfNum1 = new HasSelfNum();
    HasSelfNum hasSelfNum2 = new HasSelfNum();

    // 定义ThreadZS多线程类,用hasSelfNum1对象的方法
    ThreadZS threadZS = new ThreadZS(hasSelfNum1);
    threadZS.start();

    // 定义ThreadLS多线程类,用hasSelfNum2对象的方法
    ThreadLS threadLS = new ThreadLS(hasSelfNum2);
    threadLS.start();
}

控制台输出结果:
zs设置了num参数...
ls设置了num参数...
ls 设置的 num = 200
zs 设置的 num = 100

可以看出输出结果是非同步的,因为线程threadZS获得的是hasSelfNum1的对象锁,threadLS获得的是hasSelfNum2的对象锁,他们没有获得同一个对象锁,没有出现竞争情况,因此是非同步的结果

 

3、运用synchronized(tihs)同步代码块

public class SynRun1 {

	public static void main(String[] args) {
		// 初始化ShowTime对象
		ShowTime showTime = new ShowTime();

		// 初始化ThreadA多线层对象
		ThreadA threadA = new ThreadA(showTime);
		threadA.start();

		// 初始化TreadB多线程对象
		ThreadB threadB = new ThreadB(showTime);
		threadB.start();
	}

}

// 定义一个类ShowTime
class ShowTime {

	// 定义一个显式时间方法showTime
	public void showTime() {
		// 利用synchronized(this)同步代码块作为同步方法
		try {
			synchronized (this) {
				System.out.println("一个进程开始运行,运行时间为:" + System.currentTimeMillis());
				// 设置休眠时间
				Thread.sleep(2000);
				System.out.println("这个进程运行结束,结束时间为:" + System.currentTimeMillis());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

// 定义一个线程A
class ThreadA extends Thread {
	// 定义ShowTime对象
	private ShowTime showTime;

	// 定义有餐构造函数
	public ThreadA(ShowTime showTime) {
		super();
		this.showTime = showTime;
	}

	// 定义run方法
	@Override
	public void run() {
		super.run();
		showTime.showTime();
	}
}

// 定义一个线程B
class ThreadB extends Thread {
	// 定义ShowTime对象
	private ShowTime showTime;

	// 定义有餐构造函数
	public ThreadB(ShowTime showTime) {
		super();
		this.showTime = showTime;
	}

	// 定义run方法
	@Override
	public void run() {
		super.run();
		showTime.showTime();
	}
}

控制台输出结果为:
一个进程开始运行,运行时间为:1539525103617
这个进程运行结束,结束时间为:1539525105619
一个进程开始运行,运行时间为:1539525105619
这个进程运行结束,结束时间为:1539525107619

结果显示也是同步的,线程获取的是synchronized(this){}括号里面的对象实例的对象锁。

 

4、运用synchronized(非this对象)

public class SynRun1 {

	public static void main(String[] args) {
		ShowInfo service = new ShowInfo("Cansluck");

		ThreadA a = new ThreadA(service);
		a.setName("A");
		a.start();

		ThreadB b = new ThreadB(service);
		b.setName("B");
		b.start();
	}
}

class ShowInfo {

	String info = new String();

	public ShowInfo(String info) {
		this.info = info;
	}

	public void showInfo() {
		try {
			synchronized (info) {
				System.out.println(
						"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块");
				Thread.sleep(3000);
				System.out.println(
						"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class ThreadA extends Thread {
	private ShowInfo showInfo;

	public ThreadA(ShowInfo showInfo) {
		super();
		this.showInfo = showInfo;
	}

	@Override
	public void run() {
		showInfo.showInfo();

	}

}

class ThreadB extends Thread {

	private ShowInfo showInfo;

	public ThreadB(ShowInfo showInfo) {
		super();
		this.showInfo = showInfo;
	}

	@Override
	public void run() {
		showInfo.showInfo();

	}

}

控制台输出结果:
线程名称为:A在1539525475324进入同步块
线程名称为:A在1539525478325离开同步块
线程名称为:B在1539525478325进入同步块
线程名称为:B在1539525481325离开同步块

这里线程争夺的是info的对象锁,两个线程有竞争同一对象锁的关系,出现同步

 

5、静态synchronized同步方法

public class SynRun {

	public static void main(String[] args) {

		ThreadAA a = new ThreadAA();
		a.setName("A");
		a.start();

		ThreadBB b = new ThreadBB();
		b.setName("B");
		b.start();

	}

}

class Service {

	synchronized public static void printA() {
		try {
			System.out.println(
					"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
			Thread.sleep(3000);
			System.out.println(
					"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	synchronized public static void printB() {
		System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
		System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
	}

}

class ThreadAA extends Thread {
	@Override
	public void run() {
		Service.printA();
	}

}

class ThreadBB extends Thread {
	@Override
	public void run() {
		Service.printB();
	}
}

控制台输出结果:
线程名称为:B在1539525627704进入printB
线程名称为:B在1539525627704离开printB
线程名称为:A在1539525627704进入printA
线程名称为:A在1539525630705离开printA

两个线程在争夺同一个类锁,因此同步

 

6、运用synchronized (类.class)

public class SynRun {

	public static void main(String[] args) {

		ThreadAA a = new ThreadAA();
		a.setName("A");
		a.start();

		ThreadBB b = new ThreadBB();
		b.setName("B");
		b.start();

	}

}

class Service {

	public static void printA() {
		synchronized (Service.class) {
			try {
				System.out.println(
						"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
				Thread.sleep(3000);
				System.out.println(
						"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

	}

	public static void printB() {
		synchronized (Service.class) {
			System.out.println(
					"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
			System.out.println(
					"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
		}
	}
}

class ThreadAA extends Thread {
	@Override
	public void run() {
		Service.printA();
	}

}

class ThreadBB extends Thread {
	@Override
	public void run() {
		Service.printB();
	}
}

控制台输出结果:
线程名称为:A在1539525736333进入printA
线程名称为:A在1539525739334离开printA
线程名称为:B在1539525739334进入printB
线程名称为:B在1539525739334离开printB

两个线程依旧在争夺同一个类锁,因此同步

需要特别说明:对于同一个类A,线程1争夺A对象实例的对象锁,线程2争夺类A的类锁,这两者不存在竞争关系。对象锁和类锁互互不干预

静态方法则一定会同步,非静态方法需在单例模式才生效,但是也不能都用静态同步方法,总之用得不好可能会给性能带来极大的影响。另外,有必要说一下的是Spring的bean默认是单例的

对象锁:锁的是类的对象实例。

类锁 :锁的是每个类的的Class对象,每个类的的Class对象在一个虚拟机中只有一个,所以类锁也只有一个。

来自享学IT教育课后总结。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章