Java高并发编程-基础知识点

 

1.使用synchronized关键字对某个对象进行加锁,任何线程要执行同步代码块中的程序,必须要拿到该对象的锁。

2.在方法上使用synchronized关键字,等同于对当前对象加锁。

public synchronized void method() {
    //等于
    //synchronized(this) { }
}

3.在静态方法上使用synchronized关键字,等同于对当前类加锁。

public synchronized static method() {
    //等于
    //synchronized (当前类.class) { }
}

4.同步方法和和非同步方法可以同时调用。

public class T {

	public synchronized void m1() {
		System.out.println(Thread.currentThread().getName() + " m1 启动.....");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + " m1 结束.....");
	}
	
	public void m2() {
		System.out.println(Thread.currentThread().getName() + " m2 启动......");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + " m2 结束......");
	}
	
	public static void main(String[] args) {
		T t = new T();
		new Thread(t::m1, "t1").start();
		
//		new Thread(t::m2, "t2").start();      三种方法表达效果相同,但格式不同,前两者是java8的lambda表达式,后者是使用匿名类
//		new Thread(()->t.m2(), "t2").start();
		new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				t.m2();
			}
			
		}, "t2").start();
	}
}

5.脏读问题(dirtyRead):写业务方法加锁,但读业务方法不加锁。

6.一个同步方法可以调用另一个同步方法。一个线程已经拥有某对象的锁,再一次申请时还是会得到该对象的锁。synchronized锁是可以重入的。子类可以调用父类同步方法。

public class T {
	
	 public synchronized void m() {
		System.out.println(Thread.currentThread().getName() + " m1 启动.....");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + " m1 结束.....");
	}
	
	public static void main(String[] args) {
		new TT().m();
	}
}

class TT extends T {
	 public synchronized void m() {
		System.out.println("child start");
		super.m();
		System.out.println("child stop");
	}
}

7.在程序的执行过程中,如果出现异常,默认情况下锁会被释放。因此在处理高并发的过程中,应避免异常的产生或者使用try...catch对异常进行捕获。

8.volatile关键字保证了变量的可见性,使一个变量在多个线程间可见。

当多个线程访问一个变量时,会拷贝一份变量在自己工作区,之后就不会去内存中访问该变量。如果其中一个线程对内存中的该变量修改后,其他线程并不会知道该变量改变了,读取的变量值还是自己工作区中的该变量。

使用volatile关键字修饰变量后,到内存中变量值发生改变时,会通知过所以线程,让他们更新自己工作区当中该变量的值。

public class T {

	private volatile boolean flag = true;
	
	public void m() {
		System.out.println("start");
		while(flag) {
//			try {
//				TimeUnit.SECONDS.sleep(2);
//			} catch (InterruptedException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
		}
		System.out.println("end");
	}
	
	public static void main(String[] args) {
		T t = new T();
		new Thread(t::m, "t1").start();
		try {
			TimeUnit.SECONDS.sleep(4);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		t.flag = false;
	}
}

9.volatile不能替代synchronized关键字,它只保证可见性,不保证原子性;而synchronized即保证可见性又保证原子性。

i++是线程不安全的,可以使用synchronized解决该问题。

public class T {

	private volatile int count = 0;
	
	public /*synchronized*/ void m() {
		for(int i = 0; i < 10000; i++) {
			count++;
		}
	}
	
	public static void main(String[] args) {
		T t = new T();
		
		List<Thread> threads = new ArrayList<>();
		
		for (int i = 0; i < 10; i++) {
			threads.add(new Thread(t::m, "thread-" + i));
		}
		
		threads.forEach((o)->o.start());
		
		threads.forEach((o)->{
			try {
				o.join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		});
		
		System.out.println(t.count);
	}
}

10.解决上述问题还有更高效的方法,使用AtomicXXX类(XXX表示各种包装类),该类中的方法都是原子性的,但是不保证连续调用多个方法是原子性的。

public class T {
	
	private AtomicInteger count = new AtomicInteger(0); 
	
	public void m() {
		for(int i = 0; i < 10000; i++) {
			if (count.get() < 1000) {
				
			}
			count.incrementAndGet();
		}
	}
	
	public static void main(String[] args) {
		T t = new T();
		
		List<Thread> threads = new ArrayList<>();
		
		for(int i = 0; i < 10; i++) {
			threads.add(new Thread(t::m, "thread-"+i));
		}
		
		threads.forEach((o)->o.start());
		
		threads.forEach((o)->{
			try {
				o.join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		});
		
		System.out.println(t.count);
	}
}

11.锁定某一个对象时,改变对象的属性,不会影响锁的使用;但是对象的引用指向新的对象时,锁就会失效。因此需避免将锁定对象的引用指向新的对象。

12.synchronized的优化,同步代码块中只写需要被加锁的业务操作。这样做锁的粒度会变细,可以使线程争取cpu的时间变短,从而提高效率。

13.不要使用字符串常量作为锁定对象。因为你使用的某个类库中如果对某字符串加了锁,你也对该字符串加了锁,那么就会造成死锁的情况。

String str = "Hello World";

public void method() {

    //避免此操作
    //synchronized(str) { }

}

14.wait和notify方法是对象的方法,wait会释放锁,notify不会释放锁。在程序中线程wait释放锁后,要使用notify唤醒其他线程,为了让其他线程得到锁,在notify后需要使用wait释放锁,这样频繁的wait和notify会让整个通信过程比较繁琐。

为了解决上述问题,使通信简单,可以使用Latch(门闩)的await和countdownlatch方法来代替wait和notify。Latch不涉及同步,当门闩值减为0时,等待的线程便可运行。

public class MyContainer {
	
	private volatile List<Object> os = new ArrayList<>();
	
	public void add(Object o) {
		os.add(o);
	}
	
	public int size() {
		return os.size();
	}
	
	public static void main(String[] args) {
		MyContainer05 c = new MyContainer05();
		CountDownLatch count = new CountDownLatch(1);//定义一个门闩,门闩值设置为1
		
		new Thread(()-> {
				System.out.println("t2 start");
				if (c.size()!=5) {
					try {
						count.await();//线程进行等待
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				System.out.println("t2 end");
		}, "t2").start();
		
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		new Thread(()->{
				System.out.println("t1 start");
				for(int i = 0; i < 10; i++) {
					c.add(new Object());
					System.out.println("add "+ (i+1));
					if(c.size() == 5) {
						count.countDown();//门闩值减一,当为0时,门闩打开,让等待线程运行
					}
					try {
						TimeUnit.SECONDS.sleep(1);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
		}, "t1").start();
	}
}

15.reentrantlock可用于代替synchronized,但需要手动释放锁(synchronized锁是JVM自动释放),所以经常在finally中手动释放reentrantlock锁。reentrantlock可调用lockInterruptibly方法对线程的interrupt方法做出相应。

public class ReentrantLock {
	
	public static void main(String[] args) {
		
		Lock lock = new ReentrantLock();
		
		new Thread(()->{
			lock.lock();
			System.out.println("t1 start");
			try {
				TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
			} catch (InterruptedException e) {
				System.out.println("t1 interrupted");
			}finally {
				lock.unlock();
			}
			System.out.println("t1 end");
		}, "t1").start();
		
		Thread t2 = new Thread(()->{
//			lock.lock();
			try {
				lock.lockInterruptibly();//响应t2的interrupt方法
				System.out.println("t2 start");
				TimeUnit.SECONDS.sleep(2);
			} catch (InterruptedException e) {
				System.out.println("t2 interrupted");
			}finally {
				lock.unlock();
			}
			System.out.println("t2 end");
		});
		t2.start();
		
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		t2.interrupt();//打断t2的等待
	}
}

reentrantlock还可指定为公平锁,公平锁:每个线程获得cpu资源的时间相同。

public class ReentrantLock5 extends Thread{
	
	private static Lock lock = new ReentrantLock(true);//当为true时,为公平锁
	
	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 r = new ReentrantLock5();
		new Thread(r, "t1").start();
		new Thread(r, "t2").start();
	}

}

16.线程安全的单例模式。

public class Singleton {

	//构造方法私有化
	private Singleton() {
	}
	
	//静态内部类
	private static class InnerClass {
		private static Singleton s = new Singleton();
	}
	
	public static Singleton get() {
		return InnerClass.s;
	}
	
	public static void main(String[] args) {
		for (int i = 0 ; i < 5; i++) {
			new Thread(()-> {
				Singleton s = Singleton.get();
				System.out.println(s.hashCode() + "----------------");
			}).start();
		}
	}
}

17.同步容器类

Map:

1)并发不是特别高时:

Collections.synchronizedXXX

Hashtable(对整张表加锁)

2)并发比较高时:

ConcurrentHashMap(对表进行分段加锁)

3)并发比较高并且需要排序:

ConcurrentSkipListMap

 

CopyOnWrite容器(写时复制,写时效率低,但读时效率非常高,并且读时不用加锁,适合写少读多的情况):

1)CopyOnWriteArrayList

 

Queue:

1)ConcurrentLinkedQueue(Linked队列是没有界限的,到内存满之前,都可以往队列里加东西)

 

BlockingQueue(阻塞式队列,添加时如果满了就等待,取出时如果满了就等待):

添加:put 满了等待;add 满了出异常;offer 添加成功返回true,失败返回false;

1)LinkedBlockingQueue

2)ArrayBlockingQueue(固定大小队列)

3)DelayQueue(执行定时任务队列)

4)LinkedTransferQueue(能大幅度提高并发效率,因为生产者直接将消息给消费者,当没有消费者时,就会等待,因此需要消费者先运行;只有transfer方法才会这样,其他方法在没有消费者情况下,会将消息放入队列中)

5)SynchronousQueue(该队列的容量为0,所以必须有消费者才行;当没有消费者时,因为队列容量为0,所以阻塞等待消费者消费)

18.Runnable接口方法没有返回值,Callable接口方法有返回值。

19.线程池类

线程池的创建方法:

ExecutorService service = Executors.newXXX();

XXX可替代为:

1)FixedThreadPool(固定大小线程池)

2)CachedThreadPool(刚开始时没有线程,当有任务时,会起一个线程执行任务,一段时间(默认60秒)后,线程自动消失)

3)SingleThreadExecutor(该线程池中永远只有一个线程执行任务)

4)ScheduledThreadPool(该线程池中线程按照计划处理任务)

5)WorkStealingPool(1.工作窃取线程池,该线程池中的线程是守护/精灵/后台线程,主线程如果不阻塞的话,是看不见输出的,而且只要JVM不退出,他们会一直在后台执行;2.线程各自维护自己的任务队列,当某个线程执行完自己任务后,会去其他线程的任务队列中取任务来执行)

20.自定义线程池

/**
 * 底层线程池构造方法
 * @param corePoolSize 初始化线程数量
 * @param maximumPoolSize 线程最大数量
 * @param keepAliveTime 线程存活时间
 * @param unit 时间单位
 * @param workQueue 工作队列(上面17条提到的并发容器类,根据需求选择对应的阻塞队列)
 */
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) { }

//通过该语句创建自定义线程池
ExecutorService threadPoll = new ThreadPoolExecutor(XXX,XXX,XXX,XXX);

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值