java的多线程之线程的同步(synchronized )

java对于多线程的安全问题提供了专业的解决方式: 同步机制

什么时候需要使用synchronized :

只有共享资源的读写访问才需要同步化,如果不是共享资源那么根本就没有必要同步
目的:就是使得共享数据变得更加安全。

互斥锁

1)引入对象互斥锁,来保证共享数据操作的完整性。
2每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象

关键字synchronized 来与对象的互斥锁联系

1)当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
2)同步的局限性:导致程序的执行效率要降低
3)同步方法(非静态的)的锁为this。
4)同步方法(静态的)的锁为当前类本身。

举一个售票的例子:有三个窗口同时售100张票,可能自己运行一百次都不会出现重票等错误其他情况,程序运行结果没有错并不代表这个程序没有错误。现在我们将程序的错误放大 我让每个线程都睡10毫秒。

package com.ghl.demo;

class Window implements Runnable {
	static int ticket = 100;

	@Override
	public void run() {
		while (true) {
			if (ticket > 0) {
				try {
					Thread.currentThread().sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "售票,售票号为:" + ticket--);
			} else {
				break;
			}
		}

	}
}

public class TestWindow {
	public static void main(String[] args) {
		Window w1 = new Window();
		Thread th = new Thread(w1);
		Thread th1 = new Thread(w1);
		Thread th2 = new Thread(w1);
		th.setName("窗口1");
		th1.setName("窗口2");
		th2.setName("窗口3");

		th.start();

		th1.start();
		th2.start();

	}

}
返回结果:
窗口1售票,售票号为:100
窗口2售票,售票号为:98
窗口3售票,售票号为:99
窗口1售票,售票号为:97
窗口2售票,售票号为:97
窗口3售票,售票号为:96
窗口2售票,售票号为:95
窗口1售票,售票号为:94
窗口3售票,售票号为:95
窗口2售票,售票号为:93
窗口3售票,售票号为:93
窗口1售票,售票号为:93
窗口2售票,售票号为:92
窗口1售票,售票号为:90
窗口3售票,售票号为:91
窗口3售票,售票号为:89
窗口1售票,售票号为:88
窗口2售票,售票号为:87
窗口3售票,售票号为:86
窗口1售票,售票号为:86
窗口2售票,售票号为:85
窗口3售票,售票号为:84
窗口1售票,售票号为:84
窗口2售票,售票号为:83
窗口1售票,售票号为:82
窗口3售票,售票号为:82
窗口2售票,售票号为:82
窗口3售票,售票号为:81
窗口2售票,售票号为:81
窗口1售票,售票号为:80
窗口2售票,售票号为:79
窗口3售票,售票号为:78
窗口1售票,售票号为:79
窗口2售票,售票号为:77
窗口3售票,售票号为:76
窗口1售票,售票号为:75
窗口3售票,售票号为:74
窗口1售票,售票号为:73
窗口2售票,售票号为:72
窗口3售票,售票号为:71
窗口1售票,售票号为:71
窗口2售票,售票号为:71
窗口1售票,售票号为:70
窗口3售票,售票号为:68
窗口2售票,售票号为:69
窗口3售票,售票号为:67
窗口2售票,售票号为:67
窗口1售票,售票号为:67
窗口2售票,售票号为:66
窗口3售票,售票号为:65
窗口1售票,售票号为:64
窗口2售票,售票号为:63
窗口3售票,售票号为:61
窗口1售票,售票号为:62
窗口2售票,售票号为:60
窗口3售票,售票号为:59
窗口1售票,售票号为:58
窗口2售票,售票号为:57
窗口3售票,售票号为:56
窗口1售票,售票号为:55
窗口2售票,售票号为:54
窗口3售票,售票号为:53
窗口1售票,售票号为:52
窗口2售票,售票号为:51
窗口3售票,售票号为:50
窗口1售票,售票号为:49
窗口2售票,售票号为:48
窗口3售票,售票号为:47
窗口1售票,售票号为:46
窗口2售票,售票号为:45
窗口3售票,售票号为:44
窗口1售票,售票号为:43
窗口2售票,售票号为:42
窗口3售票,售票号为:41
窗口1售票,售票号为:40
窗口2售票,售票号为:39
窗口3售票,售票号为:38
窗口1售票,售票号为:37
窗口2售票,售票号为:36
窗口3售票,售票号为:35
窗口1售票,售票号为:34
窗口2售票,售票号为:33
窗口3售票,售票号为:32
窗口1售票,售票号为:31
窗口2售票,售票号为:30
窗口3售票,售票号为:29
窗口2售票,售票号为:28
窗口1售票,售票号为:27
窗口3售票,售票号为:26
窗口2售票,售票号为:25
窗口1售票,售票号为:24
窗口3售票,售票号为:23
窗口1售票,售票号为:22
窗口2售票,售票号为:22
窗口3售票,售票号为:21
窗口1售票,售票号为:20
窗口2售票,售票号为:19
窗口3售票,售票号为:18
窗口2售票,售票号为:17
窗口1售票,售票号为:17
窗口3售票,售票号为:16
窗口2售票,售票号为:15
窗口1售票,售票号为:15
窗口3售票,售票号为:14
窗口2售票,售票号为:13
窗口1售票,售票号为:12
窗口3售票,售票号为:11
窗口2售票,售票号为:9
窗口1售票,售票号为:10
窗口3售票,售票号为:8
窗口2售票,售票号为:7
窗口1售票,售票号为:7
窗口3售票,售票号为:6
窗口2售票,售票号为:4
窗口1售票,售票号为:5
窗口3售票,售票号为:3
窗口1售票,售票号为:2
窗口2售票,售票号为:2
窗口3售票,售票号为:1
窗口2售票,售票号为:0
窗口1售票,售票号为:-1

从上面的结果我们可以看出,即有重复的票也有负数的票。上面的问题存在了线程安全的问题。

线程安全问题出现的原因

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

java如何解决线程安全问题(两种方式)

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。类似于上厕所,一个一个来,排队

格式:
方式一、使用同步代码块(synchronized )来解决

  1. synchronized (同步监视器){
    // 需要被同步的代码;
    }
    1 共享数据:多个线程共同操作同一个数据。
    2)同步监视器:由一个类的对象来充当,哪个类获得此监视器,哪个类就只执行大括号内部的代码,也叫锁。
package com.ghl.demo;

class Window implements Runnable {
	static int ticket = 100;
Object obj=new Object();//锁 也就是一个对象
	public void run() {
		while (true) {
			synchronized (this) {//synchronized用在需要共享数据的地方,如果不是继承的可以this代表当前对象,也可以自定义对象来充当锁
			//synchronized (obj)
				if (ticket > 0) {
					try {
						Thread.currentThread().sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "售票,售票号为:" + ticket--);
				} else {
					break;
				}
			}

		}

	}
}

public class TestWindow {
	public static void main(String[] args) {
		Window w1 = new Window();
		Thread th = new Thread(w1);
		Thread th1 = new Thread(w1);
		Thread th2 = new Thread(w1);
		th.setName("窗口1");
		th1.setName("窗口2");
		th2.setName("窗口3");

		th.start();

		th1.start();
		th2.start();

	}

}

返回结果

窗口1售票,售票号为:100
窗口2售票,售票号为:99
窗口2售票,售票号为:98
窗口2售票,售票号为:97
窗口2售票,售票号为:96
窗口2售票,售票号为:95
窗口2售票,售票号为:94
窗口2售票,售票号为:93
窗口2售票,售票号为:92
窗口2售票,售票号为:91
窗口2售票,售票号为:90
窗口2售票,售票号为:89
窗口2售票,售票号为:88
窗口2售票,售票号为:87
窗口2售票,售票号为:86
窗口2售票,售票号为:85
窗口2售票,售票号为:84
窗口2售票,售票号为:83
窗口2售票,售票号为:82
窗口2售票,售票号为:81
窗口2售票,售票号为:80
窗口2售票,售票号为:79
窗口2售票,售票号为:78
窗口2售票,售票号为:77
窗口2售票,售票号为:76
窗口2售票,售票号为:75
窗口2售票,售票号为:74
窗口2售票,售票号为:73
窗口2售票,售票号为:72
窗口2售票,售票号为:71
窗口2售票,售票号为:70
窗口2售票,售票号为:69
窗口2售票,售票号为:68
窗口2售票,售票号为:67
窗口2售票,售票号为:66
窗口2售票,售票号为:65
窗口2售票,售票号为:64
窗口2售票,售票号为:63
窗口2售票,售票号为:62
窗口2售票,售票号为:61
窗口2售票,售票号为:60
窗口2售票,售票号为:59
窗口2售票,售票号为:58
窗口2售票,售票号为:57
窗口2售票,售票号为:56
窗口2售票,售票号为:55
窗口2售票,售票号为:54
窗口2售票,售票号为:53
窗口2售票,售票号为:52
窗口2售票,售票号为:51
窗口2售票,售票号为:50
窗口2售票,售票号为:49
窗口2售票,售票号为:48
窗口2售票,售票号为:47
窗口2售票,售票号为:46
窗口2售票,售票号为:45
窗口2售票,售票号为:44
窗口2售票,售票号为:43
窗口2售票,售票号为:42
窗口2售票,售票号为:41
窗口2售票,售票号为:40
窗口2售票,售票号为:39
窗口2售票,售票号为:38
窗口2售票,售票号为:37
窗口2售票,售票号为:36
窗口2售票,售票号为:35
窗口2售票,售票号为:34
窗口2售票,售票号为:33
窗口2售票,售票号为:32
窗口2售票,售票号为:31
窗口2售票,售票号为:30
窗口2售票,售票号为:29
窗口2售票,售票号为:28
窗口2售票,售票号为:27
窗口2售票,售票号为:26
窗口2售票,售票号为:25
窗口2售票,售票号为:24
窗口2售票,售票号为:23
窗口2售票,售票号为:22
窗口2售票,售票号为:21
窗口2售票,售票号为:20
窗口2售票,售票号为:19
窗口2售票,售票号为:18
窗口2售票,售票号为:17
窗口2售票,售票号为:16
窗口2售票,售票号为:15
窗口2售票,售票号为:14
窗口2售票,售票号为:13
窗口2售票,售票号为:12
窗口2售票,售票号为:11
窗口2售票,售票号为:10
窗口2售票,售票号为:9
窗口2售票,售票号为:8
窗口2售票,售票号为:7
窗口2售票,售票号为:6
窗口2售票,售票号为:5
窗口2售票,售票号为:4
窗口2售票,售票号为:3
窗口2售票,售票号为:2
窗口2售票,售票号为:1

方式二、同步方法 跟上面的基本一样 这里就不做重复了
2. synchronized还可以放在方法声明中,表示整个方法为同步方法。
public synchronized void show (String name){
需要同步数据的代码
}

释放锁的操作

1)当前线程的同步方法、同步代码块执行结束
2)当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
3)当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
4)当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

你可能感兴趣的:(Java基础)