1.请简述一下实现多线程同步的方法?
为什么要线程同步:
因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题。
-
同步方法: 即有synchronized关键字修饰的方法. 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态.
注释:synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类 -
同步代码块: 即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步.
注释: 同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。 -
使用Volatile实现线程同步:
3.1 volatile关键字为域变量的访问提供了一种免锁机制.
3.2 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新
3.3 每次使用该域就要重新计算,而不是使用寄存器中的值
3.4 volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
volatile会组织编译器对代码优化,因此能不使用它就不适用它吧。
它的原理是每次线程要访问volatile修饰的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。 -
使用重入锁实现线程同步:
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized的方法和块具有相同的基本行为和语义,并且扩展了其能力.
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用. -
使用ThreadLocal实现线程同步(空间换时间):
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
注意: 如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 。如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
每种同步方法的示例解析,请参考: https://www.jb51.net/article/124073.htm
2.内部类实现线程设计
请使用内部类实现线程设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。
实现代码:
public class TestInnerDesignThread {
//内部类
class Inner{
int j=5;
//设计实现功能
void func(){
//初始化var数组,存储j的值
int[] var=new int[]{0};
var[0]=j;
//i<4 设计4个线程
for(int i=0;i<4;i++){
//初始化index 存储当前线程号
int[] index=new int[]{0};
index[0]=i;
//设计两个+1线程
if(i<2){
new Thread(()->{
var[0]+=1;//j+1
System.out.println("当前线程是:"+index[0]+",j的值为:"+var[0]);
}).start();
}else{//设计两个-1的线程
new Thread(()->{
var[0]-=1;//j-1
System.out.println("当前线程是:"+index[0]+",j的值为:"+var[0]);
}).start();
}
}
}
}
public static void main(String[] args) {
//实例化外部类
TestInnerDesignThread test=new TestInnerDesignThread();
//实例化内部类
TestInnerDesignThread.Inner inner=test.new Inner();
inner.func();
}
}
运行结果:
3.数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字
例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
示例代码:
public class TestArray {
public int MoreThanHalfNum_TestArray(int[] array) {
}
}
实现代码:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class TestArray {
public int MoreThanHalfNum_TestArray(int[] array) {
//数组长度为0,返回0
if(array.length==0) return 0;
//存取:数字,出现的次数(key-value)
HashMap<Integer,Integer> map=new HashMap<>();
for(int i=0;i<array.length;i++){
if(!map.containsKey(array[i])){//如果没有这个key,表示没有存这个数字
map.put(array[i],1);//存未出现过的数字
}else{
//获取当前出现数字的次数
Integer index = map.get(array[i]);
//次数+1
int count=index.intValue()+1;
//把新的结果放入map中
map.put(array[i],count);
}
}
//遍历map
//map.entrySet() 获取map的key-value
Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<Integer, Integer> next = iterator.next();
Integer key = next.getKey();
Integer value = next.getValue();
//超过一半
if(value>=(array.length/2+1)){
return key;
}
}
return 0;
}
public static void main(String[] args) {
TestArray test=new TestArray();
int[] array=new int[]{1,2,3,2,2,2,5,4,2};
int index = test.MoreThanHalfNum_TestArray(array);
System.out.println(index);
}
}