并发
不自己写真是不知道,其实是有好多问题的,而java编程思想的代码带过太严重了;
下面是重点了
1.线程中的对象; 我们会发现,每次有一个新的线程对象初始化的线程是一个独立的;
public class Mytest3 implements Runnable {
int a=0;
@Override
public void run() {
System.out.println(this);
System.out.println(a);
a+=10;
}
public static void main(String []arg)
{
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i=0;i<10;i++)
executorService.execute(new Mytest3());
}
}
output:
Thinking_in_java.Thread.Synchronized.Mytest3@78b8f730
Thinking_in_java.Thread.Synchronized.Mytest3@22ef0305
0
Thinking_in_java.Thread.Synchronized.Mytest3@f2dabac
0
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
0
0
Thinking_in_java.Thread.Synchronized.Mytest3@39bdd410
0
Thinking_in_java.Thread.Synchronized.Mytest3@394b0894
0
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
0
Thinking_in_java.Thread.Synchronized.Mytest3@6840b323
0
Thinking_in_java.Thread.Synchronized.Mytest3@5f89ad23
0
Thinking_in_java.Thread.Synchronized.Mytest3@520fddc3
0
很显然这是10个Mytest3对象创建了各自的线程,a是每个对象自己的;除非你定义为static;
于是我们就可以改成这么写,一个对象初始化10个线程;
public class Mytest3 implements Runnable {
static int a=0; //一个对象的时候,static是可以不写的啦
@Override
public void run() {
System.out.println(this);
System.out.println(a);
a+=10;
}
public static void main(String []arg)
{
ExecutorService executorService = Executors.newCachedThreadPool();
Mytest3 mytest3 = new Mytest3();
for (int i=0;i<10;i++)
executorService.execute(mytest3);
}
}
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
0
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
10
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
20
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
30
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
40
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
50
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
60
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
70
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
80
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
90
那么线程同步是什么情况呢?我们将run方法,改为这样
public void run() {
System.out.println(this);
System.out.println(a);
a+=10;
Thread.yield();
a+=10;
}
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
0
0
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
40
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
60
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
70
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
100
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
120
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
140
130
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
180
我们可以发现他出现了问题,发现了什么问题呢? 同时输出0,输出了奇数,后面输出比前面小;
我们可以归为两类
1. 线程抢占资源,a+=10线程A做了一下,然后被线程B抢过去了,也做了一下,所以总会存在奇数的情况;宏观上不该访问的时候访问了
2. 操作非原子性,简单地说,不管线程怎么抢,你应该是一直增大的吧;但是事实是会出现后输出的前面输出的小,因为操作不是原子性的,+=操作进行的中途,被线程抢跑了;微观上,我们的一个++操作,或者return操作,资源中途被抢跑了;
先说说第二个,原子性,对我们来说+1这种操作好像就是一下,一个操作,但是对于jvm不是的,(差不多应该是这样)它得把数字从储存位置拿出来,+1,再放回去;这中间线程是可以把储存位置的对象读取出来的,然后你就知道会发生了;
volatile就是保护这个的;但是其实作用不是很大,因为对于我们来说宏观的操作更多,保证他们的正确性才是正确的
将a 的定义加上volatile,就会发现不会存在大小问题了(大概,嘿嘿);
对于第一个问题,我们可以这样:
public class Mytest3 implements Runnable {
volatile static int a=0;
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 100; i++) {
System.out.println(this);
System.out.println(a);
a += 10;
Thread.yield();
a += 10;
}
}
}
public static void main(String []arg)
{
ExecutorService executorService = Executors.newCachedThreadPool();
Mytest3 mytest3 = new Mytest3();
for (int i=0;i<10;i++)
executorService.execute(mytest3);
}
}
ouptut:
反正顺序对了;
或者将其拿出去当一个函数
public synchronized void a()
{
for (int i = 0; i < 100; i++) {
System.out.println(this);
System.out.println(a);
a += 10;
Thread.yield();
a += 10;
}
}
@Override
public void run() {
a();
}
一个神奇的问题
如果不是这个问题我才懒得写代码呢;
public synchronized void a()
{
++a;
Thread.yield();
++a;
}
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
a();
if (a % 2 != 0) {
System.out.println("wrong");
}
}
}
output:反正一大堆wrong
我找了一晚上错误,把前面所有特性都试验了;然后我发现,run方法,run方法里面的这段代码,它是可以随意访问的;所以只要把代码改为:
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 5000; i++) {
a();
if (a % 2 != 0) {
System.out.println("wrong");
}
}
}
}
划重点
当任务要执行被synchronized关键字保护的代码片段的时候,它将检查锁是否可用,然后获取锁,执行代码,释放锁。
所有对象都自动含有单一的锁(也称为监视器)。当在对象上调用其任意synchronized方法的时候,此对象都被加锁,这时该对象上的其他synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。
注意,在使用并发时,将域设置为private是非常重要的。否则,synchronized关键字就不能防止其他任务直接访问域,这样就会产生冲突。
JVM负责跟踪对象被加锁的次数。如果一个对象被解锁(即锁被完全释放),其计数变为0。在任务第一次给对象加锁的时候,计数的任务才能允许继续获取多个锁。每当任务离开一个synchronized方法,计数递减,当计数为零的时候,锁被完全释放,此时别的任务就可以使用此资源。
Brian同步规则:如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被一个线程写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步。
为了让临界共享资源正确地工作,每个访问临界共享资源的方法必须被同步。
我们来看一下所谓一个对象共享一把锁
public synchronized void a() throws InterruptedException {
public synchronized void a() throws InterruptedException {
Thread.sleep(1000);
}
public void b()
{
}
@Override
public void run() {
int a = (int) (Math.random()*10);
//synchronized (this) { //此处加锁就没有意义了
for (int i = 0; i < 5; i++) {
if (a>5) {
System.out.println(Thread.currentThread().getName()+" 先做a");
try {
a();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"a完成");
b();
}
else
{
System.out.println(Thread.currentThread().getName()+" 先做b");
b();
System.out.println("b完成");
try {
a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//}
}
}
output:
pool-1-thread-7 先做a //a表示它要先做,但是b实际上是可以随便访问的;
pool-1-thread-6 先做b
b完成
pool-1-thread-8 先做b
b完成
pool-1-thread-9a完成 //线程7此时其实才拿到资源; 因为a是加锁的
pool-1-thread-9 先做a
pool-1-thread-3 先做b
b完成
pool-1-thread-5a完成
pool-1-thread-5 先做a
pool-1-thread-4 先做b
b完成
pool-1-thread-1 先做b
b完成
pool-1-thread-4 先做b
b完成
pool-1-thread-5a完成
pool-1-thread-5 先做a
pool-1-thread-3 先做b
b完成
pool-1-thread-9a完成
pool-1-thread-9 先做a
pool-1-thread-8 先做b
b完成
pool-1-thread-6 先做b
b完成
pool-1-thread-7a完成
public synchronized void b() //b加锁
{
}
output
pool-1-thread-2 先做a //2和8要先做a
pool-1-thread-7 先做b
pool-1-thread-6 先做b
pool-1-thread-5 先做b
pool-1-thread-4 先做b
pool-1-thread-8 先做a
pool-1-thread-9 先做b
pool-1-thread-3 先做b
pool-1-thread-1 先做b
b完成
pool-1-thread-10 先做b
b完成
b完成
b完成
b完成
b完成
b完成
pool-1-thread-8a完成 //在b完成后才可以a完成;
a完成
pool-1-thread-2a完成
a完成
临界区
synchronized (Object),套住的的代码,这样就可以忽略方法上的锁,转为代码上的锁,但其实锁住的不是代码,而是对象,就像是this;不同对象的锁互不干涉
线程本地储存
简单来说,有些时候你是需要线程的;一个对象实例化的多个线程;那么对象里有很多的属性(域),有些是要进行++,return操作的,那么访问必须同步,否则会发生错误,但是假如不是呢?所以有些变量是要共享,但是互不冲突;