JAVA多线程学习2--线程同步
一、线程同步介绍
同步:就是协同步调,按照预定的先后顺序执行。比如:你说完我再说。
线程同步:访问同一个共享资源的时候多个线程能够保证数据的安全性、一致性。
二、JAVA中实现线程同步的方法
实现进程同步的方法是在共享竞争的资源上加锁,保证对资源的独占性。JAVA中通过关键字synchronized实现同步。看下面的例子
package cn.edu.sdust.AsyTest; public class TestAsyn implements Runnable { Timer timer = new Timer(); /** * @param args */ public void run(){ timer.add(Thread.currentThread().getName()); } public static void main(String[] args) { // TODO Auto-generated method stub TestAsyn test1 = new TestAsyn(); Thread t1 = new Thread(test1); Thread t2 = new Thread(test1); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } } class Timer{ int num=0; public void add(String name){ num++; try { Thread.sleep(1); //使当前线程睡眠,切换线程 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(name+"这是线程"+num); } }
运行结果:
t1这是线程2
t2这是线程2
分析:这是因为当线程t1执行后修改了num=1后睡眠,线程t2执行,修改num=2后睡眠,切换到线程t1执行,此时num已经为2,因此打印t1为第二个线程。显然这种结果不是我们想要的。
如何保证数据的安全性,这里需要对共享资源加锁,实现线程同步。将共享资源add()方法加上关键字synchronized,保证资源的独占性。如下
class Timer{ int num=0; public synchronized void add(String name){ num++; try { Thread.sleep(1); //使当前线程睡眠,切换线程 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(name+"这是线程"+num); } }
修改后运行结果:
t1这是线程1
t2这是线程2
解释:通过synchronized对add方法进行加锁,即使通过sleep使线程t1睡眠,线程t1仍然握有该资源的锁,因此t2不能执行,必须等t1执行完释放对资源的锁t2才能执行。(注:sleep与wait区别之一就是sleep后线程仍然握有资源的锁,而wait后线程将会放弃资源的锁,直到被唤醒后重新争夺资源的锁)
三、synchronized的一些特点
当线程握有synchronized加锁的资源的锁时,其他访问非加锁资源的线程能够执行。如下例子:
public class ThreadAsynchronmous implements Runnable{ int n=100; public synchronized void m1(){ System.out.println("m1"); n=1000; try{ Thread.sleep(5000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("n="+n); } public void m2() throws Exception{ System.out.println("------"+n); } public void run(){ m1(); } /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub ThreadAsynchronmous t=new ThreadAsynchronmous(); new Thread(t).start(); Thread.sleep(1000); //让线程非主线程先执行,执行m1 /* for(int i=0; i<100; i++){ System.out.println(i); }*/ t.m2();
//System.out.println(n); } }
运行结果:
m1 ------1000 n=1000
可以看到在加锁线程执行的同时,主线程仍然可以继续执行,非加锁资源仍然可以被执行。因此,不要在非加锁区对共享变量做修改。以防止数据的不安全、不一致。