何为线程同步?
线程同步是为了确保线程安全,所谓线程安全指的是多个线程对共享资源进行访问时,有可能产生数据不一致问题,导致线程访问的资源并不是安全的。(如果多线程程序运行结果和单线程运行的结果是一样的,且相关变量的值与预期值一样,则是线程安全的。)
线程同步的两种类型:互斥和线程间通信
1)互斥
互斥有助于防止线程在共享数据时相互干扰。在java中,这可以通过三种方式实现
1)同步方法 Synchronized method
2)同步代码块 Synchronized block
3)静态同步 static synchronization
锁的概念
同步是围绕称为锁或监视器的内部实体构建的。每个对象都有与之关联的锁。按照约定,需要对对象的字段进行一致访问的线程必须在访问对象之前获取对象的锁,然后在完成对它们的操作后释放该锁。
多线程访问共享数据而产生的不一致问题:
//不安全地买票
//线程不安全,有负数
public class BuyTicketUnsafely {
public static void main(String[] args){
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"我").start();
new Thread(buyTicket,"你").start();
new Thread(buyTicket,"他").start();
}
}
class BuyTicket implements Runnable{
//票数
private int ticketNums = 5;
//判断线程停止的标志位
private boolean flag = true;
@Override
public void run() {
while (flag){
buy();
}
}
private void buy(){
if(ticketNums <= 0){
flag = false;
return;
}
//买票
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "拿到了第"
+ ticketNums-- + "张票");
}
}
output:
我拿到了第3张票
他拿到了第4张票
你拿到了第5张票
你拿到了第2张票
他拿到了第1张票
我拿到了第0张票
你拿到了第-1张票
synchronized 修饰方法
同步方法锁定的是任意共享资源的对象
当线程调用同步方法时,它将自动获取该对象的锁,并在线程完成其任务时释放该锁
//将上述的buy()方法改为同步方法
//synchronized 同步方法 锁的是this
private synchronized void buy(){
if(ticketNums <= 0){
flag = false;
return;
}
//买票
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "拿到了第"
+ ticketNums-- + "张票");
}
output:
我拿到了第5张票
我拿到了第4张票
他拿到了第3张票
你拿到了第2张票
你拿到了第1张票
Synchronized 修饰代码块
同步代码块可以用于在方法的任何特定资源上执行同步。假设你的方法中有50行代码,但是你只想同步5行,你可以使用同步代码块。如果你把这个方法的所有代码都放在synchronized块中,它将和synchronized方法一样工作。
- 同步块用于锁定任意共享资源的对象。
- 同步块范围小于方法
示例:
public class GetMoneyUnsafely {
public static void main(String[] args){
Account account = new Account("学习基金",1000);
Drawing you = new Drawing(account,200,"你");
Drawing me = new Drawing(account,300,"我");
you.start();
me.start();
}
}
//账号
class Account{
String name;
int money;
Account(String name,int money){
this.name = name;
this.money = money;
}
getter() and setter();
}
//银行,模拟取款
class Drawing extends Thread{
Account account;
int drawingMoney;
int nowMoney;
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
//锁的是需要变化的对象
synchronized (account){
//判断有没有钱
if(account.money-drawingMoney < 0){
System.out.println(Thread.currentThread().getName()+ " 钱不够了,取不了");
return;
}
//sleep 可以放大问题的发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 你取的钱
account.money = account.money - drawingMoney;
//你手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(this.getName() + " 手里的钱 " + nowMoney);
System.out.println(account.name + " 的余额为:" + account.money);
}
}
}
Synchronized 修饰静态方法
如果你使用Synchronized修饰任何的静态方法,那么锁的是类而不是对象。
该锁针对的是类,无论实例出多少个对象,那么线程依然共享该锁。
static synchronized是限制多线程中该类的所有实例同时访问该类所对应的代码块。
示例:
class Table{
synchronized static void printTable(int n){
for(int i=1;i<=5;i++){
System.out.println(n*i);
try{
Thread.sleep(400);
}catch(Exception e){System.out.println(e);}
}
}
}
class MyThread1 extends Thread{
Table t;
MyThread1(Table t){
this.t=t;
}
public void run(){
t.printTable(5);
}
}
class MyThread2 extends Thread{
Table t;
MyThread2(Table t){
this.t=t;
}
public void run(){
t.printTable(100);
}
}
public class SynchronizedTest {
public static void main(String[] args) {
Table obj = new Table();
Table obj1 = new Table();
MyThread1 t1=new MyThread1(obj);
MyThread2 t2=new MyThread2(obj1);
t1.start();
t2.start();
}
}
output:
5
10
15
20
25
100
200
300
400
500
线程死锁
Java中的死锁是多线程的一部分。 当线程正在等待被另一个线程获取的对象锁而第二个线程正在等待由第一个线程获取的对象锁时,可能会发生死锁。 由于两个线程都在互相等待对方释放锁,形成了僵持的状态,因此这种情况称为死锁
示例:
public class DeadlockTest {
public static void main(String[] args) {
final String resource1 = "resource1";
final String resource2 = "resource2";
// t1 tries to lock resource1 then resource2
Thread t1 = new Thread() {
public void run() {
synchronized (resource1) {
System.out.println("Thread 1: locked resource 1");
try { Thread.sleep(100);} catch (Exception e) {}
synchronized (resource2) {
System.out.println("Thread 1: locked resource 2");
}
}
}
};
// t2 tries to lock resource2 then resource1
Thread t2 = new Thread() {
public void run() {
synchronized (resource2) {
System.out.println("Thread 2: locked resource 2");
try { Thread.sleep(100);} catch (Exception e) {}
synchronized (resource1) {
System.out.println("Thread 2: locked resource 1");
}
}
}
};
t1.start();
t2.start();
}
}
output:
Thread 1: locked resource 1
Thread 2: locked resource 2
(程序未结束,但也没其他输出)
2)线程间的通信(等待/通知机制)
指一个线程A调用了对象O的wait()方法进入线程等待状态,而另一个线程B调用了对象O的notify()/notifyAll()方法,线程A收到通知后退出等待队列,进入可运行状态,进而执行后续操作。上诉两个线程通过对象O来完成交互,而对象上的wait()方法和notify()/notifyAll()方法的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
wait()方法
使当前线程释放锁并等待,直到另一个线程为这个对象调用notify()方法或notifyAll()方法,或者指定的时间已经过去。当前线程必须拥有这个对象的监视器,因此必须从synchronized方法调用它,否则它将抛出异常。
//等待直到对象的唤醒 public final void wait()throws InterruptedException //等待指定的时间 public final void wait(long timeout)throws InterruptedException
notify()方法
唤醒一个在此对象的监视器上等待的线程。如果不止一个线程正在等待这个对象,则选择其中一个被唤醒。这种选择是任意的。
public final void notify()
notifyAll()方法
使所有正在等待队列中等待同一共享资源的 “全部线程” 退出等待队列,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现。
public final void notifyAll()
wait() 与 sleep() 的不同
wait() | sleep() |
wait() 方法释放锁 | sleep() 方法不会释放锁 |
属于Object类 | 属于Thread类 |
是非静态方法 | 静态方法 |
需要被notify() 或 notifyAll() 唤醒 | 经过指定时间后休眠会完成 |
示例:
/*
* 编写两个线程,一个线程打印1~25,另一个线程打印字母A~Z,打印顺序为12A34B56C……5152Z,要求使用线程间的通信。*/
public class WaitAndNotifyTest {
public int flag = 0;
public final Object o = new Object();
class Thread1 implements Runnable{
@Override
public void run() {
for (int i = 1; i < 52; i = i + 2) {
synchronized (o) {
if(flag == 1) {
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(i);
System.out.print(i + 1);
flag = 1;
o.notify();
}
}
}
}
class Thread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 26; i++) {
synchronized (o) {
if(flag == 0) {
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(String.valueOf ((char)(i+65)));
flag = 0;
o.notify();
}
}
}
}
public static void main(String[] args) {
WaitAndNotifyTest waitAndNotifyTest = new WaitAndNotifyTest();
WaitAndNotifyTest.Thread1 thread1 = waitAndNotifyTest.new Thread1();
WaitAndNotifyTest.Thread2 thread2 = waitAndNotifyTest.new Thread2();
Thread thread3 = new Thread(thread1);
Thread thread4 = new Thread(thread2);
thread3.start();
thread4.start();
}
}