java中创建线程有多种方案:
继承Thread类
通过继承Thread类并重写其run方法来创建线程,新类的实例可以通过调用start方法开始执行run方法在一个全新的线程中。
class MyThread extends Thread {@Overridepublic void run() {System.out.println("MyThread is running");}
}
public class Main {public static void main(String[] args) {MyThread thread = new MyThread();thread.start(); // 启动线程}
}
实现Runnable接口
通过实现Runnable接口(Runnable接口也有run方法),并将其实例传递给Thread对象来创建线程。
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("MyRunnable is running");}
}
public class Main {public static void main(String[] args) {Thread thread = new Thread(new MyRunnable());thread.start(); // 启动线程}
}
使用Callable接口和FutureTask
继承Callable接口创建新类,并创建一个实例传入FutureRask类的构造函数创建另一个实例,然后将其传递给Thread对象,其中真正执行的是call方法中的内容,与上面两者不同的是这个call的返回值可以通过调用FutureTask的实例的get方法获得。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {return "MyCallable is running";}
}
public class Main {public static void main(String[] args) {FutureTask<String> futureTask = new FutureTask<>(new MyCallable());Thread thread = new Thread(futureTask);thread.start(); // 启动线程try {String result = futureTask.get(); // 获取线程执行结果System.out.println(result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}
}
前两种书写简单,较好理解,都是通过实现run方法,再通过调用线程实力的start方法在新线程上执行run方法,不同的是第二种是实现的接口,而实现接口意味着它可以实现很多接口,并且继承其他类,有更好的扩展性(实际上也不常用),而第三种则比较复杂,传来传去,最终还要通过调用get方法获取线程的返回值,那他是怎么做到的呢?以下的源码实现:
可以看到整个过程极为粗劣,其实就是多传入了一个函数(Callable类的实例的call方法),然后在run方法中调用这个方法,并且保存这个方法的返回值,而get方法则是原地循环阻塞等待值,并且返回,整个过程非常简单,没有亮点。所以其实这个方法虽然复杂,但也不是很好,相比之下,还是第一种简单好用。(实际开发中还是直接用线程池的多)
操作线程的常用方法
1. Object类的方法
这些方法必须在同步块或同步方法(synchronized,后面会讲)中调用,否则会抛出IllegalMonitorStateException。
-
wait():
-
使当前线程等待,直到其他线程调用同一对象的notify()或notifyAll()方法,或者等待时间超时。
synchronized (lock) {while (!condition) {lock.wait(); // 或 lock.wait(timeout);}// 继续执行 }
-
-
notify():
-
唤醒调用此对象上的wait方法进入等待的单个线程。
synchronized (lock) {lock.notify(); }
-
-
notifyAll():
-
唤醒调用此对象上的wait方法进入等待的所有线程。
synchronized (lock) {lock.notifyAll(); }
-
2. Thread类的方法
-
join():
-
等待调用此方法的线程实例终止后在执行当前线程。
Thread t = new Thread(() -> {// 线程任务 }); t.start(); t.join(); // 等待 t 线程结束
-
-
sleep():
-
使当前线程休眠指定时间。
try {Thread.sleep(1000); // 休眠1秒 } catch (InterruptedException e) {e.printStackTrace(); }
-
-
yield():
-
暂停当前正在执行的线程对象,并执行其他线程。
Thread.yield();
-
-
interrupt():
-
中断线程,设置线程的中断标志(如果线程此时处于wait或sleep状态则直接报错)。
Thread t = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {// 线程任务} }); t.start(); t.interrupt(); // 中断 t 线程
-
-
isInterrupted():
-
检查线程是否被中断。
if (t.isInterrupted()) {// 线程被中断 }
-
-
interrupted():
-
检查当前线程是否被中断,并清除中断状态。
if (Thread.interrupted()) {// 当前线程被中断,且中断状态已被清除 }
-
多线程通过操作会引发数据安全问题,加入说淘宝购买一件东西,而这个东西库存只剩一个,那么当两个人同时购买时涉及的逻辑包括判断是否有库存,然后库存减一,如果一方检测有库存,但还没来的及减一,此时另一个人的线程也开始检测库存,发现也有库存,此时剩余的一个库存就会被减两次变成负一,为了避免这种情况,出现了线程锁的机制,线程锁就是通过给资源上锁,而只有拿到这个锁的线程才可以操作资源,以达到多线程同步操作的结果,上面说的同步块和同步方法(synchronized)就是其中之一,实现方法如下:
1.同步块
同步块通过定义一个代码块,并且通过小括号传入一个对象作为锁来实现(这个对象必须对于多个线程是相同的不变的)
public class Example {private final Object lock = new Object();
public void someMethod() {synchronized (lock) {// 同步代码块// 只有持有lock对象锁的线程才能进入此代码块System.out.println("Thread " + Thread.currentThread().getName() + " is executing synchronized block.");}}
}
在上述代码中,synchronized (lock)表示当前代码块被lock对象锁保护,只有获得lock对象锁的线程才能进入该代码块执行。这种方式并不限定哪个线程来执行,只要某个线程持有了lock对象的锁,它就可以执行同步块中的代码。而其他的线程必须排队等候。
2.同步方法
同步方法用于同步整个方法,非静态方法的锁则是这个实例本身,而静态方法的锁则是这个类对象。
public class Example {public synchronized void someMethod() {// 同步方法// 只有持有当前对象锁的线程才能进入此方法System.out.println("Thread " + Thread.currentThread().getName() + " is executing synchronized method.");}public static synchronized void someStaticMethod() {// 静态同步方法// 只有持有该类类锁的线程才能进入此方法System.out.println("Thread " + Thread.currentThread().getName() + " is executing synchronized static method.");}
}
在上述代码中,someMethod()是一个实例同步方法,表示当前方法被该对象实例的锁保护。someStaticMethod()是一个静态同步方法,表示当前方法被该类的类锁保护。任何一个线程在调用这些同步方法时,必须先获取对应的对象锁或类锁。
有人可能会说同步块比同步方法好,因为同步块可以指定一部分代码代码逻辑上锁,而不用整个方法上锁,这就可以让尽可能多的代码逻辑让线程异步执行,提高性能,而同步方法则必须是整个方法,实则不然,实际上同步方法也可以讲内部一部分逻辑封装成一个方法变为同步方法,而取消外部的同步方法,一样能实现逻辑。
对于synchronized(同步块和同步方法机制相同,这里统一用字母代替),JVM做了大量优化:
偏向锁:当一个线程持有synchronized的锁时,并且之后没有其他线程尝试获取这把锁,那么该synchronized会一直将锁交给上一个进程,这样当上一次进程再次访问synchronized时,则不需要重复获取锁
轻量级锁和重量级锁:当一个进程获取锁失败时,会重新获取,这个过程叫自旋,整个状态叫轻量级锁。当自旋达到一定次数时,证明当前进程过多,此时进程会挂起,等待锁空闲后被唤醒,这个状态叫重量级锁。
锁消除:当JVM检测到一个锁并没有可能被其他线程获取时(也就是synchronized定义的没有意义),此时锁会被回收
锁粗化:当JVM检测到有一系列连续的获取锁的过程,那么他会将这些过程公用一把更大的锁
未完待续。。。。