6-01 Java多线程编程
目录
一.线程的概念
二.创建线程
三.中断线程
四.线程常用方法
五.线程同步与交互
七.代码实现的同步和交互Lock
八.线程池
九.线程工具类
一.线程的概念
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。
比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
线程的五种状态:
新生状态(New):当一个线程的实例被创建即使用new关键字和Thread类或其子类创建一个线程对象后,
此时该线程处于新生状态。 此时线程不是活着的(not alive);
就绪状态(Runnable):通过调用线程实例的start()方法来启动线程使线程进入就绪状态;
但还没有被分配到CPU,处于线程就绪队列;此时线程是活着的(alive);
运行状态(Running):一旦获取CPU,线程就进入运行状态,线程的run()方法才开始被执行;
如果在给定的时间内没有执行结束,就会被系统给换下来回到线程的就绪状态;
此时线程是活着的(alive);
阻塞状态(Blocked):通过调用join()、sleep()、wait()或者资源被暂用使线程处于阻塞状态;
此时线程是活着的(alive)
死亡状态(Dead):当一个线程的run()方法运行完毕或被中断或被异常退出,该线程到达死亡状态。
处于Dead状态调用start()方法,会出现异常
二.创建线程
创建线程三种方式:
1. 继承Thread类
2. 实现Runnable接口
3. 匿名类的方式
//方法一 继承 Thread类方式:
public class TestThread extends Thread {
@Override
public void run() {
System.out.println("线程处理逻辑");
}
}
TestThread testThread=new TestThread();
testThread.start();
//方法二 实现 Runnable接口方式:
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println("线程处理逻辑..");
}
};
Thread thread=new Thread(runnable);
thread.start();
//方法三 匿名类方式:
Thread thread=new Thread(){
@Override
public void run() {
System.out.println("线程处理逻辑...");
}
};
thread.start();
三.中断线程
四.线程常用方法
1.join 线程等待
所有进程,至少会有一个线程即主线程,即main方法开始执行,就会有一个看不见的主线程存在。
在执行t.join,即表明在主线程中加入该线程。主线程会等待该线程结束完毕, 才会往下运行。
public static void main(String[] args){
System.out.println(" main 线程开始执行...");
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println(" t1开始执行..");
try {
Thread.sleep(2000);
System.out.println(" t1执行完毕..");
}catch (InterruptedException e){
e.printStackTrace();
}
}
};
Thread t1=new Thread(runnable);
t1.start();
//代码执行到这里,一直是main线程在运行
try{
//t1线程加入到main线程中来,只有t1线程运行结束,才会继续往下走
t1.join();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("t1线程执行完了..main 线程又重新开始执行");
}
2.守护线程
守护线程的概念是: 当一个进程里,所有的线程都是守护线程的时候,结束当前进程。
守护线程通常会被用来做日志,性能统计等工作。
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println("线程处理逻辑..");
}
};
Thread thread=new Thread(runnable);
thread.setDaemon(true);
thread.start();
五.线程同步与交互
1.在方法上加 synchronized
在recover前,直接加上synchronized ,其所对应的同步对象,就是this 和hurt方法达到的效果是一样
外部线程访问gareen的方法,就不需要额外使用synchronized 了
//回血
//直接在方法前加上修饰符synchronized
//其所对应的同步对象,就是this
//和hurt方法达到的效果一样
public synchronized void recover(){
hp=hp+1;
}
//掉血
public void hurt(){
//使用this作为同步对象
synchronized (this) {
hp=hp-1;
}
}
2.同步
public class Count {
private int n=0;
//每次加一
public void add(){
this.n++;
}
//每次减一
public void sub(){
this.n--;
}
public int getN() {
return n;
}
public void setN(int n) {
this.n = n;
}
}
//加线程
public class AddThread extends Thread {
private Count count;
public AddThread(Count count){
this.count=count;
}
@Override
public void run() {
System.out.println("加线程"+Thread.currentThread().getName()+"等待锁");
synchronized (count){
System.out.println("加线程"+Thread.currentThread().getName()+"获得锁");
count.add();
System.out.println("加线程"+Thread.currentThread().getName()+"释放锁");
}
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
//减线程
public class SubThread extends Thread {
private Count count;
public SubThread(Count count){
this.count=count;
}
@Override
public void run() {
System.out.println("减线程"+Thread.currentThread().getName()+"等待锁");
synchronized (count){
System.out.println("减线程"+Thread.currentThread().getName()+"获得锁");
count.sub();
System.out.println("减线程"+Thread.currentThread().getName()+"释放锁");
}
try {
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args){
System.out.println(" main 线程开始执行...");
int n=1000;
Thread[] addThreads = new Thread[n];
Thread[] subThreads = new Thread[n];
final Count count=new Count();
for (int i=0;i<n;i++){
AddThread t1=new AddThread(count);
t1.start();
addThreads[i]=t1;
SubThread t2=new SubThread(count);
t2.start();
subThreads[i]=t2;
}
for (Thread addThread:addThreads){
try {
addThread.join();
}catch (Exception e){
e.printStackTrace();
}
}
for (Thread subThread:subThreads){
try {
subThread.join();
}catch (Exception e){
e.printStackTrace();
}
}
System.out.println("结果为:"+count.getN());
}
3.交互 wait 和 notifyAll
这里需要强调的是,wait方法和notify方法,并不是Thread线程上的方法,它们是Object上的方法。
因为所有的Object都可以被用来作为同步对象,所以准确的讲,wait和notify是同步对象上的方法。
wait()的意思是: 让占用了这个同步对象的线程,临时释放当前的占用,并且等待。 所以调用wait是有前提条件的,一定是在synchronized块里,否则就会出错。
notify() 的意思是,通知一个等待在这个同步对象上的线程,你可以苏醒过来了,有机会重新占用当前对象了。
notifyAll() 的意思是,通知所有的等待在这个同步对象上的线程,你们可以苏醒过来了,有机会重新占用当前对象了。
public static void main(String[] args){
System.out.println(" main 线程开始执行...");
final Object obj=new Object();//同步对象
Hero hero=new Hero();
Thread t1=new Thread(){
@Override
public void run() {
while (true) {
synchronized (obj) {
if (hero.hp <= 1) {
try {
System.out.println("线程" + Thread.currentThread().getName() + "正在等待");
obj.wait();
System.out.println("线程" + Thread.currentThread().getName() + "等待结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
hero.hurt();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
Thread t2=new Thread(){
@Override
public void run() {
while (true) {
synchronized (obj) {
hero.recover();
// 通知那些等待在this对象上的线程,可以醒过来了
obj.notifyAll();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t2.start();
}
七.代码实现的同步和交互Lock
1.线程同步
与 synchronized (someObject) 类似的,lock()方法,表示当前线程占用lock对象,一旦占用,其他线程就不能占用了。
与 synchronized 不同的是,一旦synchronized 块结束,就会自动释放对someObject的占用。 lock却必须调用unlock方法进行手动释放,为了保证释放的执行,往往会把unlock() 放在finally中进行。
2.线程交互
使用synchronized方式进行线程交互,用到的是同步对象的wait,notify和notifyAll方法
Lock也提供了类似的解决办法,首先通过lock对象得到一个Condition对象,然后分别调用这个Condition对象的:await, signal,signalAll 方法
注意: 不是Condition对象的wait,nofity,notifyAll方法,是await,signal,signalAll
//线程交互使用
//condition.await();
//condition.signal();
//condition.signalAll();
public static void main(String[] args){
System.out.println(" main 线程开始执行...");
final Object obj=new Object();
Hero hero=new Hero();
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
Thread t1=new Thread(){
@Override
public void run() {
while (true) {
lock.lock();
if (hero.hp <= 1) {
try {
System.out.println("线程" + Thread.currentThread().getName() + "正在等待");
condition.await();
System.out.println("线程" + Thread.currentThread().getName() + "等待结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
hero.hurt();
lock.unlock();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
Thread t2=new Thread(){
@Override
public void run() {
while (true) {
lock.lock();
hero.recover();
// 通知那些等待在this对象上的线程,可以醒过来了
condition.signalAll();
lock.unlock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t2.start();
}
八.线程池
线程池的思路和生产者消费者模型是很接近的。
- 准备一个任务容器
- 一次性启动10个 消费者线程
- 刚开始任务容器是空的,所以线程都wait在上面。
- 直到一个外部线程往这个任务容器中扔了一个“任务”,就会有一个消费者线程被唤醒notify
- 这个消费者线程取出“任务”,并且执行这个任务,执行完毕后,继续等待下一次任务的到来。
- 如果短时间内,有较多的任务加入,那么就会有多个线程被唤醒,去执行这些任务。
在整个过程中,都不需要创建新的线程,而是循环使用这些已经存在的线程
public class ThreadPool {
int threadPoolSize;
LinkedList<Runnable> tasks=new LinkedList<>();
public ThreadPool(){
threadPoolSize=10;
synchronized (tasks) {
for (int i = 0; i < 10; i++) {
new TaskConsumerThread("任务消费者线程 " + i).start();
}
}
}
public void add(Runnable runnable){
synchronized (tasks){
tasks.add(runnable);
// 唤醒等待的任务消费者线程
tasks.notifyAll();
}
}
class TaskConsumerThread extends Thread{
public TaskConsumerThread(String name){
super(name);
}
Runnable task;
@Override
public void run() {
System.out.println("启动: " + this.getName());
while (true){
synchronized (tasks){
while (tasks.isEmpty()){
try {
tasks.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
task=tasks.removeLast();
// 允许添加任务的线程可以继续添加任务
tasks.notifyAll();
}
System.out.println(this.getName() + " 获取到任务,并执行");
task.run();
}
}
}
}
public static void main(String[] args){
System.out.println(" main 线程开始执行...");
ThreadPool pools=new ThreadPool();
int sleep=1000;
while(true){
Runnable runnable=new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
};
pools.add(runnable);
//
try {
Thread.sleep(sleep);
sleep=sleep>100?sleep-100:sleep;
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
九.线程工具类
调用Thread.currentThread()获取当前线程。
JDK提供了ThreadLocal,在一个线程中传递同一个对象。
ThreadLocal表示线程的“局部变量”,它确保每个线程的ThreadLocal变量都是各自独立的。
ThreadLocal适合在一个线程的处理流程中保持上下文(避免了同一参数在所有方法中传递)使用ThreadLocal要用try … finally结构。
还没有评论,来说两句吧...