线程同步
产生原因:每一个线程轮番抢占资源
1、不同步问题
//多个线程同时处理数据,不同步
class MyThread implements Runnable{
private int ticket = 10;//总共是10张票
@Override
public void run() {
while (this.ticket > 0){//还有余票
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"还有"+this.ticket--+"张票");
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread,"线程1").start();
new Thread(myThread,"线程2").start();
new Thread(myThread,"线程3").start();
}
}
执行以上代码之后我们会发现,票数可能会出现负数,这种问题称为不同步操作
不同步操作的优点是:处理速度快,多个线程并发执行
同步虽然可以保证数据的完整性,但是执行速度会很慢
2、synchronized处理同步问题
使用synchronized关键字处理同步问题有两种模式:同步代码块、同步方法
I、同步代码块
//使用同步代码块,必须设置一个要锁定的对象,一般锁定当前对象:this
class MyThread implements Runnable{
private int ticket = 10;//总共是10张票
@Override
public void run() {//在方法中进行拦截
for(int i = 0; i < 10; i++){
//同一时刻,只允许一个线程进入代码块处理
synchronized (this){//为程序逻辑上锁
if(this.ticket > 0){
try {
Thread.sleep(100);//模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"还剩"+this.ticket--+"张票");
}
}
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread,"线程1");
Thread thread2 = new Thread(myThread,"线程2");
Thread thread3 = new Thread(myThread,"线程3");
// thread1.setPriority(Thread.MIN_PRIORITY);//为线程设置优先级
// thread2.setPriority(Thread.MAX_PRIORITY);
// thread3.setPriority(Thread.MAX_PRIORITY);
thread1.start();
thread2.start();
thread3.start();
}
}
II、同步方法
//同步方法
class MyThread implements Runnable{
private int ticket = 10;//总共是10张票
@Override
public void run() {
for(int i = 0; i < 10; i++){
this.sale();
}
}
public synchronized void sale(){
if(this.ticket > 0){
try {
Thread.sleep(100);//模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"还剩"+this.ticket--+"张票");
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread,"线程1");
Thread thread2 = new Thread(myThread,"线程2");
Thread thread3 = new Thread(myThread,"线程3");
thread1.start();
thread2.start();
thread3.start();
}
}
关于Synchronized的说明
/synchronized锁多对象
class MyThread extends Thread{
@Override
public void run() {
Sync sync = new Sync();
sync.test();
}
}
class Sync{
public synchronized void test(){
System.out.println("test方法开始,当前线程"+Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test方法结束,当前线程"+Thread.currentThread().getName());
}
}
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 4; i++){
Thread thread = new MyThread();
thread.start();
}
}
}
此时的synchronized并没有起到作用,三个线程同时运行test()方法
实际上,synchronized(this)以及非static的synchronized方法,只能防止多个线程同时执行同一个对象的同步代码段,也就是说synchronized锁住的是括号里的内容而不是代码。对于非static的synchronized方法,锁的就是对象本身this。
当synchronized锁住同一个对象后,别的线程如果也想拿到这个对象锁,就必须等待这个线程执行完成释放锁,这样才能达到线程同步的目的。即使两个不同的代码段,都要锁同一个对象,那么这两个代码段也不能在多线程环境下同时运行
如果真要锁住这段代码,可以有以下两种方法
I、锁同一个对象
//synchronized锁同一个对象
class MyThread extends Thread{
private Sync sync;
public MyThread(Sync sync){
this.sync = sync;
}
@Override
public void run() {
sync.test();
}
}
class Sync{
public void test(){
synchronized(this){
System.out.println("test方法开始,当前线程"+Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test方法结束,当前线程"+Thread.currentThread().getName());
}
}
}
public class Test {
public static void main(String[] args) {
Sync sync = new Sync();
for (int i = 0; i < 4; i++){
Thread thread = new MyThread(sync);
thread.start();
}
}
}
II、使用synchronized(Sync.class)实现了全局锁的效果
//synchronized锁这个类对应的class对象
class MyThread extends Thread{
@Override
public void run() {
Sync sync = new Sync();
sync.test();
}
}
class Sync{
public void test(){
synchronized(Sync.class){
System.out.println("test方法开始,当前线程"+Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test方法结束,当前线程"+Thread.currentThread().getName());
}
}
}
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 3; i++){
Thread thread = new MyThread();
thread.start();
}
}
}
static synchronized()方法,static方法可以直接通过类名加方法名调用,方法中无法使用this,所有他锁的不是this而是类的class对象。
所以,static synchronized方法也相当于全局锁,相当于锁住了代码段
3、JDK1.5提供的Lock锁
使用ReentrantLock进行同步处理
class MyThread implements Runnable {
private int ticket = 50;
private Lock ticketLock = new ReentrantLock() ;
@Override
public void run() {
for (int i = 0; i < 50; i++) {
ticketLock.lock();
try {
if (this.ticket > 0) { // 还有票
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
} // 模拟网络延迟
System.out.println(Thread.currentThread().getName() + ",还有" + this.ticket--+" 张票");
}
} finally {
ticketLock.unlock();
}
}
}
}
public class Test{
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread,"线程1");
Thread thread2 = new Thread(myThread,"线程2");
Thread thread3 = new Thread(myThread,"线程3");
thread1.start();
thread2.start();
thread3.start();
}
}
在JDK1.5中,synchronized的性能是比较低效的。因为他是一个重量级操作,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。
相比之下,使用Java提供的Lock对象,性能会更高一点。