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);