输出为什么要引入同步机制
在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。
解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。
怎样实现同步
对于访问某个关键共享资源的所有方法,都必须把它们设为synchronized
例如:
synchronized void f() { /* ... */ } synchronized void g() { /* ... */ }
如果想保护某些资源不被多个线程同时访问,可以强制通过synchronized方法访问那些资源。 调用synchronized方法时,对象就会被锁定。
1 public class MyStack {
2 int idx = 0;
3 char [] data = new char[ 6];
4 public synchronized void push( char c) {
5 data[ idx] = c;
6 idx++;
7 }
8 public synchronized char pop() {
9 idx--;
10 return data[ idx];
11 }
12 }
说明:
•当synchronized方法执行完或发生异常时,会自动释放锁。
•被synchronized保护的数据应该是私有(private)的。
看几个例子
1 public class ThreadTest3
2 {
3 public static void main(String[] args)
4 {
5 Runnable r = new HelloThread();
6
7 Thread t1 = new Thread(r);
8
9 //r = new HelloThread();
10
11 Thread t2 = new Thread(r);
12
13 t1.start();
14 t2.start();
15 }
16 }
17
18 class HelloThread implements Runnable
19 {
20 int i;
21
22 @Override
23 public void run()
24 {
25 // int i = 0;
26
27 while(true)
28 {
29 System.out.println("number: " + i++);
30
31 try
32 {
33 Thread.sleep((long)(Math.random() * 1000));
34 }
35 catch (InterruptedException e)
36 {
37 e.printStackTrace();
38 }
39
40 if(5 == i)
41 {
42 break;
43 }
44 }
45 }
46 }
输出顺序执行:

程序执行时出现了两种输出,现在分别来分析一下:
为什么打印两个0:如文章开头所讲,由于两个线程同时访问一个资源,而且此资源未加锁就会导致访问冲突。
线程1打印语句还未执行++操作,线程2就进来了,所以导致打印两个0
为什么"只有一个线程输出":
只打印了0-4,给人感觉只打印了一条线程,其实这是两条线程共同作用的结果。
因为 i 这个变量是一个成员变量,而在同一个对象 r 中成员变量是共享的,当线程对 i 进行++操作,线程2中的 i 也会更改,所以打印出这样的结果
如果将 i 放在方法内即变成局部变量,则会打印两条线程的执行结果

总结:
关于成员变量与局部变量:如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,他们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。
如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。
停止线程的方式:不能使用Thread类的stop方法来终止线程的执行。一般要设定一个变量,在run方法中是一个循环,循环每次检查该变量,如果满足条件则继续执行,否则跳出循环,线程结束。
synchronized关键字
synchronized关键字:当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。
1 public class ThreadTest4
2 {
3 public static void main(String[] args)
4 {
5 Example example = new Example();
6
7 Thread t1 = new TheThread(example);
8
9 example = new Example();
10
11 Thread t2 = new TheThread2(example);
12
13 t1.start();
14 t2.start();
15 }
16 }
17
18 class Example
19 {
20 public synchronized void execute()
21 {
22 for(int i = 0; i < 5; i++)
23 {
24 try
25 {
26 Thread.sleep((long)(Math.random() * 1000));
27 }
28 catch (InterruptedException e)
29 {
30 e.printStackTrace();
31 }
32
33 System.out.println("hello: " + i);
34 }
35 }
36
37 public synchronized void execute2()
38 {
39 for(int i = 0; i < 5; i++)
40 {
41 try
42 {
43 Thread.sleep((long)(Math.random() * 1000));
44 }
45 catch (InterruptedException e)
46 {
47 e.printStackTrace();
48 }
49
50 System.out.println("world: " + i);
51 }
52 }
53 }
54
55 class TheThread extends Thread
56 {
57 private Example example;
58
59 public TheThread(Example example)
60 {
61 this.example = example;
62 }
63
64 @Override
65 public void run()
66 {
67 this.example.execute();
68 }
69 }
70
71 class TheThread2 extends Thread
72 {
73 private Example example;
74
75 public TheThread2(Example example)
76 {
77 this.example = example;
78 }
79
80 @Override
81 public void run()
82 {
83 this.example.execute2();
84 }
85 }
结果为

结果可以看出两条线程都同时执行了且互不影响。
当我们修改main方法
1 public class ThreadTest4
2 {
3 public static void main(String[] args)
4 {
5 Example example = new Example();
6
7 Thread t1 = new TheThread(example);
8
9 Thread t2 = new TheThread2(example);
10
11 t1.start();
12 t2.start();
13 }
14 }
使用同一个 example,结果为

这个结果是必然的,不管你执行多少次。
很显然,线程在执行时是一个线程一个线程的执行的。
原因就如上面红字所讲的,当访问某个对象的synchronized方法时表示对该对象上锁,我们这里使用的是同一个example对象,所以只能等上一个线程执行完后才能执行下一个线程,而不能同时执行。
如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。
再来更改Example对象,在其方法上都加上 static 修饰
1 public synchronized static void execute()
2 {
3 for(int i = 0; i < 5; i++)
4 {
5 try
6 {
7 Thread.sleep((long)(Math.random() * 1000));
8 }
9 catch (InterruptedException e)
10 {
11 e.printStackTrace();
12 }
13
14 System.out.println("hello: " + i);
15 }
16 }
17
18 public synchronized static void execute2()
19 {
20 for(int i = 0; i < 5; i++)
21 {
22 try
23 {
24 Thread.sleep((long)(Math.random() * 1000));
25 }
26 catch (InterruptedException e)
27 {
28 e.printStackTrace();
29 }
30
31 System.out.println("world: " + i);
32 }
33 }
这时,不管你是否使用同一个example,执行结果都是先执行线程1在执行线程2.
原因就在于:
如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为Java中无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,他们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行。
再对Example方法进行修改,改成一个static一个非static
1 public synchronized void execute()
2 {
3 for(int i = 0; i < 5; i++)
4 {
5 try
6 {
7 Thread.sleep((long)(Math.random() * 1000));
8 }
9 catch (InterruptedException e)
10 {
11 e.printStackTrace();
12 }
13
14 System.out.println("hello: " + i);
15 }
16 }
17
18 public synchronized static void execute2()
19 {
20 for(int i = 0; i < 5; i++)
21 {
22 try
23 {
24 Thread.sleep((long)(Math.random() * 1000));
25 }
26 catch (InterruptedException e)
27 {
28 e.printStackTrace();
29 }
30
31 System.out.println("world: " + i);
32 }
33 }
这时不管你是否使用同一个example,结果都是乱序,即两个线程同时执行,原因还是在于锁对象问题。
static锁的是class对象,非static锁的就是对象,两个锁的对象不同,结果自然不同。
synchronized代码块
synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;
synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的。
所以更多情况下,我们选择把需要同步的逻辑写在synchronized块中。
1 synchronized(object)
2 {
3 。。。
4 }
表示线程在执行的时候会对object对象上锁
所以上面的程序就可以改为
1 public void execute()
2 {
3 synchronized (this)
4 {
5 for (int i = 0; i < 20; i++)
6 {
7 try
8 {
9 Thread.sleep((long) (Math.random() * 1000));
10 }
11 catch (InterruptedException e)
12 {
13 e.printStackTrace();
14 }
15
16 System.out.println("hello: " + i);
17 }
18 }
19
20 }
21
22 public void execute2()
23 {
24 synchronized(Example.class)
25 {
26 for (int i = 0; i < 20; i++)
27 {
28 try
29 {
30 Thread.sleep((long) (Math.random() * 1000));
31 }
32 catch (InterruptedException e)
33 {
34 e.printStackTrace();
35 }
36
37 System.out.println("world: " + i);
38 }
39 }
40 }
来源:https://www.cnblogs.com/2015110615L/p/6732695.html