进程
线程:一个进程的不同执行路径
纤程
创建线程的方法:
-
继承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;
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方法
Hotspot底层实现synchronized是对象头中拿出前两位来标记是否有锁
JVM没有要求
作用:
Object不能是String常量 Integer Long等基础数据类型
因为String常量都是被引用同一个
package character01;
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;
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;
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;
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 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;
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;
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线程共同访问同一个资源,这时如果异常处理不合适,在第一个线程抛出异常,其他线程就会进入同步代码区,有可能会访问到异常时产生的数据
因此要非常小心的处理同步业务逻辑中的异常
代码了解即可,不做展示
JDK早期,synchronized是重量级的,需要调用OS
后来改进:
锁升级的概念:
参考文献:《我就是厕所所长》一 二
synchronized(Object)
markword记录这个线程的id(偏向锁)
如果线程争用:升级为自旋锁
自旋10次以后,升级为重量级锁去OS那申请资源(不占cpu)
加锁的代码,如果执行时间少,线程比较少,使用自旋锁
执行时间长,线程数比较多,使用系统锁



