(6)简单说说java中的线程

先甩出来两种创建线程的方法:

 1 private static int count = 100;
 2 
 3 public static void main(String[] args) {
 4     // 用继承Thread类的方式启动一个线程
 5     new Thread() {
 6         public void run() {
 7             synchronized (StartThreadTest.class) {
 8                 while (count > 0) {
 9                     count--;
10                     System.out.println(Thread.currentThread() + "卖了一张票,还剩" + count);
11                 }
12             }
13         }
14     }.start();
15 
16     // 用实现Runnable接口的方式启动一个线程
17     new Thread(new Runnable() {
18         public void run() {
19             synchronized (StartThreadTest.class) {
20                 while (count > 0) {
21                     count--;
22                     System.out.println(Thread.currentThread() + "卖了一张票,还剩" + count);
23                 }
24             }
25         }
26     }).start();
27 }

 

 不只是线程,在这个javase标签下的所有的笔记都是一些核心的点,甚至有些比较小的内容这里略过,就没有介绍。

线程

线程:在一个进程中负责了代码的执行,就是进程中的一个执行路径

多线程:在一个进程中有多个线程在同时执行不同的任务。

一个java应用程序至少有几个线程?至少有2个线程,一个是主线程负责了main方法的执行,一个是gc()垃圾回收器的执行,他们是互不干扰的。

多线程的特点:

1、解决了一个进程同时执行多个进程的问题

2、不能提高效率,只是提高了cpu资源的利用率。

3、引发了线程的安全问题

4、会出现死锁现象

如何创建多线程:

创建线程方式一:

1自定义一个类继承Thread类。

2、重写Thread类的run()方法,run()方法中是该线程的任务代码。自定义线程的任务代码写到run()方法中。Jvm创建的主线程的代码,就是main方法中的所有代码。

3、创建自定义线程。并且调用start()方法开启线程。一旦一个线程开启(start)就会执行run()方法,直接调用run()方法,只相当于调用了一个普通的方法。

方式二:

  方式二往后面翻

线程的生命周期  

  其实就是介绍线程的创建、可运行状态、运行状态、阻塞状态、死亡状态,五中状态之间的相互切换。

常见线程的方法

Thread(String name)     初始化线程的名字

 getName()             返回线程的名字

 setName(String name)    设置线程对象名

 sleep()                 线程睡眠指定的毫秒数。Static型的,谁执行sleep这句代码,谁睡着,可以t.sleep()调用,也可以Thread.sleep()

 getPriority()             返回当前线程对象的优先级   默认线程的优先级是5

 setPriority(int newPriority) 设置线程的优先级    虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10 ,最小的1 , 默认是5)。

 currentThread()      返回CPU正在执行的线程的对象

注意了在从写run方法的时候使用try-catch捕获处理而不使用throws处理,不然会出错,引原来的父类Threadrun方法就没有throws错误。

创建线程方式二:使用Runnable创建线程

1、定义实现Runnable接口的类class myrun inplements Runnable...

2、重写Runnable接口中的run方法,就是将线程运行的代码放入run

3、通过通过Thread类建立线程对象 Thread t = new (Runnable run);

4、Runnable接口的子类对象作为实际参数传递给Thread类的构造方法。

5、调用Thread类的start方法开启线程,并调用Runnable接口子类的run方法。

是这样的,Runnable接口只有一个方法就是run(),别的什么都没有了,包括start()方法,所以要启动其run方法必须使用Thread相关的方法,于是就把,Runnable的子类对象传递给Thread构造出来一个线程然后再启动。这就好比说,Thread是一个工人,其工作是Runnable分配的。

注意事项:

1、Runnable实现类的对象是线程对象吗?

Runnable实现类的对象并不是一个线程的对象,只不过是实现了Runnable接口的一个对象而已罢了。只有Thread或者是Thread的子类才是线程的对象。

2、为什么把Runnable实现类的对象作为实参传递给Thread的对象,其作用是什么?

其作用就是把Runnable实现类的run方法作为线程的任务去执行的。

比较推荐使用第二种Runnable的方法创建线程,这种方法创建线程。其中最大的一个优势是,可以实现所继承呀。

线程的同步机制 

两种方式,其实同步机制有很多内容,比如还有Lock锁和reetrantLock锁以及条件对象,具体可以参看javacore。这里只讲了synchronized的两种形式

同步机制方式一:同步代码块

synchronized(锁对象)

{

需要同步的代码块...

}

同步代码块需要注意的事项:

1.任意个一个对象都可以作为锁对象

2.在同步代码块中调用sleep函数,并不会释放锁对象。

3.只有真正存在线程安全问题才需要使用同步代码块,不然的话降低代码执行的效率,不然每次都要判断是否加锁。

4.多线程操作的多对象必须是唯一共享的否则是无效的。所以这个锁对象需要定义为 static型的,比如static Object o = new Object();注意了最简单的加锁方式:synchronized(”){}这种形式你也能所得住,在常量池中只有一个,所有能够达到共享锁的作用。如果是 synchronized(new String(“”)){}这样的就不行了,new String(”)得到的不是字符串常量中的不变字符,每次都会在堆内存中创建一个

同步机制方式二;同步函数

同步函数的注意事项:

1、如果是一个非静态的同步函数的锁 对象是this,如果是静态的同步函数锁 对象是当前函数所属的类的字节码文件(Class对象----专门用来描述编译之后的字节码文件,说明为什么会想着有ClassClass对象里面维护的是一个类的信息,不如类的方法有哪些访问属性等等,直接查看对应的Class对象即可)。这里具体分析一下,非静态函数每个线程进来,线程自身的this对象就是当前的锁,是锁不住共享资源的。这个时候把我们要同步的函数定义成static形式的,这样的锁,使用的是这个类对应的class对象

修饰符 synchronized 返回值类型 函数名(参数列表...

{

}

综合比较以上两种同步方式我们推荐使用第一种同步代码块,而不是用第二种同步代码函数,其原因如下:

1、同步代码块的对象我们可以自由的指定,方便控制,同步函数的锁对象是固定的(就两种,上面说明的有),不能由我们来制定,我们做多自己来选择。

2、同步代码块可以很方便的来控制需要被同步的代码范围,同步函数必须是整个函数的所有代码都同步了。但往往一个函数中并不是多有的代码都需要同步,这样就会影响整体的效率。

死锁现象

代码同步机制实现了共享数据的安全性,但是带来了死锁问题。死锁问题中的经典问题是“哲学家就餐问题”。

死锁想象出现根本原因:

1、存在了两个或两个以上的线程

2、存在两个两个以上的共享资源。

从技术层面来说没有解决的方案,只能尽可能的避免,也就是尽量避免上面的两种情况

 1 public class DeadLock {
 2     public static void main(String[] args) {
 3         new Thread(new Runnable() { // 创建线程, 代表中国人
 4                     public void run() {
 5                         synchronized ("刀叉") { // 中国人拿到了刀叉
 6                             System.out.println(Thread.currentThread().getName()
 7                                     + ": 你不给我筷子, 我就不给你刀叉");
 8                             try {
 9                                 Thread.sleep(10);
10                             } catch (InterruptedException e) {
11                                 e.printStackTrace();
12                             }
13                             synchronized ("筷子") {
14                                 System.out.println(Thread.currentThread()
15                                         .getName() + ": 给你刀叉");
16                             }
17                         }
18                     }
19                 }, "中国人").start();  
20         new Thread(new Runnable() { // 美国人
21                     public void run() {
22                         synchronized ("筷子") { // 美国人拿到了筷子
23                             System.out.println(Thread.currentThread().getName()
24                                     + ": 你先给我刀叉, 我再给你筷子");
25                             try {
26                                 Thread.sleep(10);
27                             } catch (InterruptedException e) {
28                                 e.printStackTrace();
29                             }
30                             synchronized ("刀叉") {
31                                 System.out.println(Thread.currentThread()
32                                         .getName() + ": 好吧, 把筷子给你.");
33                             }
34                         }
35                     }
36                 }, "美国人").start();
37     }
38 }
View Code

线程通信

线程通信:一个线程完成了自己的任务的时候,要通知另外一个线程去完成另外一个任务。

最最经典的例子就是“生产者与消费者”的例子。我觉着这个例子适合多次看,特别的熟悉。

Wait():等待 如果线程执行了wait方法那么该线就会进入等待的状态,等待状态下的线程必须要被其他线程调用notify()方法才能唤醒

Notify():唤醒 唤醒线程池中等待的线程之一方法

notifyAll()唤醒 唤醒等待线程池中所有等待的线程

Wait()notify()方法的注意事项

1、wait方法和notify方法属于Object类的方法

2、Wait方法和notify方法必须在同步代码块或者是同步函数中才能使用

3、Wait()方法和notify()方法必须要有由调用,否则也会调用的

Wait()方法:一个线程如果执行了wait方法,那么该线程就会进入一个以锁对象为标识符的线程池中。一旦调用wait方法会自动的释放锁。

Notify():如果一个线程执行notify方法,那么就唤醒以锁对象为标识符的线程池中等待线程中的其中一个。

为什么把wait方法和notify方法设计到Object上面?因为只有锁对象才调用这两个方法,而任意的对象都可以作为锁对象

为什么在同步代码块或者是同步函数中调用wait方法和notify方法?因为只有同步代码块才会使用锁对象,而只有锁对象才会调用waitnotify方法

消费者生产者的例子:

 1 public class Demo10 {
 2     public static void main(String[] args) {
 3         Person p = new Person();
 4         Producer pro = new Producer(p);
 5         Consumer con = new Consumer(p);
 6         Thread t1 = new Thread(pro, "生产者");
 7         Thread t2 = new Thread(con, "消费者");
 8         t1.start();
 9         t2.start();
10     }
11 }
12 
13 // 使用Person作为数据存储空间
14 class Person {
15     String name;
16     String gender;
17     
18 
19     public synchronized void set(String name, String gender) {
20         this.name = name;
21         this.gender = gender;
22     }
23 
24     public synchronized void read() {
25         System.out.println("name:" + this.name + "----gender:" + this.gender);
26     }
27 
28 }
29 
30 // 生产者
31 class Producer implements Runnable {
32     Person p;
33 
34     public Producer() {
35 
36     }
37 
38     public Producer(Person p) {
39         this.p = p;
40     }
41 
42     @Override
43     public void run() {
44         int i = 0;
45         while (true) {
46 
47             if (i % 2 == 0) {
48                 p.set("jack", "man");
49             } else {
50                 p.set("小丽", "女");
51             }
52             i++;
53 
54         }
55 
56     }
57 
58 }
59 
60 // 消费者
61 class Consumer implements Runnable {
62     Person p;
63 
64     public Consumer() {
65 
66     }
67 
68     public Consumer(Person p) {
69         this.p = p;
70     }
71 
72     @Override
73     public void run() {
74 
75         while (true) {
76             p.read();
77 
78         }
79     }
80 
81 }
View Code

wait:告诉当前线程放弃执行权,并放弃监视器(锁)并进入阻塞状态,直到其他线程持有获得执行权,并持有了相同的监视器(锁)并调用notify为止。

notify:唤醒持有同一个监视器(锁)中调用wait的第一个线程,例如,餐馆有空位置后,等候就餐最久的顾客最先入座。注意:被唤醒的线程是进入了可运行状态。等待cpu执行权。

notifyAll:唤醒持有同一监视器中调用wait的所有的线程。

如何解决生产者和消费者的问题?

可以通过设置一个标记,表示数据的(存储空间的状态)例如,当消费者读取了(消费了一次)一次数据之后可以将标记改为false,当生产者生产了一个数据,将标记改为true

,也就是只有标记为true的时候,消费者才能取走数据,标记为false时候生产者才生产数据。

代码实现:

package cn.itcast.gz.runnable;

public class Demo10 {
    public static void main(String[] args) {
        Person p = new Person();
        Producer pro = new Producer(p);
        Consumer con = new Consumer(p);
        Thread t1 = new Thread(pro, "生产者");
        Thread t2 = new Thread(con, "消费者");
        t1.start();
        t2.start();
    }
}

// 使用Person作为数据存储空间
class Person {
    String name;
    String gender;
    boolean flag = false;

    public synchronized void set(String name, String gender) {
        if (flag) {
            try {
                wait();
            } catch (InterruptedException e) {

                e.printStackTrace();
            }
        }
        this.name = name;
        this.gender = gender;
        flag = true;
        notify();
    }

    public synchronized void read() {
        if (!flag) {
            try {
                wait();
            } catch (InterruptedException e) {

                e.printStackTrace();
            }
        }
        System.out.println("name:" + this.name + "----gender:" + this.gender);
        flag = false;
        notify();
    }

}

// 生产者
class Producer implements Runnable {
    Person p;

    public Producer() {

    }

    public Producer(Person p) {
        this.p = p;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {

            if (i % 2 == 0) {
                p.set("jack", "man");
            } else {
                p.set("小丽", "女");
            }
            i++;

        }

    }

}

// 消费者
class Consumer implements Runnable {
    Person p;

    public Consumer() {

    }

    public Consumer(Person p) {
        this.p = p;
    }

    @Override
    public void run() {

        while (true) {
            p.read();

        }
    }

}
View Code

线程间通信其实就是多个线程在操作同一个资源,但操作动作不同,waitnotify(),notifyAll()都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。

为什么这些方法定义在Object类中

因为这些方法在操作线程时,都必须要标识他们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被统一锁上notify唤醒,不可以对不同锁中的线程进行唤醒,就是等待和唤醒必须是同一个锁。而锁由于可以使任意对象,所以可以被任意对象调用的方法定义在Object类中

wait() sleep()有什么区别?

wait():释放资源,释放锁。是Object的方法

sleep():释放资源,不释放锁。是Thread的方法

定义了notify为什么还要定义notifyAll,因为只用notify容易出现只唤醒本方线程情况,导致程序中的所有线程都在等待。

线程的停止

Stop()方法已经不推荐使用了

Interrupt()方法不用在同步代码快中使用 该方法强制清除线程的等待状态 该方法简单粗暴,强制清除等待状态,会抛出异常。Noitify()方法比较温和,能够唤醒阻塞队列里的某个线程但无法指定具体的某个线程。

线程的停止方法:

1、一般通过一个变量去控制,线程的主要内容是run()方法,这个方法中我们一般写的是一个while(true){}的循环,所以我们一般定义一个flag 用一个标志变量来控制线程的结束,这是比较好的一种用法。

2、如果要停止一个处于“等待”状态下的线程,我们需要变量配合或notify方法或者interrupt方法。

 线程生命周期

任何事物都是生命周期,线程也是,

1. 正常终止  当线程的run()执行完毕,线程死亡。

2. 使用标记停止线程

注意:Stop方法已过时,就不能再使用这个方法。

如何使用标记停止线程停止线程。

开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,线程就结束。

 1 class StopThread implements Runnable {
 2     public boolean tag = true;
 3     @Override
 4     public void run() {
 5         int i = 0;
 6 
 7         while (tag) {
 8             i++;
 9             System.out.println(Thread.currentThread().getName() + "i:" + i);
10         }
11     }
12 }
13 public class Demo8 {
14     public static void main(String[] args) {
15         StopThread st = new StopThread();
16         Thread th = new Thread(st, "线程1");
17         th.start();
18         for (int i = 0; i < 100; i++) {
19             if (i == 50) {
20                 System.out.println("main i:" + i);
21                 st.tag = false;
22             }
23         }
24     }
25 }
View Code

后台线程

后台线程:就是隐藏起来一直在默默运行的线程,直到进程结束。

 实现:

      setDaemon(boolean on)

 特点:

当所有的非后台线程结束时,程序也就终止了同时还会杀死进程中的所有后台线程,也就是说,只要有非后台线程还在运行,程序就不会终止,执行main方法的主线程就是一个非后台线程。

必须在启动线程之前(调用start方法之前)调用setDaemontrue)方法,才可以把该线程设置为后台线程。

一旦main()执行完毕,那么程序就会终止,JVM也就退出了。

可以使用isDaemon() 测试该线程是否为后台线程(守护线程)。

该案例:开启了一个qq检测升级的后台线程,通过while真循环进行不停检测,当计数器变为100的时候,表示检测完毕,提示是否更新,线程同时结束。

为了验证,当非后台线程结束时,后台线程是否终止,故意让该后台线程睡眠一会。发现只要main线程执行完毕,后台线程也就随之消亡了。

 1 class QQUpdate implements Runnable {
 2     int i = 0;
 3 
 4     @Override
 5     public void run() {
 6         while (true) {
 7 
 8             System.out.println(Thread.currentThread().getName() + " 检测是否有可用更新");
 9             i++;
10             try {
11                 Thread.sleep(10);
12             } catch (InterruptedException e) {
13 
14                 e.printStackTrace();
15             }
16             if (i == 100) {
17                 System.out.println("有可用更新,是否升级?");
18                 break;
19             }
20         }
21     }
22 }
23 public class Demo9 {
24     public static void main(String[] args) {
25         QQUpdate qq = new QQUpdate();
26         Thread th = new Thread(qq, "qqupdate");
27         th.setDaemon(true);
28         th.start();
29         System.out.println(th.isDaemon());
30         System.out.println("hello world");
31     }
32 }
View Code

Threadjoin方法

A线程执行到了B线程Join方法时A就会等待,等B线程都执行完A才会执行,Join可以用来临时加入线程执行

本案例,启动了一个JoinThread线程,main(主线程)进行for循环,当计数器为50时,让JoinThread,通过join方法,加入到主线程中,发现只有JoinThread线程执行完,主线程才会执行完毕.

可以刻意让JoinThread线程sleep,如果JoinThread没有调用join方法,那么肯定是主线程执行完毕,但是由于JoinThread线程加入到了main线程,必须等JoinThread执行完毕主线程才能继续执行。

 1 class JoinThread implements Runnable {
 2 
 3     @Override
 4     public void run() {
 5         int i = 0;
 6         while (i < 300) {
 7             try {
 8                 Thread.sleep(1000);
 9             } catch (InterruptedException e) {
10                 e.printStackTrace();
11             }
12             System.out.println(Thread.currentThread().getName() + " i:" + i);
13             i++;
14         }
15     }
16 }
17 
18 public class Demo10 {
19     public static void main(String[] args) throws InterruptedException {
20         JoinThread jt = new JoinThread();
21         Thread th = new Thread(jt, "one");
22         th.start();
23         int i = 0;
24         while (i < 200) {
25             if (i == 100) {
26                 th.join();
27             }
28             System.err.println(Thread.currentThread().getName() + " i:" + i);
29             i++;
30 
31         }
32     }
33 }
View Code
原文地址:https://www.cnblogs.com/OliverZhang/p/6014012.html