处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
线程同步会用到队列和锁
同步方法由于我们可以用过private关键字保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块
//同步方法
public synchronized void method(int args){}
synchronized方法控制对"对象"的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized将会影响效率
//synchronized方法默认锁的是this对象,有些时候,this方法运行run对象,但是要同步的数据并不是this对象,就需要使用同步块
同步块synchronized(Obj){}
Obj称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class【反射中讲解】
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
//正确的方法(以买票为例)
public class TestThread1 implements Runnable{
int ticket=10;
boolean flag=true;
public synchronized void buyTicket() throws InterruptedException {
if(ticket<=0){flag=false;return;}
Thread.sleep(300);
System.out.println(Thread.currentThread().getName()+"拿到了"+ticket--+"张票");
}
@Override
public void run() {
while(flag){
try {
buyTicket();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TestThread1 ts=new TestThread1();
new Thread(ts,"我").start();
new Thread(ts,"你").start();
new Thread(ts,"他").start();
}
}
//两种错误的同步方法
//例子一
public class TestThread1 implements Runnable{
int ticket=10;
@Override
public synchronized void run() {
while(true){
if(ticket<=0)break;
System.out.println(Thread.currentThread().getName()+"拿到了"+ticket--+"张票");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TestThread1 ts=new TestThread1();
new Thread(ts).start();
new Thread(ts).start();
new Thread(ts).start();
}
}
//例子二,无法做到线程同步
public class TestThread2 {
public static void main(String[] args) {
Ticket ticket=new Ticket(10);
Thread_run me=new Thread_run(ticket);
Thread_run you=new Thread_run(ticket);
me.start();
you.start();
}
}
class Ticket{
int ticket;
Ticket(int x){this.ticket=x;}
}
class Thread_run extends Thread{
Ticket ticket;
Thread_run(Ticket ticket){this.ticket=ticket;}
@Override
public synchronized void run() {
while(true){
if(ticket.ticket<=0)break;
System.out.println(Thread.currentThread().getName()+"拿到了"+ticket.ticket--+"张票");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
原因:synchronized方法本质是给this对象加锁。
第一个例子中,加锁的对象是ts,然后将ts放到3个线程中,线程在运行方法时发现对象有锁,就会同步执行。
第二个例子中,枷锁的对象是me和you。但实际上需要同步访问的对象是ticket。程序在运行的过程中,发现对象me和you有锁,但是实际上并没有多个线程同时访问me和you,所以me和you的方法同步执行run方法。从而导致ticket对象出现并发问题
对于第二个例子的问题,需要用到同步块
//使用同步块
public class TestThread2 {
public static void main(String[] args) {
Ticket ticket=new Ticket(10);
Thread_run me=new Thread_run(ticket);
Thread_run you=new Thread_run(ticket);
me.start();
you.start();
}
}
class Ticket{
int ticket;
Ticket(int x){this.ticket=x;}
}
class Thread_run extends Thread{
Ticket ticket;
Thread_run(Ticket ticket){this.ticket=ticket;}
@Override
public synchronized void run() {
while(true){
synchronized (ticket){
if(ticket.ticket<=0)break;
System.out.println(Thread.currentThread().getName()+"拿到了"+ticket.ticket--+"张票");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}



