多个线程去共享资源、一段代码可能会出现一些不能预料的问题。就比如模拟三个窗口同时售票
package com.smallchili.demo;
public class CreateThread {
public static void main(String[] args) {
ThreadDemo2 t = new ThreadDemo2();
Thread t1 = new Thread(t,"[窗口1]");//创建线程1实例
Thread t2 = new Thread(t,"[窗口2]");//创建线程2实例
Thread t3 = new Thread(t,"[窗口3]");//创建线程3实例
t1.start();//启动线程2
t2.start();//启动线程2
t3.start();//启动线程3
}
}
class ThreadDemo2 implements Runnable{
private int tickets = 10;
Object object = new Object();
@Override
public void run() {
while(tickets > 0){
tickets--;
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数="+tickets);
}
}
}
结果
[窗口2]卖了一张票,剩余票数=8
[窗口3]卖了一张票,剩余票数=7
[窗口1]卖了一张票,剩余票数=8
[窗口3]卖了一张票,剩余票数=5
[窗口3]卖了一张票,剩余票数=3
[窗口2]卖了一张票,剩余票数=6
[窗口3]卖了一张票,剩余票数=2
[窗口1]卖了一张票,剩余票数=4
[窗口3]卖了一张票,剩余票数=0
[窗口2]卖了一张票,剩余票数=1
结果中第一行就出现了问题,窗口1卖了一张票应该是剩余9张而不是八张,为什么会出现这种情况呢?因为线程2进入了run()方法执行了tickets–,tickets变为9,还没执行到输出,另一个线程又执行了tickets–,tickets变成了8。这时线程2执行到了输出拿到的值是8。
这是多个线程会出现的问题,java提供synchronized关键字来帮助处理多个线程同时访问资源出现的问题,简单来说synchronized关键字是用来上锁的,。保证同一时刻只能有一个线程执行该方法或者某段代码,这样就避免了多个线程同时对资源读写而产生的问题。
synchronized关键字的几种用法
synchronized关键字有四种实现方式
分为两大类,对象锁和类锁,两者的区别就是对象锁只是锁某个对象里的方法或代码块,该类的其他对象一样可以获得锁,而类锁是锁了整个类,即使不同对象,保证同一时刻而只能有一个对象一个线程获得锁。
synchronized对象锁有两种实现方法:
- 同步代码块锁
- 方法锁
1.同步代码块锁
同一时刻,只能有一个线程访问该代码块。
使用方式
...
Object lock = new Object();//创建一个对象
...
synchronized(lock){//需要传入一个对象
//里面放的是要锁住的代码
}
或者
...
synchronized(this){//需要传入一个对象
//里面放的是要锁住的代码
}
...
上面的模拟三个窗口售票加同步代码块锁
class ThreadDemo2 implements Runnable{
private int tickets = 10;
Object lock = new Object();
@Override
public void run() {
while(tickets > 0){
synchronized(lock){ //同步代码块锁
if(tickets > 0){
tickets--;
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数="+tickets);
}else{
System.out.println("票已售完..");
}
}
}
}
}
2.synchronized关键字加在普通方法上
方法锁和同步代码块锁都是对象锁,区别是方法锁锁了整个方法,而同步代码块可以只锁一小段代码,控制粒度不一样。
用法:
public synchronized void todoSomething() {
...
}
3.synchronized关键字加在静态方法上
synchronized关键字加在普通方法和加在静态方法上的含义是不同的,加在静态方法上是类锁。
用法:
public static synchronized void todoSomething() {
...
}
4.synchronized(类名.class)代码块
这种用法也是属于类锁。
用法:
public void todoSomething() {
...
synchronized(类名.class){
//代码块
}
...
}
拓展
阐述一下我的理解:
所谓的锁就是我假设为一个flag变量,在线程运行到该代码块时或先检查该锁是否被占有,如果没有被占用(flag=0,即锁是打开),那么该线程就可以进入去执行该段代码,并且占用该锁(把flag设为1,锁上),此时如果第二个线程执行到该代码,也会先判断当前锁的状态,发现锁是被占有的(flag=1,即锁是关闭的)就会等待,等待占有锁的线程释放锁。
java中通过synchronized关键字来上锁而已是传入一个对象参数,像synchronized(锁){代码块}这种形式,如果是
- synchronized(this){代码块}这种就是对象锁。
- 像 synchronized(类名.class){代码块}这种就是类锁
区别不难看出参数的差异,一个是传唯一的
看个简单的例子吧
package com.smallchili.demo;
public class SynchronizedDemo {
public static void main(String[] args) {
ThreadDemo t = new ThreadDemo();
//t1,t2共享一个ThreadDemo对象
Thread t1 = new Thread(t,"[线程1]");
Thread t2 = new Thread(t,"[线程2]");
t1.start();
t2.start();
}
}
class ThreadDemo implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"begin..");
System.out.println(Thread.currentThread().getName()+"end..");
}
}
简单创建了两个共享代码的线程,不加锁的情况可能出现以下情况
[线程2]begin…
[线程1]begin…
[线程1]end…
[线程2]end…
是不安全的。
加了对象锁
class ThreadDemo implements Runnable{
@Override
public void run() {
synchronized (this) {//使用当前对象作为锁
System.out.println(Thread.currentThread().getName()+"begin..");
System.out.println(Thread.currentThread().getName()+"end..");
}
}
}
保证了同一ThreadDemo 对象的线程不互相干预,加了锁不会出现上面的问题
[线程1]begin…
[线程1]end…
[线程2]begin…
[线程2]end…
如果是两个不同的ThreadDemo实例对象间呢,他们会不会互相干扰?
package com.smallchili.demo;
public class SynchronizedDemo {
public static void main(String[] args) {
//两个不同实例的ThreadDemo td1,td2
ThreadDemo td1 = new ThreadDemo();
ThreadDemo td2 = new ThreadDemo();
//td1的线程
Thread t1 = new Thread(td1,"[线程1]");
Thread t2 = new Thread(td1,"[线程2]");
//td2的线程
Thread t3 = new Thread(td2,"[线程3]");//创建线程3实例
Thread t4 = new Thread(td2,"[线程4]");//创建线程4实例
//启动
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class ThreadDemo implements Runnable{
@Override
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName()+"begin..");
System.out.println(Thread.currentThread().getName()+"end..");
}
}
}
结果是当然,如下线程1还没执行完线程3、4就中途插入,没有保证同一时刻一个线程访问该代码块。之所以出现这种情况是因为加的是对象锁,不同实例之间获取的锁是不同的。上面的代码他们synchronized关键字 传入的锁对象不一样,线程1,2的锁是synchronized (对象td1)。而3,4的锁是synchronized (对象td2)。即每个ThreadDemo对象都是不一样的锁。
也就是说他们的锁不一样而导致他们同一时刻可以并行执行。
[线程1]begin…
[线程3]begin…
[线程3]end…
[线程4]begin…
[线程4]end…
[线程1]end…
[线程2]begin…
[线程2]end…
这时可以改为用类锁,所谓的类锁,就是让不同对象都使用同一把锁
修改synchronized (this)为synchronized (ThreadDemo.class)
class ThreadDemo implements Runnable{
@Override
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName()+"begin..");
System.out.println(Thread.currentThread().getName()+"end..");
}
}
}
运行结果
[线程1]begin…
[线程1]end…
[线程4]begin…
[线程4]end…
[线程3]begin…
[线程3]end…
[线程2]begin…
[线程2]end…
不同对象之间使用同一把锁了,这就是类锁。
很容易懂synchronized (ThreadDemo.class)里的ThreadDemo.class是唯一的,一个类可以有很多个实例对象,但只有一个类对象(类名.class)。
按照这个理解,我大开脑洞,发现类锁也可以这样写:
class ThreadDemo implements Runnable{
private static String myLock = "I am Lock";//静态变量是所有对象共享的
@Override
public void run() {
synchronized (myLock) {//用静态字符串对象作为锁
System.out.println(Thread.currentThread().getName()+"begin..");
System.out.println(Thread.currentThread().getName()+"end..");
}
}
}
上面的myLock 是一个字符串对象。
还可以这样
synchronized ("我是类锁") {
System.out.println(Thread.currentThread().getName()+"begin..");
System.out.println(Thread.currentThread().getName()+"end..");
}
虽然可以这样写但是不建议,因为没见过这样写的,只是拿来理解而已。
同样还有synchronized关键字加在方法上和加在静态方法上的区别,就不详细讲了。加在普通方法上默认是使用this加锁属于对象锁,加在静态方法上,由于静态方法是类共享的所以锁也是一样的,属于类锁。
如果再深入猜想,锁也许是内存地址,不好说,就到这里,喜欢我可以给我转qqq1726581875
【完】