先来一个例子:
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同步锁
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同步方法
同步方法就是把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和sleep的区别
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,执行结束,其他线程才能进入同步代码块
wait,notify,notifyAll
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的线程。