指的是多个线程同时修改一个过程时,可能导致的问题
引入:
以卖票问题为例:假设总共有十张票,三个黄牛卖票(代表三个子线程),预测最后一个黄牛卖完票,还剩下0张票
package se.SE.practice;
class MyThread implements Runnable {
private int ticket=10;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (this.ticket > 0) {
try {
// 模拟网络延迟
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "还剩下:" + this.ticket-- + "票");
}
}
}
}
public class Thread1{
public static void main(String[] args) {
MyThread mt=new MyThread();
Thread thread1=new Thread(mt,"黄牛1");
Thread thread2=new Thread(mt,"黄牛2");
Thread thread3=new Thread(mt,"黄牛3");
thread1.start();
thread2.start();
thread3.start();
}
}
执行结果:
我们发现最后一个黄牛卖票还剩下1张票,和预期不符
为什么会出现这个问题呢?是因为在多线程并发执行的时候,发生了同步的问题,也就是多个线程同时访问同一块资源(ticket)
解决思路:
在黄牛线程访问ticket时,不允许有其他线程访问ticket
引入:synchronized关键字
Object someObject =new Object();
synchronized (someObject){
//此处的代码只有占有了someObject后才可以执行
}
卖完票才解锁:在for循环后加synchronized(this){}
也就是在任何一个时刻内只有一个线程在卖票,
三个线程(黄牛1,黄牛2,黄牛3):可以同时进入for循环和run()但是不可同时进入synchroniazed锁住的代码块
public void run() {
for (int i = 0; i < 100; i++) {
synchronized (this) {
//---------------------------
if (this.ticket > 0) {
try {
// 模拟网络延迟
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "还剩下:" + this.ticket-- + "票");
}
}
//-------------------------------------------------
}
}
1同步问题:
a.同步代码块
在方法中使用synchronized对象,一般可以锁定当前this
表示同一时刻只有一个线程可以进入同步代码块,但是多个线程可以同时进入方法
b.同步方法
在方法声明中加入synchronized,表示此时只有一个线程可以进入方法
2synchronized对象锁
观察以下代码:
1定义一个synchronized方法,锁住当前对象this
2第一次new了一个Syn对象syn1,记线程0,那么线程0
线程1启动创建Syn对象syn2,各自锁各自的对象
package se.SE.practice;
class Syn{
public synchronized void test(int ticket){
System.out.println(Thread.currentThread().getName()+"此方法开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"此方法结束");
}
}
class MyThread implements Runnable {
private int ticket = 10;
@Override
public void run() {
Syn syn=new Syn();
syn.test(ticket);
}
}
public class Thread1{
public static void main(String[] args) {
MyThread myThread=new MyThread();
Thread thread0=new Thread(myThread,"子线程0");
Thread thread1=new Thread(myThread,"子线程1");
Thread thread2=new Thread(myThread,"子线程2");
thread0.start();
thread1.start();
thread2.start();
/*for(int i=0;i<3;i++){
new Thread(myThread,"线程"+i).start();
}
*/
}
}
结果:
总结:用synchronized(this)以及synchronized()只能防止多个线程执行同一个对象的同步段
synchronized锁的是括号中的对象而非代码
那么怎样才可以使得可以锁住一个对象呢,关键点在MyThread类实现Runnable 接口中只有一个对象syn
1定义Syn类对象syn
2通过有参构造函数实现对MyThread类对象个数的限制
package se.SE.practice;
class Syn{
public synchronized void test(int ticket){
System.out.println(Thread.currentThread().getName()+"此方法开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"此方法结束");
}
}
class MyThread implements Runnable {
private int ticket = 10;
private Syn syn;
public MyThread(Syn syn){
this.syn=syn;
}
@Override
public void run() {
this.syn.test(ticket);
}
}
public class Thread1{
public static void main(String[] args) {
Syn syn=new Syn();
MyThread myThread=new MyThread(syn);
for(int i=0;i<3;i++){
new Thread(myThread,"线程"+i).start();
}
}
}
输出结果:
达到预期结果!
引入全局锁,从sychronized(this)或者synchronized方法只可以锁住当前对象,锁住代码段需要全局锁
3全局锁
锁代码段
1使用类的静态方法,此时锁住的是当前使用的类,而非对象
也就是把public synchronized void test(int ticket){}方法改为静态方法--->public static synchronized void test(int ticket){}
把类方法锁住--就把代码块锁住
package se.SE.practice;
class Syn{
public synchronized void test(int ticket){
System.out.println(Thread.currentThread().getName()+"此方法开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"此方法结束");
}
}
class MyThread implements Runnable {
private int ticket = 10;
private Syn syn;
@Override
public void run() {
this.syn.test(ticket);
}
}
public class Thread1{
public static void main(String[] args) {
MyThread myThread=new MyThread();
for(int i=0;i<3;i++){
new Thread(myThread,"线程"+i).start();
}
}
}
2在同步代码块中,锁当前的class对象,类名称.class
如果是锁普通方法:将普通方法的代码段放在synchronized(Syn.class){}包含的代码段中