1、基本概念
进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
线程: 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
创建线程的几种方式
public class T02_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!");
}
}
static class MyCall implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("Hello MyCall");
return "success";
}
}
public static void main(String[] args) {
// 第一种
new MyThread().start();
// 第二种
new Thread(new MyRun()).start();
// 第三种 Lambda
new Thread(() -> {
System.out.println("Hello Lambda!");
}).start();
// 第四种
Thread t = new Thread(new FutureTask<String>(new MyCall()));
t.start();
// 第五种:线程池。
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> {
System.out.println("Hello ThreadPool");
});
service.shutdown();
}
}
//请你告诉我启动线程的三种方式 1:Thread 2: Runnable 3:Executors.newCachedThrad(实际也是用的前两种)
线程常用的方法:
- join():调用join方法的线程强制执行,其他线程处于阻塞状态,等该线程执行完后,其他线程再执行。有可能被外界中断产生InterruptedException 中断异常。
- sleep():在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。休眠的线程进入阻塞状态。
- yield():调用yield方法的线程,会礼让其他线程先运行。(让出CPU大概率其他线程先运行,小概率自己还会运行)
- Thread.currentThead():获取当前线程对象
- getPriority():获取当前线程的优先级
- setPriority():设置当前线程的优先级
- 注意:线程优先级高,被CPU调度的概率大,但不代表一定会运行,还有小概率运行优先级低的线程。
- isAlive():判断线程是否处于活动状态 (线程调用start后,即处于活动状态)
- interrupt():中断线程
- wait():导致线程等待,进入堵塞状态。该方法要在同步方法或者同步代码块中才使用的
- notify():唤醒当前线程,进入运行状态。该方法要在同步方法或者同步代码块中才使用的
- notifyAll():唤醒所有等待的线程。该方法要在同步方法或者同步代码块中才使用的
常见的线程状态:
- New(新建状态): 当用new操作符创建一个新线程时,如 new Thread(r), 该线程还没有开始运行还没有调用.start()。这意味着它的状态是new。
- Runnable(可运行): 一旦调用start()方法,他就会被线程调度器来执行,也就是操作系统来执行,线程就处于runnable状态。
- Ready就绪状态:就绪状态是说扔到CPU的等待队列里面去排队等待CPU运行
- Running运行状态:等真正扔到CPU上运行的时候才叫Running运行状态(调用yiled时候会从Running状态跑到Ready状态去,线程调度器选中执行的时候又从Ready状态跑到Running状态去)
- 结束状态:如果你线程顺利的执行完了就会进去(注意:Teminated完了之后是不可以回到new状态在调用start,完了就是结束了)
- TimedWaiting等待:a按照时间等待,等时间结束自己就回去了,Thread.sleep(time),o.wait(time),t.join(time),LockSupport.parkNanos(),LockSupport.parkUntil()这些都是关于时间等待的方法。
- ,LockSUWaiting等待:在运行的时候如果调用了o.wait()、t.join()、LockSupport.park()进入Waiting状态,调用notify()、notifyAll()、LockSupport.unpark()又回到Running状态
- Blocked阻塞:在同步代码块的情况下没有得到锁就会阻塞状态,获取锁的时候就是就绪状态。
上面的这些状态全是由JVM管理的,因为JVM管理的时候也要通过操作系统,所以呢,那个是操作系统和哪个是JVM时分不开的,JVM是跑在操作系统上的一个普通程序。
synchronized
- 保证原子性也保证可见性
- 可重入性
对某个对象加锁:
public class T {
private int count = 10;
private Object o = new Object();
public void m() {
synchronized(o) { //任何线程要执行下面的代码,必须先拿到o的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
synchronized(this):鉴于上述每次都要重新new一个对象,所以用锁定当前对象就行。
public class T {
private int count = 10;
public void m() {
synchronized(this) { //任何线程要执行下面的代码,必须先拿到this的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
public static void main(String[] args) {
T t = new T();
t.m();
}
}
或者可以写成:(和上述等同)
public class T {
private int count = 10;
public synchronized void m() { //等同于在方法的代码执行时要synchronized(this)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
静态方法时:是没有this对象的,你不需要new出一个对象来就能执行这个方法,但如果这个上面加一个synchronized的话就代表synchronized(T.class)这里这个synchronized(T.class)锁的就是T类的对象
public class T {
private static int count = 10;
public synchronized static void m() { //这里等同于synchronized(FineCoarseLock.class)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void mm() {
synchronized(T.class) { //考虑一下这里写synchronized(this)是否可以?
count --;
}
}
public static void main(String[] args) {
m();
mm();
}
}
补充:同一个ClassLoader空间的class load到内存它是单例的,不同类加载器就不是的,不同类加载器互相之间也不能访问,所以能访问,就是单例。
synchronized既能保证原子性,又保证了可见性(前提是通过了同一把锁,singleton的代码不满足这个条件)
public class T implements Runnable {
private /*volatile*/ int count = 10;
public synchronized void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
for(int i=0; i<5; i++) {
T t = new T();
new Thread(t, "THREAD" + i).start();
}
}
}
同步代码和非同步代码是否可以同时调用:
public class T {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + " m1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m1 end");
}
public void m2() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
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();*/
new Thread(t::m1, "t1").start();
new Thread(t::m2, "t2").start();
/*
//1.8之前的写法
new Thread(new Runnable() {
@Override
public void run() {
t.m1();
}
});
*/
}
}
t1 m1 start...
t2 m2
t1 m1 end
如果业务允许脏读就可以不用对读过程加锁。
public class Account {
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) {
Account a = new Account();
new Thread(()->a.set("zhangsan", 100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
}
}
synchronized可重入
一个同步方法可以调用另一个同步方法,一个线程可以拥有某个对象得锁,再次申请得时候仍然会得到该对象得锁。也就是说synchronized获得锁是可重入的。
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();
}
}
父类调用子类的概念,父类synchronized,子类调用super.m的时候必须可重入,否则就会出问题(调用父类是同一把锁)。所谓的重入锁就是你拿到这把锁之后不停枷锁枷锁,加好几道锁,但锁定的还是同一个对象,去一道就减个1.
public class T {
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 TT().m();
}
}
class TT extends T {
@Override
synchronized void m() {
System.out.println("child m start");
super.m();
System.out.println("child m end");
}
}
异常锁:程序在执行过程中如果出现异常,默认情况锁会被释放,所以在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。
比如:在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。
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得底层实现是重量级得,都会要去找操作系统申请锁得地步,这就会造成synchronized效率非常低
- 改进后,才有了锁升级得概念(可以参考:没错,我就是厕所所长!(二)),当我们使用synchronized得时候HotSpot得实现实在何样的,上来之后第一个去访问某把锁线程比如sync(Object),来了之后现在这个Object得头上面markword记录这个线程。如果只有第一个线程访问得时候实际上是没有给这个objet枷锁得,在内部实现得时候,只是记录这个线程得ID(偏向锁)。偏向锁如果有线程争用得话,就升级为自旋锁概念就是用一个while得循环在这进行自旋(升级为自旋锁),jdk1.6规定10次就会再次升级为重量级锁,重量级锁就是去操作系统那里去申请资源。这是个锁升级得过程。(锁升级过程是不可逆得)