一、基本概念
- 进程:硬盘有一个程序叫QQ.exe,这是一个程序,这个程序是一个静态概念,双击启动,登录进去,这叫一个进程。进程相对于程序来说是一个动态的概念。
- 线程:作为一个进程里面最小的执行单元他就叫线程,一个程序里不同的执行路径就叫一个线程。
二、创建线程几种方式
- new Thread().start();
- new Thread(Runnable).start();
- Executors.newCachedThreadPool();(或者FutureTask+Callable)
三、线程的几种状态
- new:新建状态
- Runnable:就绪状态(start后)
- Ready:就绪状态
- Running:运行状态
- Terminated:结束状态
- TimedWaiting:等待状态
- Waiting:等待状态
- Blocked:阻塞状态(没得到锁阻塞,得到锁就绪)
ps:getState()方法可以获取线程状态
四、线程的几个方法
sleep:当前线程暂停一段时间让给别的线程运行。到达了规定睡眠时间,自动复活。就绪状态
yield:当前线程正在执行的时候停下来进入等待队列,让出CPU,等待系统调度,依然有可能调度到它继续执行。就绪状态
join:两个线程t1,t2,当线程t1中执行了t2.join();t1跑去t2执行,即t1等待t2执行完毕再运行(自己join自己是没有任何意义的)
五、synchronized加锁方式
- 加锁方式
- 类锁一
public void add(int m){ synchronized (Account.class){ String name = Thread.currentThread().getName(); System.out.println("类锁添加" + m + "钱," + name + "添加后:" + (count+=m)); } }
- 类锁二
public static synchronized void add(int m){ String name = Thread.currentThread().getName(); System.out.println("类锁添加" + m + "钱," + name + "添加后:" + (count+=m)); }
- 对象锁一
public void add(int m){ synchronized(this){ String name = Thread.currentThread().getName(); System.out.println("对象锁添加" + m + "钱," + name + "添加后:" + (count+=m)); } }
- 对象锁二
public synchronized void add(int m){ String name = Thread.currentThread().getName(); System.out.println("对象锁添加" + m + "钱," + name + "添加后:" + (count+=m)); }
- 类锁一
总结:
1.类锁是对静态方法使用synchronized关键字后,无论是多线程访问单个对象还是多个对象的sychronized块,都是同步的。
2.对象锁是实例方法使用synchronized关键字后,如果是多个线程访问同个对象的sychronized块,才是同步的,但是访问不同对象的话就是不同步的。
3.类锁和对象锁是两种不同的锁,可以同时使用,但是注意类锁不要嵌套使用,这样子容易发生死锁。
六、synchronized可重入概念
- 重入锁概念:拿到了这把锁后不停地加锁,加了很多道,但锁定的对象还是同一个,去掉一道减一。
- synchronized的一个属性:可重入
-
如果一个同步方法调用另一个同步方法,有一个方法加了锁,另一个方法也需要加锁,加的是同一把锁也是同一个线程,那这个时候申请仍然会得到该对象的锁。
/** * 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁. * 也就是说synchronized获得的锁是可重入的 * @author mashibing */ import java.util.concurrent.TimeUnit; public class T { synchronized void m1() { System.out.println("m1 start"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } m2(); System.out.println("m1 end"); } synchronized void m2() { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m2"); } public static void main(String[] args) { new T().m1(); } }
-
七、异常锁
- 程序在执行过程中,如果出现异常,默认情况锁会被释放,所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。因此要非常小心的处理同步业务逻辑中的异常。
import java.util.concurrent.TimeUnit; public class T { int count = 0; synchronized void m() { System.out.println(Thread.currentThread().getName() + " start"); while(true) { count ++; System.out.println(Thread.currentThread().getName() + " count = " + count); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } if(count == 5) { int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续 System.out.println(i); } } } public static void main(String[] args) { T t = new T(); Runnable r = new Runnable() { @Override public void run() { t.m(); } }; new Thread(r, "t1").start(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(r, "t2").start(); } }
八、synchronized底层实现(锁升级过程)
- 早期jdk,它是重量级的,就是synchronized要去OS申请锁,效率很低
- 改进后,锁升级(原本要去OS申请锁,现在当我们使用时,HosPot实现方式如下:synchronized(Object)加锁后,来了之后再Object头上MarkWord记录这个线程,如果第一个线程访问时候实际上是没有给Object加锁的,内部实现只是记录这个线程ID(偏向锁))
- 偏向锁如果有线程争用,就升级自旋锁(自身旋转占用CPU,默认10次)
- 自旋10次后升级为重量锁,即去OS申请资源。
关于自旋锁和重量级锁的选择:1. 加锁的代码执行时间段,线程数量少用自旋。2. 执行时间长线程数多,用系统(OS)锁