synchronized是Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。
当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍可以访问该object中的非加锁代码块。
synchronized实现原理Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法,锁是当前实例对象静态同步方法,锁是当前类的class对象同步方法块,锁是括号里面的对象当一个线程访问同步代码块时,它首先是需要得到锁,当退出或者抛出异常时必须要释放锁
synchronized卖票的代码:
package com.fan.sync;
//第一步,创建资源类,定义属性和操作方法
class Ticket{
//属性:公共资源
private int number = 30;
//操作方法
synchronized void sale(){
//判断:是否有票
if(number > 0){
System.out.println(Thread.currentThread().getName()+
':卖出:'+(number--)+'剩下:'+number );
}
}
}
//主启动类,主线程类
public class SaleTicket {
//创建多个线程,调用资源类的操作方法
public static void main(String[] args) {
//创建Ticket对象
Ticket ticket = new Ticket();
//创建三个线程
new Thread(new Runnable() {
@Override
public void run() {
//调用要线程干的事情:卖票
for (int i = 0; i < 40; i++) {//循环调用
ticket.sale();
}
}
}, 'aa').start();
new Thread(new Runnable() {
@Override
public void run() {
//调用要线程干的事情:卖票
for (int i = 0; i < 40; i++) {//循环调用
ticket.sale();
}
}
}, 'bb').start();
new Thread(new Runnable() {
@Override
public void run() {
//调用要线程干的事情:卖票
for (int i = 0; i < 40; i++) {//循环调用
ticket.sale();
}
}
}, 'cc').start();
}
}
说明:sync的上锁和解锁是自动完成的,而lock是可以手动上锁和解锁
Lock:
区别如下:
总结来说,Lock和synchronized有以下几点不同:
Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时,此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。lock示例代码:
start方法源码解析:start()之后的代码的核心就是其中调用的start0()方法,下面看看start0()的定义:privatenativevoidstart0();很明显这是一个只声明而未实现的方法,native表示调用本机的原⽣操作系统函数,native方法往往意味着这个方法无法使用平台无关的手段来实现。还是那句话,实际上线程的实现与Java无关,由平台所决定,Java所做的是将Thread对象映射到操作系统所提供的线程上面去,对外提供统一的操作接口,向程序员隐藏了底层的细节,使程序员感觉在哪个平台上编写的有关于线程的代码都是一样的。这也是Java这门语言诞生之初的核心思想,一处编译,到处运行,只面向虚拟机,实现所谓的平台无关性,而这个平台无关性就是由虚拟机为我们提供的。
总结一下: start()方法的具体流程:start->调用start0()方法->通过JVM进行资源调度,系统分配->回调run()方法执行线程的具体操作任务。 由于start()方法调用了JVM进行系统调度、系统分配等一系列操作,因此创建一个线程只能由start()来完成,而若直接调用run()方法,相当于是在调用一个普通方法。
使用可重入锁实现卖票:
可重入锁:
可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁。可重入锁示例(同一个线程可以重入上锁的代码段,不同的线程则需要进行阻塞)java的可重入锁有:ReentrantLock、synchronized
可重入锁诞生的目的就是防止上面不可重入锁的那种情况,导致同一个线程不可重入上锁代码段。
目的就是让同一个线程可以重新进入上锁代码段。
卖票代码:
package com.fan.lock;
import java.util.concurrent.locks.ReentrantLock;
//多线程编程第一步:资源类,定义属性和操作方法
class LTicket{
//票数量
private int number = 30;
//创建可重入锁对象,final定义的对象不能再重新指向其他对象了
private final ReentrantLock lock =new ReentrantLock();
//卖票的方法
public void sale(){
//上锁
lock.lock();
try {
//判断是否有票
if(number > 0){
System.out.println(Thread.currentThread().getName()+
'-卖出:'+(number-- )+'剩余:'+number);
}
}finally {
lock.unlock();//解锁
}
}
}
public class LSaleTicket {
//多线程第二步:创建多个线程,调用资源类的操作方法
public static void main(String[] args) {
//在主线程内,创建三个子线程
LTicket lTicket = new LTicket();//创建同一个资源对象
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
lTicket.sale();//调用资源类的卖票方法
}
}
}, 'aa').start();
//
new Thread(()->{
for (int i = 0; i < 40; i++) {
lTicket.sale();//调用资源类的卖票方法
}
},'bb').start();
//
new Thread(()->{
for (int i = 0; i < 40; i++) {
lTicket.sale();
}
},'cc').start();
}
}
文章为作者独立观点,不代表股票交易接口观点