先甩出来两种创建线程的方法:
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处理,不然会出错,引原来的父类Thread的run方法就没有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对象----专门用来描述编译之后的字节码文件,说明为什么会想着有Class,Class对象里面维护的是一个类的信息,不如类的方法有哪些访问属性等等,直接查看对应的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 }
线程通信
线程通信:一个线程完成了自己的任务的时候,要通知另外一个线程去完成另外一个任务。
最最经典的例子就是“生产者与消费者”的例子。我觉着这个例子适合多次看,特别的熟悉。
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方法?因为只有同步代码块才会使用锁对象,而只有锁对象才会调用wait和notify方法
消费者生产者的例子:
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 }
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(); } } }
线程间通信其实就是多个线程在操作同一个资源,但操作动作不同,wait,notify(),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 }
后台线程
后台线程:就是隐藏起来一直在默默运行的线程,直到进程结束。
实现:
setDaemon(boolean on)
特点:
当所有的非后台线程结束时,程序也就终止了同时还会杀死进程中的所有后台线程,也就是说,只要有非后台线程还在运行,程序就不会终止,执行main方法的主线程就是一个非后台线程。
必须在启动线程之前(调用start方法之前)调用setDaemon(true)方法,才可以把该线程设置为后台线程。
一旦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 }
Thread的join方法
当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 }