线程
基本概念
- 进程:程序的⼀次执⾏过程,是系统运⾏程序的基本单位,因此进程是动态的。系统运行⼀个程序即是⼀个进程从创建,运行到消亡的过程
- 线程:一个进程的不同执行路径
线程与进程相似,但线程是⼀个比进程更小的执行单位。⼀个进程在其执行的过程中可以产生多个线
程。
线程也被称为轻量级进程 - 纤程:是用户态的线程,是线程中的线程,切换和调度不需要经过OS(操作系统)。轻量级的线程 - 纤程
创建线程的方法:
-
继承Thread类,重写run()方法:class MyThread extends Thread{};
创建实例:new MyThread().start(); -
实现Runable接口,重写run()方法:MyRun implements Runable{}
创建实例:new Thread(new MyRun()).start(); -
使用Lambda表达式:
创建实例:new Thread(
()->{System.out.println(""};}
).start();
package character01;
/**
* @author laimouren
*/
public class HowToCreateThread {
static class MyThread extends Thread{
@Override
public void run(){
System.out.println("Hello MyThread");
}
}
static class MyRun implements Runnable{
@Override
public void run(){
System.out.println("Hello MyRun");
}
}
public static void main(String[] args) {
new MyThread().start();
new Thread(new MyRun()).start();
new Thread(()->{
System.out.println("Hello lambda");
}).start();
}
}
面试题:
启动线程的三种方式?
- 继承Tread类
- 实现Runable接口
- 使用线程池:Executors.newCachedThread
线程方法
sleep():当前线程暂停一段时间,让别的线程运行,时间到了会自动回到就绪队列
yield():返回到线程等待队列继续等待,可能会刚返回又继续调用(返回绪状态)
join():在线程t1中调用t2.join(),意思是t1会等待t2线程完成之后再继续运行,可以用来保证线程之间的顺序
stop():方法不建议使用
interrupt():打断线程,业务逻辑不建议用
getState():获得线程的状态
线程状态
java线程状态迁移图
状态都是由JVM管理的,借助操作系统
创建的时候是new状态,
调用start方法时是runable状态(线程被挂起时处于ready状态,运行时处于running状态)
处于running状态时,可以通过调用各种方法处于另外三种状态TimeWaiting,Waiting,Blocked
调用结束就会进入terminated状态,之后不能再调用start方法
synchronized关键字
Hotspot底层实现synchronized:从对象头中拿出前两位作为markword来标记是否有锁
JVM没有要求
使用
多个线程访问同一个资源,该资源需要上锁(该锁不一定是锁自己,可以是其他对象)
普通方法
Object不能是String常量 Integer Long等基础数据类型
Integer类型:
i++实际上是i = new Integer(i+1),所以执行完i++后,i已经不是原来的对象了,锁的对象发生了改变,多个线程锁的对象各不相同,同步块自然就无效了。
Long 类型:同上
String类型:
是因为String定义的变量会放在常量池中,如果多个线程定义的String变量的值相等,则锁无效,他们看起来锁的是不同对象,其实是同一个对象。这种很难发现
package character01;
/**
* @author laimouren
*/
public class Synchronized {
private int count = 0;
private Object o = new Object();
public void m(){
//任何线程要执行下面的代码,都需要先获得o的锁
synchronized (o){
count--;
System.out.println(Thread.currentThread().getName()+"count = " + count);
}
}
}
因为如果每次都new一个Object太麻烦了,所有还有如下方法实现
package character01;
/**
* @author laimouren
*/
public class Synchronized01 {
private int count = 0;
public void m(){
//任何线程要执行下面的代码,都需要先获得this的锁
synchronized (this){
count--;
System.out.println(Thread.currentThread().getName()+"count = " + count);
}
}
public synchronized void m1(){
//等同于synchronized(this)
count--;
System.out.println(Thread.currentThread().getName()+"count = " + count);
}
}
static方法
package character01;
/**
* @author laimouren
*/
public class Synchronized03 {
private static int count = 0;
public synchronized static void m(){
//等同于synchronized(Synchronized03.class)
count--;
System.out.println(Thread.currentThread().getName()+"count = " + count);
}
public static void m1(){
synchronized(Synchronized03.class) {
count--;
System.out.println(Thread.currentThread().getName() + "count = " + count);
}
}
}
同步方法和非同步方法是否能被同时调用:多个线程执行时,使用synchronized关键字修饰的方法执行时能否执行没有使用该关键字的方法?
答案是可以的
面试题:
模拟银行账户,对业务写方法加锁,对业务读方法不加锁,这样行不行?
答案是如果允许脏读,则允许,否则不行,会产生脏读问题(dirtyRead)
解决方案是都加锁
package character01;
import java.util.concurrent.TimeUnit;
/**
* 模拟银行账户,对业务写方法加锁,对业务读方法不加锁,这样行不行?
* 答案是不行,会产生脏读问题(dirtyRead)
* @author laimouren
*/
public class Synchronized04 {
String name;
double balance;
public synchronized void set(String name,double balance){
this.name = name;
try{
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
this.balance = balance;
}
public /*synchronized */ double getBalance(String name){
return this.balance;
}
public static void main(String[] args) {
Synchronized04 s = new Synchronized04();
new Thread(()->s.set("zhangsan",100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(s.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(s.getBalance("zhangsan"));
}
}
可重入
意思是同一个线程的锁可以访问多个方法
同一个类中的多个方法共用一把锁
package character01;
import java.util.concurrent.TimeUnit;
/**
* @author laimouren
*/
public class Synchronized05 {
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(1);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("m2");
}
public static void main(String[] args) {
new Synchronized05().m1();
}
}
父子继承的两个类共用一把锁
package character01;
import java.util.concurrent.TimeUnit;
/**
* 父子锁可重入性
* @author laimouren
*/
public class Synchronized06 {
synchronized void m(){
System.out.println("m start");
try{
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("m end");
}
public static void main(String[] args) {
new son().m();
}
static class son extends Synchronized06{
@Override
synchronized void m(){
System.out.println("son m start");
super.m();
System.out.println("son m end");
}
}
}
异常和锁
程序执行的过程中,如果出现异常,默认情况锁会被释放
所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况
比如:在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,在第一个线程抛出异常,其他线程就会进入同步代码区,有可能会访问到异常时产生的数据
因此要非常小心的处理同步业务逻辑中的异常
package Test;
import java.util.concurrent.TimeUnit;
/**
* 程序在执行过程中,如果出现异常,默认情况锁会被释放
* 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况
* 比如,在一个web app处理过程中,多个servlet线程共同访问通一个资源,这是如果异常处理不合适
* 在第一个线程中抛出异常,其他线程就会进入同步代码去,有可能访问到异常产生是的数据
* 因此要非常小心的处理同步业务逻辑中的异常
* @author Jcon
*
*/
public class Demo11 {
int count = 0;
synchronized void test(){
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处理,然后让循环继续
}
}
}
public static void main(String[] args) {
Demo11 demo11 = new Demo11();
Runnable r = new Runnable() {
@Override
public void run() {
demo11.test();
}
};
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
底层实现
JDK早期,synchronized是重量级的,需要调用OS
后来改进:
锁升级的概念:
参考文献:《我就是厕所所长》一 二
synchronized(Object)
刚开始只有一个线程访问的时候,没有给它加锁,只是用markword记录这个线程的id(偏向锁)(占用CPU)
如果线程争用:升级为自旋锁(占用CPU,只在用户态)
自旋10次以后,升级为重量级锁去OS那申请资源(不占cpu)
锁只能升级不能降级
什么情况下使用自旋锁?
加锁的代码,如果执行时间少,线程比较少,使用自旋锁
执行时间长,线程数比较多,使用系统锁