1.常用的锁操作
1.1:synchronized
通过对象的内存地址来锁定代码块
private int count =10;
private Object o=new Object();
public void m(){
synchronized (o){
count--;
System.out.println(Thread.currentThread().getName()+" count = "+count);
}
}
加在方法上,锁定这个方法的代码块,锁的对象为当前对象
private int count=10;
public synchronized void m(){
count--;
System.out.println(Thread.currentThread().getName()+" count = "+count);
}
一个同步方法可以调用另一个同步方法,一个线程已经拥有了某个对象的值,再次申请的时候仍然会得到该对象的锁,也就是说synchronized获得的锁是可重入的
synchronized void m1(){
System.out.println("m1 start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
}
synchronized void m2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2");
}
1.2:reentrantlock
lock.lock(); //相当于synchronized(this),注意reentrantlock必须手动释放锁,所以写在finally里面,并且需要把加锁的过程写在try代码块外面,避免加锁失败导致释放锁出现异常
Lock lock=new ReentrantLock();
void m1(){
lock.lock(); //相当于synchronized(this)
try {
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
void m2(){
lock.lock();
try {
System.out.println("m2....");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLock2 r1=new ReentrantLock2();
new Thread(r1::m1).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r1::m2).start();
}
使用trylock进行尝试锁定,不管锁定与否,方法都将继续执行,trylock也可以指定时间,由于trylock(time)抛出异常,所以要注意unlock的处理,必须方法finally中
Lock lock=new ReentrantLock();
void m1(){
lock.lock(); //相当于synchronized(this)
try {
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
/**
* 使用trylock进行尝试锁定,不管锁定与否,方法都将继续执行
* 可以根据trylock的返回值来判断是否锁定
* 也可以指定trylock的时间,由于trylock(time)抛出异常,所以要注意unlock的处理,必须方法finally中
*/
void m2(){
/*boolean locked=lock.tryLock();
System.out.println("m2..."+locked);
if (locked) lock.unlock();*/
boolean locked=false;
try {
locked=lock.tryLock(5,TimeUnit.SECONDS);
System.out.println("m2...."+locked);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (locked) lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLock3 r1=new ReentrantLock3();
new Thread(r1::m1).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r1::m2).start();
}
使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断
public static void main(String[] args) {
Lock lock=new ReentrantLock();
Thread t1=new Thread(()->{
lock.lock();
try {
System.out.println("t1 start");
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
System.out.println("t1 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
t1.start();
Thread t2=new Thread(()->{
try {
lock.lockInterruptibly(); //可以对interrupt()做出响应
System.out.println("t2 start");
TimeUnit.SECONDS.sleep(5);
System.out.println("t2 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt();//打断线程2的等待
}
ReentrantLock还可以指定公平锁
private static ReentrantLock lock=new ReentrantLock(true);//参数为true表示为公平锁,轻对比输出结果
@Override
public void run() {
for (int i = 0; i < 100; i++) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"获得锁");
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
ReentrantLock5 r1=new ReentrantLock5();
Thread th1=new Thread(r1);
Thread th2=new Thread(r1);
th1.start();
th2.start();
}
2容器
- HashMap:线程不安全,不能保证数据的同步,常用于单线程
- Hashtable:线程安全,但效率很低,它的初始容量和扩容策略跟HashMap都不同,而且键和值不能为null
- TreeMap:线程不安全,它使用的存储结构是平衡二叉树也称红黑树,适用于需要排序的Map
- LinkedHashMap:线程不安全,是HashMap的子类,常用于插入顺序和取出顺序一样的场景
- Collections.synchronizedXXX:传入一个不安全的容器返回一个加锁的容器,也就是安全的,常用于并发小的场景
- ConcurrentHashMap:线程安全的,使用ReentrantLock实现了分段锁技术,为了解决HashMap不安全,Hashtable效率太低而诞生的
- ConcurrentSkipListMap:使用跳表实现,是线程安全并且有序的,适用于高并发
- ArrayList:线程不安全的数组,删除和插入的开销比较大,适用于单线程随机访问集合元素的场景
- LinkedList:线程不安全的,使用双链表实现,删除和插入相对于ArrayList更快,适用于写操作比读操作多的场景
- CopyOnWriteArrayList:写时复制容器,常用于读操作远远大于写操作,免去了读时加锁,避免了资源浪费
3队列
- CocurrentLinkedQueue:基于链接节点的无界线程安全队列,按照 FIFO(先进先出)原则对元素进行排序
- LinkedBlockingQueue:使用链表来实现的阻塞式队列,可并行执行读写操作,当队列满了时做添加操作和队列空了时做取出操作就会出现阻塞
- ArrayBlockingQueue基于数组的有界阻塞式队列,是一个线程安全的队列,按照先进先出排序
- LinkedTransferQueue:无界的阻塞队列,适用于消费者先启动,生产者后启动场景
- DelayQueue:延迟无界队列,在指定时间才能获取队列元素,常用于定时执行任务
- SynchronusQueue:特殊的TransferQueue,容量为0,使用时必须要有一个消费者在等待中
4线程池
- newFixedThreadPool:指定数量的线程池,当任务大于指定数量时会进入等待,如果不手动关闭则会一直处于启动状态
- newCachedThreadPool:可缓存的线程池,灵活创建回收线程,默认线程收回为60秒,最大为当前系统支撑的最大线程
- newSingleThreadExecutor:单线程化线程池,串行执行所有任务,保证了所有任务的执行顺序按照任务的提交顺序执行
- newScheduledThreadPool:定时执行的线程池,适用于周期性执行任务的场景,于定时器不同的是他是一个线程池,里面的线程是可复用的
- newWorkStealingPool:JDK1.8增加的具有抢占式操作的线程池也叫工作窃取线程池,它创建的线程属于精灵线程,只要jvm不结束它就不会结束,适合于使用在很耗时的操作
- ForkJoinPool:将一个任务拆分成多个“小任务”并行计算,可拆分可合并,适用于计算密集型的任务
补充
volatile关键字,使其具有内存可见性,使一个变量在多个线程间可见,例如当A B线程都用到一个变量时,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道, 使用volatile关键字,会让所有线程都读到该变量的修改值
ThreadLocal线程局部变量,ThreadLocal是使用空间换时间,synchronized是使用时间换空间
public class ThreadLocal1 {
volatile static Person p =new Person();
public static void main(String[] args) {
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(p.name);
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.name="lisi";
}).start();
}
}
class Person{
String name="zhangsan";
}