线程(一)

线程 Thread

1)应用程序以进程为单位运行,一个进程之内可以分为一到多个线程
2)操作系统有个组件叫任务调度器,将cpu的时间分给不同程序使用,微观串行(单核),宏观并行(多核)
3)可以使用jconsle 来查看某个java进程中线程的运行情况,包括死锁等

好处:
	1. 多进程,多线程可以让程序不被阻塞.
	2. 充分利用多核cpu的优势,提高运行效率

课程要求
1.掌握创建线程的方法和常用的sleep,join方法等
2.掌握synchronized解决并发问题,熟悉使用wait,notify方法
3.了解死锁,线程的五种状态

 

 

多线程

Thread类

创建线程

方法一:

Thread t = new Thread(){
        public void run(){
        //执行代码,注意不能抛出检查异常
        }
}
t.start; //启动线程,注意 : 多次调用会出现IllegalThreadStateException

方法二:

Runnable r = new Runnable() {
    public void run(){
        // 要执行的代码
    }
};
Thread t = new Thread( r );
t.start();

注意 :
直接调用run和使用start间接调用run的区别:
        1) 直接调用run是在主线程中执行了run,没有启动新的线程
         2) 使用start是启动新的线程,通过新的线程间接执行run

 

 

线程中常见方法

Thread.sleep(long n); // 让当前线程休眠n毫秒
Thread.currentThread(); // 找到当前线程
join() : 等待该线程结束
join(long n ) : 等待该线程结束,但最多等待n毫秒
其它方法:
getName() : 得到线程的名称
yield() : 谦让
不推荐使用:
stop(),suspend,resume()

interrupt():
打断正在等待的线程(例如sleep,join的等待)

Thread t = new Thread(()->{
    try {
        Thread.sleep(5000); // 被打断线程会抛出InterruptedException
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("结束 。。。");
});
t.start();
Thread.sleep(1000); //休眠一秒
System.out.println("打断t线程。。。");
t.interrupt();

 

 

守护线程

setDaemon(true)
默认情况下,java进程需要等待所有线程都运行结束,才会结束
而守护线程,只要主线程运行结束,即使守护线程未执行完,也会跟着主线程一起结束

Thread t1= new Thread(()->{
    System.out.println("守护线程开始执行...");
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("守护线程结束");
});
t1.setDaemon(true); // 设置该线程为守护线程
t1.start();
Thread.sleep(1000);

 

 

线程的并发(Concurrent)

synchronized(同步关键字)

语法

synchronized(对象) {
    要作为原子操作代码
}

作用 : 解决并发问题

static int i = 0;
static Object obj = new Object(); 
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int j = 0; j < 5000; j++) {
            synchronized (obj) { 
                i++;
            } 
        }
    });
    Thread t2 = new Thread(() -> { 
        for (int j = 0; j < 5000; j++) {
            synchronized (obj) { 
                i--;
            } 
        }
    });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(i);
}//如果不处理并发,最终的值将不是0

每个对象有自己的monitor,当一个线程调用synchronized(对象),就相当于进入了这个对象的监视器。要检查有没有owner,如果没有,此线程成为owner; 但如果已经有owner了,这个线程在entryset的区域等待owner的位置空出来。图解如下:


注意 :

    1)synchronized必须是进入同一个对象的monitor 才有上述的效果
    2)两个线程进入这个对象的监视器时,竞争是不分先后的,系统随机

 

 

synchronized的另外两种写法

写法一(只是常规写法的对象固定为当前类对象):

public synchronized void test() {
}
等价于
public void test() {
    synchronized(this) {
    }
}

写法二(常规写法的对象固定为类.class):

```java
class Test{
	public synchronized static void test() {
	}
}
等价于
public static void test() {
	synchronized(Test.class) {
		
	}
}

 

 

volatile 易变的

用于修饰成员变量/静态成员变量

优点 :
防止线程从自己的高速缓存中查找变量的值,必须到主存中获取它的值
局限性 :
只保证变量在多个线程间的可见性,不保证原子性

static boolean run = true;
public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(()->{
        while(run){
        }
    });
    t.start();
    Thread.sleep(1000);
    run = false;//对t线程不可见(导致线程无法停止),除非在定义run时添加volatile修饰符
}

相比之下,synchronized 语句块既可以保证代码块的原子性,也可以保证代码块内变量的可见性。但缺点是synchronized是属于重量级操作,性能会受到影响。

 

 

线程死锁

一般来说,成为owner可以理解为获得了对象的锁,死锁即是:
a线程获得A对象的锁,锁里还获取B对象的锁
b线程获得B对象的锁,锁里还获得A对象的锁

处理方法 : 尽可能避免

 

 

wait(),notify(),notifyAll()

前提 : 首先获得对象锁,否则无法使用wait,notify,notifyAll
特点 : 都属于Object对象的方法
Object obj = new Object();
obj.wait(); 让object监视器的线程等待
obj.notify(); 让object上正在等待的线程中挑一个唤醒
obj.notifyAll(); 让object上正在等待的线程全部唤醒

Object obj = new Object();
new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-0线程执行....");
        try {
            obj.wait(); // 让线程在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-0其它代码....");
    }
}).start();
new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-1线程执行....");
        try {
            obj.wait(); // 让线程在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-1其它代码....");
    }
}).start();
System.out.println("唤醒obj上其它线程");
synchronized (obj){
//            obj.notify();
    obj.notifyAll();
}

wait(long n) 有时限的等待, 到n毫秒后结束等待,或是被notify

sleep(long n) , wait(long n) 的区别

     1) sleep是Thread的方法,wait是Object的方法
     2) wait使用的前提是获得对象锁(使用了synchronized)
     3) sleep睡眠时不会释放对象锁,wait会释放

 

 

线程的状态

NEW (新建): 刚创建了线程,未start
RUANNABLE(可运行的): start之后
BLOCKED(阻塞) : 线程进入monitor监视区,然后在entrySet竞争owner位置
WAIT类型 :
1)WAITING(等待) : 调用了wait或者join方法进入WaitSet,处于等待状态
2)TIMED_WAITING 当调用wait(long n) join(long n) 进入了WaitSet,处于有限时的等待状态
TERMINATED (终止)当线程代码运行结束

猜你喜欢

转载自blog.csdn.net/Wolves_7/article/details/81607628