一.简介
在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,目前实现线程同步的方法有很多,临界区对象就是其中一种。
二.线程同步的方式和机制
临界区、互斥量、事件、信号量四种方式
临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的区别:
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源 进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进 入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。
2、互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不 会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享。
3、信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。
4、事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作。
三.synchronized
图例说明:因为线程t1和t2共用同一个对象timer,因此当两个线程同时访问的时候,就会因为不同线程对num的操作,造成不同步。
示例代码:
public class TestSync implements Runnable{ Timer timer = new Timer(); public static void main(String[] args) { TestSync test = new TestSync(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); t1.setName("线程1"); t2.setName("线程2"); t1.start(); t2.start(); } public void run() { timer.add(Thread.currentThread().getName()); } } class Timer { private static int num = 0; public void add(String name){ //public synchronized void add(String name){ //①表示执行当前方法过程中,锁定当前对象,是当前对象被锁定了 //synchronized(this){ //②表示锁定某一块东西 num++; try{ Thread.sleep(1); }catch(InterruptedException e){ } System.out.println(name+",你是第"+num+"个使用Timer的线程"); //} } }
此时结果是:
线程1,你是第2个使用Timer的线程
线程2,你是第2个使用Timer的线程
此时,可以用关键字synchronized通过线程的互斥来实现同步,
(提供了两种方法:①方法前加上synchronized;②synchronized(this){}):
放开注释的其中之一,此时结果是:
线程1,你是第1个使用Timer的线程
线程2,你是第2个使用Timer的线程
四.死锁
所谓死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
所以使用了synchronized关键字后,当几个线程共同竞争资源的时候就会产生死锁。如下例:
public class TestDeadLock implements Runnable{ public int flag = 0; static Object obj1 = new Object(); static Object obj2 = new Object(); public void setFlag(int flag){ this.flag = flag; } public static void main(String[] args) { TestDeadLock tdl1 = new TestDeadLock(); TestDeadLock tdl2 = new TestDeadLock(); tdl1.setFlag(0); tdl1.setFlag(1); Thread thread1 = new Thread(tdl1); Thread thread2 = new Thread(tdl2); thread1.start(); thread2.start(); } public void run() { System.out.println("flag="+flag); if(flag == 1){ synchronized(obj1){ //先申请资源1 try{ Thread.sleep(500); }catch(InterruptedException e){ e.printStackTrace(); } synchronized(obj2){ //后申请资源2 System.out.println("情况"+flag+"的资源申请完毕"); } } }else if(flag ==0){ synchronized(obj2){ //先申请资源2 try{ Thread.sleep(500); }catch(InterruptedException e){ e.printStackTrace(); } synchronized(obj1){ //后申请资源1 System.out.println("情况"+flag+"的资源申请完毕"); } } } } }
五.synchronized作用域
synchronized只会对使用了synchronized关键字的对象加锁,这样当被加锁对象正在访问时,另外的线程要对其进行访问才会互斥,未被加锁的对象不会产生互斥,即使都会用到同样的资源。因此要实现对某资源的同步就必须要把用到此资源的对象都加上synchronized关键字。如下例:
public class TestLockConcept implements Runnable{ int b = 100; //比如这是多处会用到的资源 public synchronized void m1() throws InterruptedException { b = 1000; Thread.sleep(2000); System.out.println("m1:b = " + b); } // public void m2() { // System.out.println("m2:b = " + b); //此时m2未加锁,m1加了锁,因此可以直接访问m2 // //结果: m2:b = 1000 m1:b = 1000 final:b = 1000 // } // public void m2() { // b = 2000; //此时m2未加锁,m1加了锁,也可以直接修改b和访问m2 // System.out.println("m2:b = " + b); // //结果: m2:b = 2000 m1:b = 2000 final:b = 2000 // } public synchronized void m2() throws InterruptedException { b = 2000; //此时m2加锁,m1也加了锁,不可以直接修改b和访问m2 System.out.println("m2:b = " + b); //结果: m1:b = 1000 m2:b = 2000 final:b = 2000 } public void run() { try{ m1(); }catch(InterruptedException e){ e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { TestLockConcept tl = new TestLockConcept(); Thread t = new Thread(tl); t.start(); Thread.sleep(1000); tl.m2(); Thread.sleep(2000); System.out.println("final:b = " + tl.b); } }
六.生产者和消费者
实例:
public class ProductConsumer { public static void main(String[] args) { SyncStack ss = new SyncStack(); Product p = new Product(ss); Consumer c = new Consumer(ss); //一个人每天生产40个馒头供四个人吃,每人吃十个。 new Thread(p).start(); new Thread(c).start(); new Thread(c).start(); new Thread(c).start(); new Thread(c).start(); } } class WoTou { //窝头即是资源,有人生产窝头,有人需要窝头。 int id; WoTou(int id){ this.id = id; } public String toString(){ return "wotou:" + id; } } class SyncStack { //装窝头的篮子,最多只能装6个,提供了往里放窝头以及从里面拿窝头的方法。 int index = 0; WoTou[] arrWT = new WoTou[6]; public synchronized void push(WoTou wt){ //往篮子里放窝头 while(index == arrWT.length){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.notifyAll(); arrWT[index] = wt; index++; System.out.println("生产了:" + wt); } public synchronized WoTou pop(){ //从篮子里面拿窝头 while(index == 0){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.notifyAll(); index--; WoTou wt = arrWT[index]; System.out.println("消费了:" + wt); return wt; } } class Product implements Runnable { SyncStack syncStack = null; Product(SyncStack syncStack) { this.syncStack = syncStack; } public void run() { for(int i=0;i<40;i++){ WoTou wt = new WoTou(i); syncStack.push(wt); try { Thread.sleep(200); //隔一阵子生产一个窝头 } catch (InterruptedException e) { e.printStackTrace(); } } } } class Consumer implements Runnable { SyncStack ss = null; Consumer(SyncStack ss) { this.ss = ss; } public void run() { for(int i=0;i<10;i++){ ss.pop(); try{ Thread.sleep(1000); //隔一阵子消费一个窝头 }catch(InterruptedException e){ e.printStackTrace(); } } } }