Educoder–Java高级特性(第六章)- 多线程基础(3)线程同步【笔记+参考代码】

Educoder–Java高级特性(第六章)- 多线程基础(3)线程同步【笔记+参考代码】


第一关


1.在并发编程中,我们需要以下哪几个特性来保持多线程程序执行正确( ABD )

A、可见性
B、原子性
C、并发性
D、有序性

2.请分析以下语句哪些是原子操作( AB )
A、int a = 3;
B、boolean flag = false;
C、a–;
D、a =a *a

3.以下代码的执行结果是( E )

public class Test {
    public  int inc = 0;
    public void increase() {
        inc++;
    }
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }
        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

A、10000
B、9870
C、大于10000
D、小于10000
E、不一定,大概率小于一万



第二关


编程要求
请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,具体任务如下:

使num变量在同一时刻只能有一个线程可以访问。

测试说明
使程序的输出结果如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18



参考代码

package step2;

public class Task {
	public static void main(String[] args) {	
		final insertData insert = new insertData();
		
		for (int i = 0; i < 3; i++) {
			new Thread(new Runnable() {
				public void run() {
					insert.insert(Thread.currentThread());
				}
			}).start();
		}		
	}
}
class insertData{
	public static int num =0;
	/********* Begin *********/
	public synchronized void insert(Thread thread){
		for (int i = 0; i <= 5; i++) {
			num++;
			System.out.println(num);
		}
	}
	/********* End *********/
}



第三关


编程要求
请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充。

测试说明
使得程序输出如下结果(因为线程的执行顺序是随机的可能需要你评测多次):

Thread-0得到了锁

1

2

3

4

5

Thread-0释放了锁

Thread-1得到了锁

6

7

8

9

10

Thread-1释放了锁

Thread-2得到了锁

11

12

13

14

15

Thread-2释放了锁



参考代码

package step3;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Task {
	public static void main(String[] args) {
		final Insert insert = new Insert();
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				insert.insert(Thread.currentThread());
			}
		});
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				insert.insert(Thread.currentThread());
			}
		});
		Thread t3 = new Thread(new Runnable() {
			public void run() {
				insert.insert(Thread.currentThread());
			}
		});
		// 设置线程优先级
		t1.setPriority(Thread.MAX_PRIORITY);
		t2.setPriority(Thread.NORM_PRIORITY);
		t3.setPriority(Thread.MIN_PRIORITY);

		t1.start();
		t2.start();
		t3.start();

	}
}
class Insert {
	public static int num;
	// 在这里定义Lock
	private Lock lock = new ReentrantLock(); 
	public void insert(Thread thread) {
		/********* Begin *********/
		if(lock.tryLock()){
			try{
				System.out.println(thread.getName()+"得到了锁");
				for (int i = 0; i < 5; i++) {
					num++;
					System.out.println(num);
				}
			}finally{
				System.out.println(thread.getName()+"释放了锁");
				lock.unlock();
			}
		}else{
			System.out.println(thread.getName()+"获取锁失败");
		}
	}
		/********* End *********/
}



第四关


编程要求
请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充。


测试说明
预期输出:10000。

提示:可以使用两种方式实现原子性,所以本关有多种方式都可以通关。

解题方法有两个:
方法1: 使用synchronized加锁,使i的数据同步,但是这个方式简单是简单,比较耗资源,在多线程竞争资源的时候,加锁、释放锁,线程之间不停的切换会引起性能问题。一个线程持有锁,其它线程需要等到的这把锁之后才能执行,期间线程是被挂起,容易导致死锁发生。

方法2: 使用原子变量AtomicInteger定义变量 i ,在i++原子性问题中,方案2对比起方案1更加优。


参考代码

方法1:

package step4;

public class Task {
	public volatile int inc = 0;
	//请在此添加实现代码
	/********** Begin **********/
	public synchronized void increase() {
		inc++;
	}
	/********** End **********/
	public static void main(String[] args) {
		final Task test = new Task();
		for (int i = 0; i < 10; i++) {
			new Thread() {
				public void run() {
					for (int j = 0; j < 1000; j++)
						test.increase();
				};
			}.start();
		}
		while (Thread.activeCount() > 1) // 保证前面的线程都执行完
			Thread.yield();
		System.out.println(test.inc);
	}
}

方法2:

package step4;
import java.util.concurrent.atomic.AtomicInteger;
public class Task {
	public static AtomicInteger inc = new AtomicInteger(0);
	//请在此添加实现代码
	/********** Begin **********/
	public static void increase() {
		inc.incrementAndGet();
	}
	/********** End **********/
	public static void main(String[] args) {
		final Task test = new Task();
		for (int i = 0; i < 10; i++) {
			new Thread() {
				public void run() {
					for (int j = 0; j < 1000; j++)
						increase();
				};
			}.start();
		}
		while (Thread.activeCount() > 1) // 保证前面的线程都执行完
			Thread.yield();
		System.out.println(test.inc);
	}
}



【笔记】

在线程中比较重要的几个概念:

1.原子性
原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败。
在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。

	x = 10;        //语句1
	y = x;         //语句2
	x++;           //语句3
	x = x + 1;     //语句4

只有语句1是原子性操作。

语句1是直接将10的值赋值给x,所以也就是说执行这个语句会直接将数值10写入到内存中,所以这是原子性的,语句2其实是两个操作,先读取x的值,然后赋值给y,这两个步骤是原子性的,但是他们合起来就不是原子性操作了,后面两个语句也是同样的道理。

也就是说,只有简单的读取,赋值(必须是将数字赋值给某个变量,变量之间的赋值不是原子性操作),才是原子性操作。

如果要实现大范围的原子性,可以通过synchronized和lock来实现,synchronized和lock可以保证在任何时候只有一个线程执行该代码块,所以就保证了原子性。

2.可见性
可见性是当多个线程访问一个变量时,一个线程改变了变量的值,其他线程立马可以知道这个改变。

对于可见性,Java提供了Volatile关键字来保证,当一个变量被Volatile修饰时,它会保证修改的值会被立即重新写入到主内存,当其他线程要调用该共享变量时,会去主内存中重新读取。

但是普通的共享变量是不能保证可见性的,因为普通变量会被读入到线程自己的内存,当一个线程修改了之后,可能还没来得及刷新到主内存,其他线程就从主存中读取了该变量。所以其他线程读取的时候可能还是原来的值,所以普通共享变量是无法保证可见性的。

关于保证可见性还可以通过Synchronized和lock的方式来实现。

3.有序性

有序性:即程序的执行是按照代码编写的先后顺序执行的

 int a;              
 boolean flag;
 i = 10;                //语句1  
 flag = false;          //语句2

上述代码定义了一个整形的变量a,布尔类型变量flag,使用语句1和语句2对变量i和flag进行赋值,看起来语句1是在语句2之前的,但是在程序运行的时候语句1一定会在语句2之前执行吗?是不一定的,因为这里可能会发生指令重排序(Instruction Reorder)。

什么是指令重排序呢?

一般来说,处理器为了提升执行效率,会对输入代码进行优化,它不保证代码执行的顺序和代码编写的顺序一致,但是它会保证程序的输出结果和代码的顺序执行结果是一致的。

比如上面的代码中,语句1和语句2谁先执行对最终的程序结果并没有影响,那么就有可能在执行过程中,语句2先执行而语句1后执行。

指令重排序不会影响单个线程的执行,但是会影响多线程的执行。

也就是说,要保证多线程程序执行的正确性,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。

猜你喜欢

转载自blog.csdn.net/weixin_44177494/article/details/105493335