一、守护线程和用户线程
守护线程,是指在程序运行的时候在后台提供的一种服务于用户线程的线程,比如说垃圾回收机制就是一个很称职的守护线程。当用户线程执行结束时,守护线程会一直执行,但是JVM虚拟机会因为用户线程的结束而结束,那么守护线程就会*结束。反过来说,如果用户线程还在运行,程序就不会终止。
在线程中默认创建的都是用户线程,如果需要创建守护线程,那么可以使用thread.setDaemon(true)来将线程设置为守护线程,在设置守护线程时要注意几点:
1.不能将正在运行的线程设置为守护线程,不然会抛出IllegalThreadStateException异常,所以setDaemon必须放在start之前。
2.在Daemon线程中创建的线程也是Daemon线程。
3.不是所有的线程都可以设置成Daemon线程,比如说读写操作或者是计算逻辑,因为在DaemonThread还没来得及操作之前,虚拟机就已经结束了。
4.可以使用isDaemon方法来查看线程是否为Daemon线程。
1 public class UserThreadTest extends Thread{ 2 @Override 3 public void run() { 4 for (int i = 0; i < 5; i++) { 5 try { 6 Thread.sleep(1000); 7 } catch (InterruptedException e) { 8 e.printStackTrace(); 9 } 10 System.out.println("用户线程UserThread"); 11 } 12 13 } 14 } 15 16 17 18 public class DeamonTest extends Thread{ 19 @Override 20 public void run() { 21 while (true){ 22 try { 23 Thread.sleep(500); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 28 System.out.println("守护线程Deamon"); 29 } 30 } 31 } 32 33 package com.chinasofti.deamon; 34 35 public class Test { 36 public static void main(String[] args) { 37 // 创建三个用户线程 38 UserThreadTest u1 = new UserThreadTest(); 39 UserThreadTest u2 = new UserThreadTest(); 40 UserThreadTest u3 = new UserThreadTest(); 41 // 创建一个守护线程 42 DeamonTest d1 = new DeamonTest(); 43 // 设置守护线程的setDeamon值为true true时为守护线程 默认为false用户线程 设置守护线程也可以在该守护线程的构造方法中设置 44 d1.setDaemon(true); 45 // 启动线程 46 u1.start(); 47 u2.start(); 48 u3.start(); 49 d1.start(); 50 51 // 当用户线程全部执行完毕后,守护线程也会自动关闭 52 } 53 }View Code
执行结果:
二、线程类的重要方法
优先级概念:
每一个Thread都有一个setName的方法,可以给线程起名字,也可以使用getName来获取该线程的名字。
Java线程可以设置优先级,高优先级的线程比低优先级的线程有更高的机会得到执行。
Java线程的优先级是一个1~10的整数,Thread.MAX_PRIORITY是指的是最大优先级10,Thread.MIN_PRIORITY指的是最小优先级1,Thread.NORM_PRIORITY指的是默认优先级5,
可以使用setPriority来设置线程优先级,setMaxPriority和setMinPriority设置线程的最大和最小优先级。
Java线程中有线程组(ThreadGroup)的概念:
如果线程组的优先级设置为7,那么这个线程组中的其他线程默认优先级为7
如果线程组的最大优先级为7,那么这个线程组中的线程优先级最大为7,如果这时候给线程的优先级设置为8,并不会报错,线程的优先级也依旧是7,没有发生变化。
如果其他线程的优先级是8,那么当线程组的最大优先级从8改成了7,那么其他线程的优先级并不会发生变化,也不会出现异常。
1 package com.chinasofti.threadpriority; 2 3 public class PriorityTest { 4 public static void main(String[] args) { 5 // 获取当前main主线程中的优先级 6 System.out.println(Thread.currentThread().getPriority()); 7 // 创建一个线程组 8 ThreadGroup threadGroup = new ThreadGroup("呵呵"); 9 // 创建一个线程,放在线程组中 10 Thread thread = new Thread(threadGroup,"组中的线程"); 11 // 创建一个单独的线程 12 Thread thread1 = new Thread("main中的线程"); 13 // 设置线程组优先级为7 当线程组最高优先级确定后 其组内的线程优先级最高也是7 即使设置超过7-10 但也不会出错 而是不会改变优先级,依旧为7 14 threadGroup.setMaxPriority(7); 15 // 获取优先级查看 16 System.out.println(thread.getName() + ":" + thread.getPriority()); 17 System.out.println(thread1.getName() + ":" + thread1.getPriority()); 18 19 System.out.println("-----------------"); 20 // 设置线程组中的某个线程优先级为8 但是由于线程组最高优先级为7 所以优先级设置成了7 21 thread.setPriority(8); 22 // 而单独的线程没有线程组的限制 所以为10 23 thread1.setPriority(Thread.MAX_PRIORITY); 24 System.out.println(thread.getName() + ":" + thread.getPriority()); 25 System.out.println(thread1.getName() + ":" + thread1.getPriority()); 26 27 28 } 29 }View Code
sleep:
使当前线程进入阻塞状态,不会释放掉同步锁,
可使低优先级的线程和其他级别的线程一样有公平的机会去获得执行
Java并不保证线程在阻塞给定的时间之后能够马上执行,因为时间到了之后,线程会从阻塞态转到就绪态,能不能立即执行依旧要看cpu的脸色。
yield:
使当前线程回到就绪状态,不会释放掉同步锁,
使高优先级的线程更快的执行
两者的区别:
sleep需要提供阻塞时长,可以让低优先级的线程有公平的执行机会,yield由于线程直接进入就绪状态,没有阻塞时长,只能让高优先级的线程优先执行。
join:
当前线程中其他线程调用join方法,那么当前线程会进入阻塞状态,等待调用join的线程执行结束,结束后当前线程才会继续执行,
1 package com.chinasofti.join; 2 3 public class Join { 4 public static void main(String[] args) { 5 6 Thread t1 = new Thread( 7 ()->{ 8 for (int i = 0; i < 10; i++) { 9 System.out.println("t1:" + i); 10 } 11 } 12 ); 13 14 Thread2 t2 = new Thread2(t1); 15 16 t1.start(); 17 t2.start(); 18 19 } 20 } 21 class Thread2 extends Thread{ 22 Thread thread; 23 24 public Thread2(Thread thread) { 25 this.thread = thread; 26 } 27 28 @Override 29 public void run() { 30 for (int i = 0; i < 10; i++) { 31 if(i==3){ 32 try { 33 // join 这里指的是当前线程类对象等待thread线程执行结束后 再继续执行。 相当于两个线程一起执行时,使用了这个方法后,thread开始执行,当前类线程开始等待其执行结束 34 thread.join(); 35 System.out.println("--------------"); 36 } catch (InterruptedException e) { 37 e.printStackTrace(); 38 } 39 } 40 System.out.println("t2:" + i); 41 if(i==2){ 42 System.out.println("--------------");} 43 } 44 } 45 }
三、线程同步
- 上述代码中的问题是,在一特殊的时序情况下,两个线程可以调用getNext并得到相同的返回值。
- 自增操作value++可能看起来是一个单一的操作,但是事实上它分为3个独立的操作: 因为这些操作发生在多个线程中,这些线程可能交替占有运行时,所以两个线程很可能同时读取这个值,两个线程都得到相同的值,并都使之增加了10结果就是不同的线程返回了相同的序列数
- 读取这个值
- 使之加1
- 再写入新值。
- 因为这些操作发生在多个线程中,这些线程可能交替占有运行时,所以两个线程很可能同时读取这个值,两个线程都得到相同的值,并都使之增加了10结果就是不同的线程返回了相同的序列数
如果需要将多条代码是做一个整体调度单元,希望这个调度单元在多线程环境中的调度顺序不影响任何结果,除了保证可见性、防止重排序改变语义之外,还要将该代码段进行原子保护,这种保护就称作是线程同步,其主要作用是实现线程安全。
使用synchronized关键字来操作进行同步处理
在JVM中,每个对象和类在逻辑上都是和一个监视器相关联的,为了实现监视器的排他性监视能力(即保证资源只能同时被一个线程访问),JVM为每一个对象和类都关联了一个,锁住了一个对象,就是获得了该对象的监视器。
监视器就像一座建筑,它有一个很特别的房间,房间里有一些数据,而且在同一时间只能被一个线程占据,进入这个建筑叫做"进入监视器",进入建筑中的那个特别的房间叫做"获得监视器",占据房间叫做"持有监视器",离开房间叫做"释放监视器",离开建筑叫做"退出监视器"
- 而一个锁就像一种任何时候只允许一个线程拥有的特权
- 一个线程可以允许多次对同一对象上锁,对于每一个对象来说,Java虚拟机维护一个计数器,记录对象被加了多少次锁,没被锁的对象的计数器是0,线程每加锁一次,计数器就加1,每释放一次,计数器就减1.当计数器跳到0的时候,锁就被完全释放了
- java虚拟机中的一个线程在它到达监视区域开始处的时候请求一个锁,JAVA程序中每一个监视区域都和一个对象引用相关联
- Java中synchronized关键字获得对象锁,实现线程同步
synchronized会降低程序的性能
实现同步的两种方法:
1.同步方法
2.同步代码块
1 package com.chinasofti.synchronizedtest; 2 3 import java.util.Arrays; 4 5 public class synchronizedTEST { 6 public static void main(String[] args) { 7 // 创建一个栈对象 8 MyStack myStack = new MyStack(); 9 // 创建一个出栈线程 10 Thread pop = new Pop(myStack); 11 // 创建一个入栈线程 12 Thread push = new Push(myStack); 13 14 // 提前打印一下栈中的情况 15 System.out.println(myStack); 16 // 出栈线程启动 17 pop.start(); 18 // 入栈线程启动 19 push.start(); 20 } 21 } 22 23 // 出栈线程 24 class Pop extends Thread{ 25 private MyStack myStack; 26 public Pop(MyStack myStack){ 27 this.myStack = myStack; 28 } 29 30 @Override 31 public void run() { 32 myStack.pop(); 33 } 34 } 35 36 // 入栈线程 37 class Push extends Thread{ 38 private MyStack myStack; 39 public Push(MyStack myStack){ 40 this.myStack = myStack; 41 } 42 43 @Override 44 public void run() { 45 myStack.push('D'); 46 } 47 } 48 49 class MyStack{ 50 char ch[] = {'A','B',' ',' '}; 51 static int index = 2; 52 53 // 这里加上synchronized关键字后,如同加上了锁 当某个线程访问这个方法时,其他线程无法再进行访问 只能等待该线程执行完毕 释放资源 其他线程才能继续使用 54 public synchronized void pop(){ 55 index--; 56 char a = ch[index]; 57 ch[index] = ' '; 58 System.out.println("出栈:"+a); 59 System.out.println(this); 60 } 61 62 public synchronized void push(char c){ 63 ch[index] = c; 64 index++; 65 System.out.println("入栈:" + c); 66 System.out.println(this); 67 } 68 69 @Override 70 public String toString() { 71 return "MyStack{" + 72 "ch=" + Arrays.toString(ch) + 73 '}'; 74 } 75 }View Code
- 临界区同步块:临界区同步块可以适当降低同步整个方法带来的性能消耗
- 有时,你只是希望防止多个线程同时访问方法内部的部分代码而不是整个方法
- 通过这种方式分离出来的代码被称为 “临界区” (critical section),它也使用synchronized关键字建立。
- 这里, synchronized被用来指定某个对象,此对象的锁被用来对花括号内的代码进行同步控制
- 临界区同步块可以适当降低同步整个方法带来的性能消耗
四、线程通讯
1 package com.chinasofti.volatiletest; 2 3 public class VolatileTest { 4 // volatile用来修饰共享变量 线程安全 5 volatile boolean hasEgg = false; 6 Thread hunman = new Thread( 7 ()->{ 8 while (true){ 9 try { 10 Thread.sleep(1000); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 if(hasEgg){ 15 System.out.println("收获鸡蛋"); 16 hasEgg = false; 17 }else { 18 19 System.out.println("无蛋"); 20 } 21 } 22 } 23 ); 24 25 Thread hen = new Thread( 26 ()->{ 27 while (true){ 28 if(!hasEgg){ 29 try { 30 Thread.sleep(10000); 31 } catch (InterruptedException e) { 32 e.printStackTrace(); 33 } 34 System.out.println("生了"); 35 hasEgg = true; 36 } 37 } 38 } 39 ); 40 41 42 public static void main(String[] args) { 43 VolatileTest volatileTest = new VolatileTest(); 44 volatileTest.hunman.start(); 45 volatileTest.hen.start(); 46 } 47 }
- 线程human和hen必须获得指向同一个hasEggs共享实例的引用,以便进行通信。如果它们持有的引用指向不同的实例,那么彼此将不能检测到对方的信号。需要处理的数据可以存放在一个共享缓存区里
- 上例这种循环轮询方式被称为忙等待
- 忙等待没有对运行等待线程的CPU进行有效的利用,除非平均等待时间非常短。否则,让等待线程进入睡眠或者非运行状态更为明智,直到它接收到它等待的信号
wait()方法:
一旦线程调用了任意对象的wait方法,那么这个线程就会变为非运行状态。直到另一个线程调用了notify方法或者notifyAll方法。
为了调用wait、notify和notifyAll方法,线程必须先获得那个对象的,也就是说,线程必须在同步块里调用wait或者notify。如果调用的时候没有获得锁,那么就会抛出IllegalMonitorStateException异常。
wait方法也具备有时间参数的重载版本,超过时间之后即使没有其他线程调用notify方法,线程也将唤醒。
如果一个线程在wait之前进行notify,那么wait线程就不会被唤醒,可能这个线程就会永久的等待。
如果在一个线程中使用管道输入流和管道输出流,可能会造成死锁。
1 package com.chinasofti.pipe; 2 3 import java.io.IOException; 4 import java.io.PipedReader; 5 import java.io.PipedWriter; 6 7 // 线程间的通信 8 public class PipeTest { 9 public static void main(String[] args) throws IOException { 10 Sender sender = new Sender(); 11 Receiver receiver = new Receiver(sender.getOut()); 12 13 sender.start(); 14 receiver.start(); 15 } 16 17 } 18 19 // 输入流 20 class Receiver extends Thread{ 21 private PipedReader in; 22 23 // 构造方法中获取发送流 24 public Receiver(PipedWriter out) throws IOException { 25 this.in = new PipedReader(out); 26 } 27 28 @Override 29 public void run() { 30 while (true){ 31 try { 32 System.out.println(in.read()); 33 } catch (IOException e) { 34 e.printStackTrace(); 35 } 36 } 37 } 38 } 39 40 // 输出流 41 class Sender extends Thread{ 42 private PipedWriter out; 43 44 public Sender() { 45 this.out = new PipedWriter(); 46 } 47 48 public PipedWriter getOut() { 49 return out; 50 } 51 52 public void setOut(PipedWriter out) { 53 this.out = out; 54 } 55 56 @Override 57 public void run() { 58 for (char i = 'A'; i < 'Z'; i++) { 59 try { 60 Thread.sleep(500); 61 out.write(i); 62 } catch (IOException e) { 63 e.printStackTrace(); 64 } catch (InterruptedException e) { 65 e.printStackTrace(); 66 } 67 } 68 } 69 }
View Code
五、生产者和消费者模式
1 package com.chinasofti.shop; 2 3 public class Consumer extends Thread{ 4 // 消费者姓名 5 private String name; 6 // 消费的仓库 7 private WareHouse wh; 8 9 // 构造方法 参数是消费的仓库还有姓名 10 public Consumer(WareHouse wh,String name){ 11 this.wh = wh; 12 this.name = name; 13 } 14 15 // 消费货物 16 @Override 17 public void run() { 18 while (true){ 19 if(wh.isEmpty()){ 20 synchronized (wh){ 21 // 如果仓库为空 生产者不能消费 那么就让次线程等待 释放资源 22 System.out.println("仓库已空"); 23 try { 24 wh.wait(); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 } 29 }else { 30 synchronized (wh){ 31 // 如果仓库有货物 消费者购买 32 wh.subGoods(); 33 System.out.println( name + ":已消费货物,目前库存->" + wh.getCount()); 34 wh.notify(); 35 } 36 } 37 try { 38 Thread.sleep(1000); 39 } catch (InterruptedException e) { 40 e.printStackTrace(); 41 } 42 } 43 } 44 }View Code
1 package com.chinasofti.shop; 2 3 public class Producer extends Thread{ 4 // 生产者对应的工厂 5 private WareHouse wh; 6 // 生产者姓名 7 private String name; 8 9 // 构造方法 参数为仓库和生产者姓名 10 public Producer(WareHouse wh,String name){ 11 this.wh = wh; 12 this.name = name; 13 } 14 15 // 生产方法 16 @Override 17 public void run() { 18 while (true){ 19 // 如果是满的 直接释放锁 线程开始等待 20 if(this.wh.isFull()){ 21 synchronized (wh){ 22 System.out.println("仓库已满"); 23 try { 24 wh.wait(); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 } 29 }else { 30 synchronized (wh){ 31 // 否则生产 当库存不为0或者满时 唤醒所有线程 继续执行 32 wh.addGoods(); 33 System.out.println( name + ":已生产货物,目前库存->" + wh.getCount()); 34 // 唤醒所有线程 35 wh.notify(); 36 } 37 } 38 try { 39 Thread.sleep(1000); 40 } catch (InterruptedException e) { 41 e.printStackTrace(); 42 } 43 } 44 } 45 }View Code
1 package com.chinasofti.shop; 2 3 public class WareHouse { 4 // 库存最大数量 5 private static final int MAX = 10; 6 // 库存最小数量 7 private static final int MIN = 1; 8 // 剩余货物数量 9 private volatile int count = 5; 10 11 // 生产货物 货物++ 12 public void addGoods(){ 13 this.count++; 14 } 15 16 // 判断是否为空 17 public boolean isEmpty(){ 18 return this.count <= MIN; 19 } 20 21 // 判断是否为满 22 public boolean isFull(){ 23 return this.count >= MAX; 24 } 25 26 // 出售货物 货物-- 27 public void subGoods(){ 28 this.count--; 29 } 30 31 // 获得剩余数量 32 public int getCount() { 33 return count; 34 } 35 }View Code
1 package com.chinasofti.shop; 2 3 public class Test { 4 /* 5 * 当一个生产者访问仓库时,如果仓库不为满,那么这个生产者就会生产货物, 6 * */ 7 public static void main(String[] args) { 8 WareHouse wh = new WareHouse(); 9 Consumer c1 = new Consumer(wh, "消费者1"); 10 Consumer c2 = new Consumer(wh, "消费者2"); 11 Consumer c3 = new Consumer(wh, "消费者3"); 12 Producer p1 = new Producer(wh, "生产者1"); 13 Producer p2 = new Producer(wh, "生产者2"); 14 Producer p3 = new Producer(wh, "生产者3"); 15 Producer p4 = new Producer(wh, "生产者4"); 16 Producer p5 = new Producer(wh, "生产者5"); 17 18 c1.start(); 19 c2.start(); 20 c3.start(); 21 p1.start(); 22 p2.start(); 23 p3.start(); 24 p4.start(); 25 p5.start(); 26 } 27 }View Code