先来一个例子:
public class Main { public static void main(String[] args) { ThreadTest test1 = new ThreadTest("线程1"); ThreadTest test2 = new ThreadTest("线程2"); test1.start(); test2.start(); } } class ThreadTest extends Thread{ static int num = 0; public ThreadTest(String name){ super(name); } @Override public void run() { for(int i = 0;i<1000;i++){ num++; } System.out.println(num); } }这里ThreadTest继承了Thread类,run方法是把类变量进行1000次相加,启动了两个线程,所以预期结果应该是打印出:
1000 2000
实际是多次运行的情况下才会出现预期的输出,输出有可能是:1234,2000或者1349,1890之类的,原因是因为在两个线程对num进行++操作的时候没有同步。 java内存模式: 如上为java内存工作模式,线程并不是直接操作内存,而是把主内存的变量复制一份到工作内存,进行操作,然后把工作内存的变量同步到主内存当中,
比如上面的程序,test1的工作内存有一个num的副本,test2的工作内存也有一个num的副本,比如当num=123的时候,假设此时主内存,和全部工作内存都同步,num的值都是123,这时候test1对num++,得到124,在test1还没有把变量同步到主内存的时候,这时候test2也对num++,也得到124,然后test1和test2无论按照上面顺序把num同步到主内存中,最终就是进行了两次相加,但是实际上num只是从123到124。所以会出现和预期的结果不一样的输出。
synchronized是java提供的一种同步的锁,先看一下改进的代码:
@Override public void run() { synchronized(ThreadTest.class){ for(int i = 0;i<1000;i++){ num++; } System.out.println(num); } }只是对run方法更改了,加了一行synchronized(ThreadTest.class),这里起到的作用就是在一个线程进入这个代码块的时候要持有ThreadTest.class这个对象的锁,在这个线程执行期间,其他任何线程不能进入这个代码块。这样子无论执行多少次输出都是1000,2000。
synchronized()里面可以是任何对象,任何对象都可以是一把锁,这里ThreadTest.class是ThreadTest的class对象,因为只有一个,所以test1和test2之后又一个线程进入,等先进入的线程执行完自动释放锁等待线程才能进入代码块执行。如果把锁换成this,也就是执行时的线程对象,此处是两个不同的对象,所以还是会出现与预期不同的输出。这种给代码块加锁的方式叫做同步代码块。
同步方法就是把synchronized加在方法上,把ThreadTest类改成如下:
class ThreadTest extends Thread{ static int num = 0; public ThreadTest(String name){ super(name); } @Override public void run() { add(); } private synchronized static void add(){ for(int i = 0;i<1000;i++){ num++; } System.out.println(num); } }把加的操作抽成一个方法add,在方法上加上synchronized,这里是一个静态方法,静态方法是默认锁的类的class对象,也就是和上面的synchronzied(ThreadTest.class)作用一样,如果实例方法,实例方法的默认锁的类的实例,也就是和上面的synchronzied(this)效果是一样的。
wait方法:当前线程暂停,释放锁,直到被唤醒
如下代码:
class ThreadA extends Thread{ private String lock; public ThreadA(String name,String lock){ super(name); this.lock = lock; } @Override public void run() { synchronized(lock){ System.out.println(Thread.currentThread().getName()+"进入等待"); try { lock.wait(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"退出等待"); } } } public static void main(String[] args) throws InterruptedException { String str = "lock"; ThreadA test1 = new ThreadA("线程1",str); ThreadA test2 = new ThreadA("线程2",str); test1.start(); test2.start(); }输出是:
线程1进入等待 线程2进入等待 线程1退出等待 线程2退出等待
如果把lock.wait(1000)改成Thread.sleep(1000),输出为:
线程1进入等待 线程1退出等待 线程2进入等待 线程2退出等待
wait方法会释放锁,其他线程可以进入同步代码块里面,而sleep不会释放锁,只有等当前对象结束sleep,执行结束,其他线程才能进入同步代码块
notify方法:唤醒对应的锁的线程,随意唤醒一个 notify方法:唤醒对应的锁的全部线程 代码如下:
public class Main { public static void main(String[] args) throws InterruptedException { String str = "lock"; ThreadA test1 = new ThreadA("线程A-1",str); ThreadA test2 = new ThreadA("线程A-2",str); ThreadB test3 = new ThreadB("线程B",str); test1.start(); test2.start(); //保证test1和test2先执行wait方法 Thread.sleep(1000); test3.start(); } } class ThreadA extends Thread{ private String lock; public ThreadA(String name,String lock){ super(name); this.lock = lock; } @Override public void run() { synchronized(lock){ System.out.println(Thread.currentThread().getName()+"进入等待"); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"退出等待"); } } } class ThreadB extends Thread{ private String lock; public ThreadB(String name,String lock){ super(name); this.lock = lock; } @Override public void run() { synchronized(lock){ System.out.println(Thread.currentThread().getName()+"唤醒其他线程"); lock.notify(); } } }输出为:
线程A-1进入等待 线程A-2进入等待 线程B唤醒其他线程 线程A-1退出等待
这里有可能最后只有线程A-2退出等待,因为是随机唤醒一个。 如果把notify换成notifyAll,输出为:
线程A-1进入等待 线程A-2进入等待 线程B唤醒其他线程 线程A-1退出等待 线程A-2退出等待
会唤醒全部lock对象wait的线程。
