线程同步
当使用多个线程访问同一资源的时候,且多个线程中对这个资源都有写的操作,就容易出现线程安全问题。
线程不安全例子
//卖票例子
public class Tickets implements Runnable{
private int tickets = 10;
@Override
public void run() {
while (tickets > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "线程正在卖票,余票:" + tickets);
}
}
}
public class TicketsTest {
public static void main(String[] args) {
Tickets tickets = new Tickets();
new Thread(tickets).start();
new Thread(tickets).start();
new Thread(tickets).start();
//三个线程同时卖票
}
}
运行结果
Thread-0线程正在卖票,余票:7
Thread-1线程正在卖票,余票:8
Thread-2线程正在卖票,余票:9
Thread-0线程正在卖票,余票:5
Thread-1线程正在卖票,余票:6
Thread-2线程正在卖票,余票:4
Thread-2线程正在卖票,余票:2
Thread-0线程正在卖票,余票:1
Thread-1线程正在卖票,余票:3
Thread-1线程正在卖票,余票:0
Thread-0线程正在卖票,余票:-2
Thread-2线程正在卖票,余票:-1
出现的问题
- 票数余数不是依次递减的
- 出现票数余数为复数的情况
线程同步解决线程安全问题
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制 (synchronized)来解决。
完成同步操作的三种方式
- 同步代码块
- 同步方法
- 锁机制
同步代码快
- 同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实现互斥访问。
//格式
synchronized(同步锁) {
...
}
- 同步锁:对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。
- 锁对象可以是任意类型
- 多个线程对象要使用同一把锁
Tips:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能等待锁被释放,再次和其他线程抢夺锁对象
public class TicketsSynchronized implements Runnable{
private int tickets = 10;
Object object = new Object();
@Override
public void run() {
while (true) {
synchronized (object) {
if (tickets > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "线程正在卖票,余票:" + tickets);
}
}
}
}
}
public class TicketsSynchronizedTest {
public static void main(String[] args) {
TicketsSynchronized tickets = new TicketsSynchronized();
Thread t1 = new Thread(tickets);
Thread t2 = new Thread(tickets);
Thread t3 = new Thread(tickets);
t1.start();
t2.start();
t3.start();
}
}
//运行结果
Thread-0线程正在卖票,余票:9
Thread-0线程正在卖票,余票:8
Thread-1线程正在卖票,余票:7
Thread-2线程正在卖票,余票:6
Thread-1线程正在卖票,余票:5
Thread-0线程正在卖票,余票:4
Thread-1线程正在卖票,余票:3
Thread-2线程正在卖票,余票:2
Thread-1线程正在卖票,余票:1
Thread-1线程正在卖票,余票:0
同步方法
同步方法:使用synchronized修饰的方法就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
public synchronized void method(){
...
}
谁是同步锁?对于非static方法,同步锁就是this对于static方法,我们使用当前方法所在类的字节码对象(类名.class)作为同步锁
public class TicketsSynchronizedMethod implements Runnable{
private static int tickets = 10;
@Override
public void run() {
while (true) {
sellTickets();
}
}
public synchronized void sellTickets(){
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "线程正在卖票,余票:" + tickets);
}
}
}
同步方法中的锁对象
同步方法所在类的对象,也就是this
public void sellTickets(){
synchronized(this){
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "线程正在卖票,余票:" + tickets);
}
}
}
静态同步方法的锁对象
不可以是this,因为静态属性优先于对象存在
静态同步方法的锁对象是所在类的class属性既class文件对象
public static void sellTickets(){
synchronized(TicketSynchronizedMethod.class){
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "线程正在卖票,余票:" + tickets);
}
}
}
public class TicketsSynchronizedMethodTest {
public static void main(String[] args) {
TicketsSynchronizedMethod tickets = new TicketsSynchronizedMethod();
new Thread(tickets).start();
new Thread(tickets).start();
new Thread(tickets).start();
}
}
//运行结果
Thread-0线程正在卖票,余票:9
Thread-0线程正在卖票,余票:8
Thread-2线程正在卖票,余票:7
Thread-1线程正在卖票,余票:6
Thread-2线程正在卖票,余票:5
Thread-0线程正在卖票,余票:4
Thread-2线程正在卖票,余票:3
Thread-1线程正在卖票,余票:2
Thread-2线程正在卖票,余票:1
Thread-0线程正在卖票,余票:0
锁机制
java.util.concurrent.locks.Lock
机制提供了比synchronized
代码块和synchronized
方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock
都有,除此之外各个强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化。
public void lock()
:加同步锁public void unlock()
:释放同步锁
public class TicketsLock implements Runnable{
private int tickets = 10;
Lock lock = new ReentrantLock();
//创建锁对象
@Override
public void run() {
while (true) {
lock.lock();
//加锁
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "线程正在卖票,余票:" + tickets);
}
lock.unlock();
}
}
public class TicketsLockTest {
public static void main(String[] args) {
TicketsLock tickets = new TicketsLock();
new Thread(tickets).start();
new Thread(tickets).start();
new Thread(tickets).start();
}
}
//运行结果
Thread-1线程正在卖票,余票:9
Thread-1线程正在卖票,余票:8
Thread-0线程正在卖票,余票:7
Thread-2线程正在卖票,余票:6
Thread-1线程正在卖票,余票:5
Thread-0线程正在卖票,余票:4
Thread-2线程正在卖票,余票:3
Thread-1线程正在卖票,余票:2
Thread-0线程正在卖票,余票:1
Thread-2线程正在卖票,余票:0