Java多线程基础
1.1 线程的生命周期:
- 新建( new ):新创建了一个线程对象。
- 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。
- 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。
- 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行(
running )状态。阻塞的情况分三种: (一). 等待阻塞:运行( running )的线程执行 o . wait ()方法,
JVM 会把该线程放 入等待队列( waitting queue )中。 (二). 同步阻塞:运行( running
)的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。 (三).
其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join
()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。 当 sleep ()状态超时、
join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。 - 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。
1.2 新建线程
java生成线程的四种方式:
package Chapter1;
public class CreateThread {
public static void main(String[] args) {
Rabbit t1 = new Rabbit();
t1.start();
}
}
class Rabbit extends Thread {
@Override
public void run() {
System.out.println("i am t1");
}
}
/**
* 启动:使用静态代理
1)、创建真实角色
2)、创建代理角色
3)、调用start()方法 启动线程
优点:可以同时实现继承,Runnable接口方式更加通用一些。
1、避免单继承的局限性
2、便于共享资源
通过实现Runnable接口实现多线程。(用到了静态代理设计模式)
*/
public class thread2 {
/**
* 推荐使用Runnable创建线程
* 1、避免单继承的局限性
* 2、便于共享资源
*/
public static void main(String[] args) {
//1)、创建真实角色
Programmer pro = new Programmer();
//2)、创建代理角色+真实角色引用
Thread proxy = new Thread(pro);
//3)、调用start()方法 启动线程
proxy.start();
for(int i=0;i<100;i++){
System.out.println("一边聊QQ");
}
}
}
/**
* 使用Runnable 创建进程
* 1、类实现Runable接口+重写run()方法
* 2、启动多线程 使用静态代理
* 1)、创建真实角色
* 2)、创建代理角色
* 3)、调用start()方法 启动线程
*/
class Programmer implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("一边敲代码");
}
}
}
Callable 和 Future接口 Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。
优点:可以返回值,可以抛异常。
缺点:实现繁琐。
public class thread3 {
public static void main(String[] args) {
// 第三種:使用implements Callable方式,具有返回值
List<FutureTask<Integer>> resultItems1 = new ArrayList<FutureTask<Integer>>();
CountDownLatch countDownLatch3 = new CountDownLatch(2);
for (int i = 0; i < 2; i++) {
MyCallable myCallable = new MyCallable(countDownLatch3);
FutureTask<Integer> futureTask = new FutureTask<Integer>(myCallable);
new Thread(futureTask).start();
resultItems1.add(futureTask);
}
try {
countDownLatch3.await();
Iterator<FutureTask<Integer>> iterator = resultItems1.iterator();
while (iterator.hasNext()) {
try {
System.out.println(iterator.next().get());
} catch (ExecutionException e) {
e.printStackTrace();
}
}
System.out.println("callable complete...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class MyCallable implements Callable<Integer> {
CountDownLatch countDownLatch;
public MyCallable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
public Integer call() throws Exception {
try {
Thread.sleep(2000);
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
System.out.println(Thread.currentThread().getName() + ":my callable");
return sum;
} finally {
countDownLatch.countDown();
}
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class thread4 {
private static int POOL_NUM = 10;
public static void main(String[] agrs) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < POOL_NUM; i++) {
RunnableThread thread = new RunnableThread();
executorService.execute(thread);
}
}
}
class RunnableThread implements Runnable {
private int THREAD_NUM = 10;
public void run() {
for (int i = 0; i < THREAD_NUM; i++) {
System.out.println("线程" + Thread.currentThread() + i);
}
}
}
1.3 终止线程
不要使用jdk的stop()方法,可能会引起一些数据不一致的问题。
推荐以下方法:
public class demo extends Thread {
volatile boolean stopme = false;
public void stopMe() {
stopme = true;
}
@Override
public void run() {
while(true){
if (stopme){
System.out.println("exit by stop me!");
break;
}
}
//业务逻辑, 想要退出时调用
stopMe();
}
}
1.4 等待(wait)和通知(notify)
现在有多个线程A,B,C。当线程A调用了obj.wait()方法后,那么线程A就会停止继续执行,而转为等待状态。
等待到其他线程调用了obj.notify()方法为止。obj.notify()方法会随机唤醒其中一个线程。如下:
package Chapter2;
public class SimpleWaitAndNotify {
final static Object object = new Object();//通過object對象來进行多线程通信
public static class Thread1 extends Thread {
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ":thread1 start !");
try {
System.out.println(System.currentTimeMillis() + ":thread1 wait for object !");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + ":thread1 end!");
}
}
}
public static class Thread2 extends Thread {
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ":thread2 start ! notify one thread");
object.notify();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + ":thread2 end!");
}
}
}
public static class Thread3 extends Thread {
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ":thread3 start !");
try {
System.out.println(System.currentTimeMillis() + ":thread3 wait for object !");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + ":thread3 end!");
}
}
}
public static void main(String args[]) {
Thread thread1 = new Thread1();
Thread thread2 = new Thread2();
Thread thread3 = new Thread3();
thread1.start();
thread3.start();
thread2.start();
}
//wait()方法会释放目标对象的锁,而Thread.sleep()方法不会释放任何资源.
}
1.5 挂起(supend)和继续执行(resume)线程 (已废弃,不推荐使用)
1.6 等待线程结束(join)和谦让(yield)
如下所示,显示了3个join()方法:
public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
public final void join(long millis,int nanos)
- 第一个join()方法表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕。
- 第二个join()方法给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。
- 第三个join()方法表示 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
如下为一个join()参考实例:
public class JoinMain {
public volatile static int i = 0;
public static class AddThread extends Thread {
public void run() {
System.out.println("add!");
for (i = 0; i < 1000000; i++) ;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String args[]) throws InterruptedException {
AddThread at = new AddThread();
at.start();
at.join();//使用join()方法后,主线程会等待AddThread执行完毕,i输出为1000000,如果没有这条语句,i输出为0
//可以查看join的底层代码,本质即让调用线程在当前线程对象实例上等待
System.out.println(i);
at.join();
}
}
Thread.yield()的定义如下:
public static native void yield();
这是一个静态方法,一旦执行,它会使当前线程让出CPU。但是,让出CPU并不表示当前线程不执行了。当前线程在让出CPU后,还会进行CPU资源的争夺,是否能够再次被分配到就不一定了。
如下例子:
/**
* 这里是 在 Thread 子类覆盖的 run 方法 新建线程的方法
* 其中一种写法是lamda表达式
*/
public class Yield {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.print("1");
yield();
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3");
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("2");
}
});
t1.start();
t2.start();
t2.wait();
}
}
1.7 volatile与Java内存模型
Java使用了一些特殊的操作或者关键字来申明、告诉虚拟机,在这个地方,要尤其注意,不能随意变动优化目标指令。关键字volatile就是其中之一。
当你用volatile去申明一个变量时,就等于告诉了虚拟机,这个变量极有可能会被某些程序或者线程修改。
如下例子,通过volatile是无法保证i++的原子性操作的:
public class VolatileDemo {
static volatile int i = 0;
public static class PlusTask implements Runnable {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
i++;
}
}
}
public static void main(String args[]) throws InterruptedException {
long start = System.currentTimeMillis();
Thread[] threads = new Thread[100];
for (int j = 0; j < 100; j++) {
threads[j] = new Thread(new PlusTask());
threads[j].start();
}
for (int j = 0; j < 100; j++) {
threads[j].join();
}
System.out.println(i);
long end = System.currentTimeMillis();
long time = end - start;
System.out.println("耗时:" + time);
}
}
1.8 线程局部变量
ThreadLocal 解决多线程变量共享问题
-
ThreadLocal 的作用和目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。
-
ThreadLocal 的应用场景:
- 订单处理包含一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理,如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。
- 银行转账包含一系列操作: 把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法。
ThreadLocal 的 API 提供了如下的 4 个方法。
- protected T initialValue()返回当前线程的局部变量副本的变量初始值。
- T get()返回当前线程的局部变量副本的变量值,如果此变量副本不存在,则通过 initialValue() 方法创建此副本并返回初始值。
- void set(T value)设置当前线程的局部变量副本的变量值为指定值。
- void remove()删除当前线程的局部变量副本的变量值。在实际使用中,我们一般都要重写 initialValue() 方法,设置一个特定的初始值。
简单示例:
首先,我们来看看不考虑多线程共享数据的情况。现在有小明、小刚、小红三人在同一家银行,分别向各自账户存入 200 元钱:
public class MainTest {
public static void main(String[] args) {
Bank bank = new Bank();
Thread xMThread = new Thread(() -> bank.deposit(200), "小明");
Thread xGThread = new Thread(() -> bank.deposit(200), "小刚");
Thread xHThread = new Thread(() -> bank.deposit(200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
private int money = 1000;
public void deposit(int money) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "--当前账户余额为:" + this.money);
this.money += money;
System.out.println(threadName + "--存入 " + money + " 后账户余额为:" + this.money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:小明–当前账户余额为:1000
小红–当前账户余额为:1000
小红–存入 200 后账户余额为:1400
小刚–当前账户余额为:1000
小刚–存入 200 后账户余额为:1600
小明–存入 200 后账户余额为:1200
结果是除了小明存钱和自己账户余额能对上外,小刚和小红也都只存了 200,但他们的账户余额分别多了 200 和 400?这是因为多个线程共享了同一个实例对象的局部变量所致。使用 ThreadLocal 保存对象的局部变量。package com.wuxianjiezh.demo.threadpool;
import java.util.function.Supplier;
public class MainTest {
public static void main(String[] args) {
Bank bank = new Bank();
Thread xMThread = new Thread(() -> bank.deposit(200), "小明");
Thread xGThread = new Thread(() -> bank.deposit(200), "小刚");
Thread xHThread = new Thread(() -> bank.deposit(200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
// 初始化账户余额为 100
ThreadLocal<Integer> account = ThreadLocal.withInitial(new Supplier<Integer>() {
@Override
public Integer get() {
return 1000;
}
});
public void deposit(int money) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "--当前账户余额为:" + account.get());
account.set(account.get() + money);
System.out.println(threadName + "--存入 " + money + " 后账户余额为:" + account.get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:小明–当前账户余额为:1000
小红–当前账户余额为:1000
小红–存入 200 后账户余额为:1200
小刚–当前账户余额为:1000
小刚–存入 200 后账户余额为:1200
小明–存入 200 后账户余额为:1200
可以看到,我们要的效果达到了。各线程间同时操作自己的变量,相互间没有影响。
ThreadLocal 与 Thread 同步机制的比较
- 同步机制采用了以时间换空间方式,通过对象锁保证在同一个时间,对于同一个实例对象,只有一个线程访问。
- ThreadLocal 采用以空间换时间方式,为每一个线程都提供一份变量,各线程间同时访问互不影响。
1.9 多线程共享数据
在 Java 传统线程机制中的共享数据方式,大致可以简单分两种情况:
➢ 多个线程行为一致,共同操作一个数据源。 也就是每个线程执行的代码相同,可以使用同一个 Runnable 对象,这个 Runnable 对象中有那个共享数据,例如,卖票系统就可以这么做。
➢ 多个线程行为不一致,共同操作一个数据源。也就是每个线程执行的代码不同,这时候需要用不同的Runnable 对象。例如,银行存取款。
下面我们通过两个示例代码来分别说明这两种方式。
如果每个线程执行的代码相同,可以使用同一个 Runnable 对象,这个 Runnable 对象中有那个共享数据,例如,
买票系统就可以这么做。
public class demo2 {
/**
*共享数据类
**/
static class ShareData{
private int num = 10 ;
public synchronized void inc() {
num++;
System.out.println(Thread.currentThread().getName()+": invoke inc method num =" + num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
*多线程类
**/
static class RunnableCusToInc implements Runnable{
private ShareData shareData;
public RunnableCusToInc(ShareData data) {
this.shareData = data;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
shareData.inc();
}
}
}
public static void main(String[] args) {
ShareData shareData = new ShareData();
for (int i = 0; i < 4; i++) {
new Thread(new RunnableCusToInc(shareData),"Thread "+ i).start();
}
}
}
如果inc()方法不加上synchronized 最终的结果明显得数不够,如下:
Thread 0: invoke inc method num =11
Thread 2: invoke inc method num =13
Thread 1: invoke inc method num =12
Thread 3: invoke inc method num =14
Thread 0: invoke inc method num =16
Thread 3: invoke inc method num =17
Thread 1: invoke inc method num =16
Thread 2: invoke inc method num =16
Thread 1: invoke inc method num =19
Thread 0: invoke inc method num =21
Thread 2: invoke inc method num =20
Thread 3: invoke inc method num =19
Thread 3: invoke inc method num =22
Thread 1: invoke inc method num =24
Thread 0: invoke inc method num =23
Thread 2: invoke inc method num =25
Thread 3: invoke inc method num =26
Thread 2: invoke inc method num =27
Thread 1: invoke inc method num =29
Thread 0: invoke inc method num =28Process finished with exit code 0
如果每个线程执行的代码不同,这时候需要用不同的 Runnable 对象,有如下两种方式来实现这些 Runnable 对
象之间的数据共享:
1. 将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个 Runnable 对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。
public class demo3 {
public static void main(String[] args) {
ShareData shareData = new ShareData();
for (int i = 0; i < 4; i++) {
if(i%2 == 0){
new Thread(new RunnableCusToInc(shareData),"Thread "+ i).start();
}else{
new Thread(new RunnableCusToDec(shareData),"Thread "+ i).start();
}
}
}
}
/**
*共享数据类
**/
class ShareData{
private int num = 10 ;
public synchronized void inc() {
num++;
System.out.println(Thread.currentThread().getName()+": invoke inc method num =" + num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void dec() {
num--;
System.err.println(Thread.currentThread().getName()+": invoke dec method num =" + num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//封装共享数据类
class RunnableCusToInc implements Runnable{
//封装共享数据
private ShareData shareData;
public RunnableCusToInc(ShareData data) {
this.shareData = data;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
shareData.inc();
}
}
}
//封装共享数据类
class RunnableCusToDec implements Runnable{
//封装共享数据
private ShareData shareData;
public RunnableCusToDec(ShareData data) {
this.shareData = data;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
shareData.dec();
}
}
}
2. 将这些 Runnable 对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable 对象调用外部类的这些方法。
public class demo5 {
public static void main(String[] args) {
//公共数据
final ShareData shareData = new ShareData();
for (int i = 0; i < 4; i++) {
if (i % 2 == 0) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
shareData.inc();
}
}
}, "Thread " + i).start();
} else {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
shareData.dec();
}
}
}, "Thread " + i).start();
}
}
}
}
class ShareData {
private int num = 10;
public synchronized void inc() {
num++;
System.out.println(Thread.currentThread().getName() + ": invoke inc method num = " + num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void dec() {
num--;
System.err.println(Thread.currentThread().getName() + ": invoke dec method num = " +
"" + num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
参考资料:《实战Java高并发程序设计》