20180811_18_Java中线程的创建,synchronized关键字,volatile和死锁与线程的状态的一些介绍

线程

应用程序以进程为单位运行时,一个进程之内可以分为一到多个线程
window下可以通过任务管理器查看进程
Linux下可以通过 ps-fe查看
线程进程都可以并行执行

操作系统中有一个组建叫任务调度器,将CPU的时间片分给不同的程序使用,围观串行(单核),宏观并行。

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

1.Java中的多线程

Thread类

创建线程的方法(1):

        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("****");
            }
        };

创建线程的方法(2):

 		Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("***");
            }
        };

        Thread t = new Thread(r);
        t.start();

2.线程中常见的方法

Thread.sleep(long n); 当前线程休眠n毫秒。
Thread.currentThread(); 找到当前线程。

main方法其实是主线程)(mian)调用

注意:在控制台使用jconsle来查看某个Java进程中线程的运行情况

实例方法:
start() 让线程启动, 只能调用一次,如果调用了多次会出现IllegalThreadStateException

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

join() 等待某个线程执行结束,例:

static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1= new Thread(()->{
            System.out.println("t1运行开始");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1运行结束");
            r = 100;
        });
        t1.start();

        /*System.out.println("main开始");
        t1.join();
        System.out.println("main结束");*/
        t1.join();
        System.out.println(r);
    }

join(long n) 表示等待线程结束,但是最多等待n毫秒。

其他方法:
getName() 得到线程的名称
yield() 谦让

不推荐使用的方法:
.stop() 让线程停止
.suspend() 让线程暂停
.resume() 让线程继续

默认情况下,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);

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

 		Thread t = new Thread(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束...");
        });

        t.start();

        Thread.sleep(1000);

        System.out.println("打断t进程");
        t.interrupt();

线程的并发(Concurrent)

synchronized(同步关键字)

语法

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

例子


    static int j = 0;
    static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() ->{
            for (int i = 0; i < 5000; i++) {
                synchronized (obj){
                    j++;
                }
            }
        });

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                synchronized (obj){
                    j--;
                }
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println(j);
    }

每个对象都有自己的monitor监视器,当一个线程调用synchronization(对象),就相当于进入了这个对象的监视器,要检查有没有owner,如果没有,此线程成为owner,如果有,这个线程在entrySet的区域等待owner的位置空出来。

成为owner可以理解为获得对象的锁。

在竞争是,是不公平的,并不是谁先进来谁先进入owner

synchronized必须是进入同一个对象的monitor 才有上述的效果

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;
}

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

public synchronized void test() {

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

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

线程死锁

a 线程 获得 A 对象 锁
接下来获取B对象的锁
b 线程获得 B对象 锁
接下来获取A对象的锁
例:

Object A = new Object();
Object B = new Object();


Thread a = new Thread(()->{
    synchronized (A) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (B) {
            System.out.println("操作...");
        }
    }
});

Thread b = new Thread(()->{
    synchronized (B) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (A) {
            System.out.println("操作...");
        }
    }
});
a.start();
b.start();

检测死锁可以使用 jconsole工具

wait() notify() notifyAll()

都属于Object对象的方法

wait() 等待
notify() 唤醒
它们都是线程之间进行协作的手段

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();

try {
    Thread.sleep(2000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("唤醒obj上其它线程");
synchronized (obj){
//            obj.notify();
    obj.notifyAll();
}

}

wait() 方法实际是会释放对象的锁,进入WaitSet等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify为止

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

面试题:sleep(long n) 睡眠n毫秒, wait(long n) 等待n毫秒
1) sleep是Thread方法,而wait是Object的方法
2) sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起用
3) sleep 在睡眠的同时,不会释放对象锁的,但wait在等待的时候会释放对象锁。

 线程的状态

NEW(新建) 线程刚被创建,但是还没有调用 start方法

RUNNABLE(可运行) 当调用了start() 方法之后

BLOCKED(阻塞) 当线程进入了monitor监视器区,处于entrySet里准备竞争锁的时候,处于阻塞状态

WAITING(等待) 当调用了对象的wait方法,或调用了线程对象的join方法,进入了WaitSet,处于等待状态

TIMED_WAITING 当调用wait(long n) join(long n) 进入了WaitSet,处于有限时的等待状态
当调用sleep(long n) 是让当前线程放弃cpu的时间片,睡眠一会

TERMINATED (终止)当线程代码运行结束

五种状态:
NEW(新建), RUNNABLE(可运行) , RUNNING(正在运行), 阻塞(BLOCKED,WAITING, TIMED_WAITING )TERMINATED(终止)

 如何让两个线程以固定的顺序运行

static Object obj = new Object();
static boolean t2runed = false;// t2是否执行过
// 打印 2, 1
public static void main(String[] args) {

    Thread t1 = new Thread(() -> {
        synchronized (obj) {
            while(!t2runed) { // 如果t2没有执行过
                try {
                    obj.wait(); // 线程t1 先等一会
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(1);
    });

    Thread t2 = new Thread(()->{
        System.out.println(2);
        synchronized (obj) {
            t2runed = true;
            obj.notify();
        }
    });

    t1.start();
    t2.start();
}

猜你喜欢

转载自blog.csdn.net/Lisiluan/article/details/81590396