资讯专栏INFORMATION COLUMN

<jdk7学习笔记>读书笔记-线程

woshicixide / 2112人阅读

摘要:如果把注释去掉,则在所以非线程都结束时,自动终止。默认所有从线程产生的线程也是线程。停止线程线程完成方法后,就进入状态。被标示为的区块会被监控,任何线程要执行区块都必须先获得指定的对象锁定。

Tread和Runnable 定义线程

实现Runnable接口,重写run()方法

继承Thread类,重写run()方法

启动线程
        Runnable tortoise=new Tortoise();
        Thread thread1=new Thread(tortoise);
        thread1.start();
        Thread thread1=new Tortoise();
        thread1.start();
线程生命周期 daemon线程

主线程会从main()方法开始执行,直到main()方法结束后停止jvm.如果主线程中启动了额外线程,则主线程默认会等到被启动的所有额外线程都执行完run()方法才会终止jvm.

public class Daemon implements Runnable{
    @Override
    public void run() {
        while(true){
            System.out.println("TheViper");
        }
    }
    public static void main(String[] args){
        Thread thread=new Thread(new Daemon());
        //thread.setDaemon(true);
        thread.start();
    }
}
TheViper
TheViper
TheViper
...

控制台会不停的打印。如果把注释去掉,thread.setDaemon(true),则在所以非daemon线程都结束时,jvm自动终止。
默认所有从daemon线程产生的线程也是daemon线程。

线程基本状态图

启动线程后,基本状态可分为可执行(runnable),阻塞(blocked),执行中(running).

在一个时间点上,一个cpu只能执行一个线程,只是cpu会不断切换线程。可以用Thread的setPriority()方法设置线程的优先级,设定值1-10,默认为5.优先级越高的线程,越有机会抢占cpu.
线程进入blocked状态可能的情况:

调用Thread.sleep()方法

进入synchronized前,对竞争对象的锁定

调用wait()方法阻断

等待io完成

...

一个处于blocked状态的线程,可由另一个线程调用interrupt()方法,让它离开blocked状态。

public class Daemon implements Runnable{
    @Override
    public void run() {
        System.out.println("TheViper");
        try {
            Thread.sleep(3000);//进入blocked状态
        } catch (InterruptedException e) {
            System.out.println("离开blocked状态");
        }
    }
    public static void main(String[] args){
        Thread thread=new Thread(new Daemon());
        thread.start();
        thread.interrupt();
    }
}
TheViper
离开blocked状态
安插线程

假设线程A正在运行,这时线程B想占用cpu运行,等它运行完后,继续运行线程A.这种情况可以使用join()方法.
未使用join()

public class Daemon implements Runnable{
    @Override
    public void run() {
        System.out.println("线程开始");
        try {
            Thread.sleep(3000);
            System.out.println("线程执行");
        } catch (InterruptedException e) {
            System.out.println("离开blocked状态");
        }
        System.out.println("线程结束");
    }
    public static void main(String[] args){
        Thread thread=new Thread(new Daemon());
        thread.start();
        System.out.println("主线程开始");
    }
}
主线程开始
线程开始
线程执行
线程结束

使用join()

public class Daemon implements Runnable{
    @Override
    public void run() {
        System.out.println("线程开始");
        try {
            Thread.sleep(3000);
            System.out.println("线程执行");
        } catch (InterruptedException e) {
            System.out.println("离开blocked状态");
        }
        System.out.println("线程结束");
    }
    public static void main(String[] args) throws InterruptedException{
        Thread thread=new Thread(new Daemon());
        thread.start();
        thread.join();
        System.out.println("主线程开始");
    }
}
线程开始
线程执行
线程结束
主线程开始

程序启动后就开始运行主线程(因为新建线程使用了sleep(),让新建线程进入blocked状态,主线程可以占用cpu),对新建的线程使用join(),将其加入到主线程后,原本应该一来就运行的主线程,现在需要等到后面新建的线程执行完后,才能继续执行。
如果加入的线程处理时间太久,可以在join()时指定时间,如join(10000),表示加入的线程最多只能处理10秒。

停止线程

线程完成run()方法后,就进入dead状态。这时不能再次调用start()方法。
Thread类上的stop()方法是过时方法。
如果要停止线程,最好自行操作,让线程跑完应有的流程,而不是调用stop()方法

...
private boolean isContinue=true;
public void stop(){
    this.isContinue=false;
}
public void run(){
    while(isContinue){
        ...
    }
}
...
synchronized
public synchronized void add(Object o){...}

每个对象都有一个内部锁定。被标示为synchronized的区块会被监控,任何线程要执行synchronized区块都必须先获得指定的对象锁定。
如果线程A已获得对象锁定开始执行sychronized区块,线程B也想执行synchronized区块,线程B会因为无法获得对象锁定而进入等待对象锁定状态,直到线程A释放锁定(如执行完synchronized区块)。
在方法上标示sychronized,则执行方法必须获得该实例的指定。
实际上等待对象锁定时,也会进入线程的blocked状态

synchronized不仅可以声明在方法上,也可以描述句方式使用

public void add(Object o){
    synchronized(this){
        ...
    }
}
List list=new ArrayList();
synchronized(list){
    ...
    list.add("TheViper");//让ArrayList线程安全
}

这种方式不用锁定整个方法,只锁定会发生竞争状况的区块。获得锁定的线程执行完这个区块后,会立即释放锁定,其他线程就有机会再竞争这个锁定.
相比于将整个方法声明为synchronized,这种方式更有效率。
可以提供不同的对象作为锁定的来源

private Object lock1=new Object();
private Object lock2=new Object();
public void method1(){
    synchronized(lock1){
        data1++;
        ...
    }
}
public void method2(){
    synchronized(lock2){
        data2++;
        ...
    }
}

在两个synchronized区块里,没有公共的数据,方法。当有一个线程执行method1()而另一个线程执行method2()时,并不会引起共享存取问题,而且一个线程不会因为另一个线程获得锁定而阻塞.
java的synchronized提供的是可重入同步,也就是线程获得某对象锁定后,若执行过程中又要执行synchronized,而这个锁定对象的来源又和前面的是同一个,则可以直接执行。

死锁
public class Resource {
    synchronized void doSome(){
        
    }
    public synchronized void deal(Resource res){
        res.doSome();
    }
}
public class TheadDemo extends Thread{
    Resource res1;
    Resource res2;
    TheadDemo(Resource res1,Resource res2){
        this.res1=res1;
        this.res2=res2;
    }
    @Override
    public void run(){
        for(int i=0;i<10;i++)
            this.res1.deal(this.res2);
    }
    public static void main(String[] args){
        Resource res1=new Resource();
        Resource res2=new Resource();
        Thread thread1=new TheadDemo(res1,res2);
        Thread thread2=new TheadDemo(res2,res1);
        thread1.start();
        thread2.start();
    }
}

这段代码可能出现死锁,也可能不会。
如果出现的话,原因在于

Thread thread1=new TheadDemo(res1,res2)=>this.res1.deal(this.res2)时,thread1获得res1的锁定

Thread thread2=new TheadDemo(res2,res1)=>this.res2.deal(this.res1),thread2获得res2的锁定

thread1线程res2.doSome(),准备获得res2的锁定,但是res2的锁定被thread2线程占用,于是thread1线程进入blocked状态

thread2线程res1.doSome(),准备获得res1的锁定,但是res1的锁定被thread1线程占用,于是thread2线程进入blocked状态

volatile

synchronized所标志区块的特点:

互斥性:synchronized区块在一个时间点上只能有一个线程执行它

可见性:线程离开synchronized区块后,另一个线程面对的是上一个线程改变后的对象状态

在java中对于可见性的要求,可以使用volatile达到变量范围。
没用volatile,synchronized

public class Variable {
    static int i=0,j=0;
    static void deal(){
        i++;
        j++;
    }
    static void print(){
        System.out.println("i="+i+" j="+j);
    }
}
public class VariableTest1 extends Thread{
    @Override
    public void run(){
        while(true)
            Variable.print();
    }
}
public class VariableTest extends Thread{
    @Override
    public void run(){
        while(true)
            Variable.deal();
    }
    public static void main(String[] args){
        Thread t1=new VariableTest();
        Thread t2=new VariableTest1();
        t1.start();
        t2.start();
    }
}
...
i=638864948 j=638864993
i=638866963 j=638867006
i=638868897 j=638868941
i=638870928 j=638870974
...

可以看到j大于i,甚至可以远大于i.原因在于为了效率,线程可以快取变量的值。
t2调用Variable.print()从共享内存中取到变量i的值,并存储在自己的内存空间,如果此时cpu切换线程至t1,并不断的执行Variable.deal()多次,再切回t2,取得变量j的值,然后和i值一起输出。

如果在deal()和print()方法上标志synchronized,则t1每次调用deal()方法时,t2都必须等到t1释放锁定才能调用print()方法.t2每次调用print()方法也一样。
这种情况下,输出的i一定等于j.

在变量上声明volatile,表示变量是不稳定的,易变的。这样变量就可以在多线程情况下存取,保证了变量的可见性.换句话说,如果有线程修改了变量的值,另一个线程一定可以看到被修改的变量。
被标示为volatile的变量,不允许线程快取,变量的存取一定是在共享内存中进行。
下面将上面例子中的i,j声明为volatile.

volatile static int i=0,j=0;
...
i=34456445 j=34456448
i=34456620 j=34456623
i=34456789 j=34456791
i=34456959 j=34456961
...

可以看到没有出现j远大于i的情况,都是i略小于j.
事实上,ij的关系可以是大于,等于,小于三种之中的任一种。

i大于j

i已经改变,但是j仍然是上一次操作的j.

i小于j

第一次输出时,保存i,然后j++,第二次输出的是没有修改过的i和修改过的j.

i等于j

输出的都是共享内存中修改过的值.

由此可见,volatile保证的是单一变量可见性,线程对变量的存取一定是在共享内存中进行,不会在自己的内存空间中快取变量。线程对共享内存中变量的存取,另一个线程一定看得到。

等待与通知

wait(),notify(),notifyAll()是Object类中的方法,可以通过这三个方法控制线程释放对象的锁定,或者通知线程参与锁定的竞争.

线程进入synchronized区块前,要先获得指定对象的锁定。而在执行synchronized区块代码时,若调用锁定对象的wait()方法,线程会释放对象锁定,并进入对象等待集合,其他线程这时可以竞争对象锁定,取得锁定的线程可以执行synchronized区块代码。
处于等待集合中的线程不会参与竞争cpu.wait()方法可以指定等待时间,时间到之后,线程会参与竞争cpu.如果指定时间为0或没指定,则线程会一直等待,直到被中断(interrupt)或通知(notify)可以参与竞争cpu.
锁定的对象调用notify()方法,会对象等待集合中随机通知一个线程参与竞争cpu.
如果调用的是notifyAll()方法,则等待集合中的所有线程都会参与竞争cpu.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64706.html

相关文章

  • &lt;jdk7学习笔记&gt;读书笔记-并行api

    摘要:然而,这两个方法都只是读取对象状态,如果只是读取操作,就可以允许线程并行,这样读取效率将会提高。分配线程执行子任务执行子任务获得子任务进行完成的结果 Lock Lock接口主要操作类是ReentrantLock,可以起到synchronized的作用,另外也提供额外的功能。用Lock重写上一篇中的死锁例子 import java.util.concurrent.locks.Lock; ...

    bovenson 评论0 收藏0
  • 《HTML与CSS 第一章 认识HTML》读书笔记

    摘要:一让广播明星黯然失色要建立页面,需要创建用超文本标记语言,编写的文件,把它们放在一个服务器上二服务器能做什么服务器在互联网上有一份全天候的工作。一、Web让广播明星黯然失色    要建立Web页面,需要创建用超文本标记语言(HyperText Markup Language,HTML)编写的文件,把它们放在一个Web服务器上二、Web服务器能做什么?  Web服务器在互联网上有一份全天候的工...

    番茄西红柿 评论0 收藏0
  • &lt;javascript高级程序设计&gt;第十二章读书笔记----偏移量

    摘要:包括元素的高度上下内边距上下边框值,如果元素的的值为那么该值为。该值为元素的包含元素。最后,所有这些偏移量都是只读的,而且每次访问他们都需要重新计算。为了避免重复计算,可以将计算的值保存起来,以提高性能。 offsetHeight 包括元素的高度、上下内边距、上下边框值,如果元素的style.display的值为none,那么该值为0。offsetWidth 包括元素的宽度、左...

    dayday_up 评论0 收藏0
  • &lt;java核心技术&gt;读书笔记2

    摘要:如果需要收集参数化类型对象,只有使用警告这节讨论,向参数可变的方法传递一个泛型类型的实例。异常不能抛出或捕获泛型类的实例实际上,泛型类扩展也是不合法的。 Object:所有类的超类 java中每个类都是由它扩展而来,但是并不需要这样写:class Employee extends Object.如果没有明确指出超类,Object类就被认为是这个的超类。可以使用Object类型的变量引用...

    jimhs 评论0 收藏0
  • &lt;java核心技术&gt;读书笔记1

    摘要:关键字作用调用超类方法调用超类构造器关键字作用引用隐式参数如调用该类的其他构造器在覆盖一个方法时,子类方法可见性不能低于超类方法阻止继承类和方法目的确保它们不会在子类中改变语义。但是如果将一个类声明为后面可以改变类变量的值了。 数据类型 整型 int 存储要求:4byte 取值范围:-2147483648 -- 2147483647(超过20亿) short 存储要求:2byte 取...

    William_Sang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<