javase-Thread
|总字数:2.9k|阅读时长:12分钟|浏览量:|
定义多线程的三种方式
继承Thread类
class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName()+"run()..."); } } } public class ThreadDemo01 { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.setName("线程1"); t2.setName("线程2"); t1.start(); t2.start(); } }
|
实现Runnable接口的方式
class MyRun implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { Thread thread = Thread.currentThread(); System.out.println(thread.getName()+"run()..."); } } } public class ThreadDemo02 { public static void main(String[] args) { MyRun mr1 = new MyRun(); MyRun mr2 = new MyRun(); Thread t1 = new Thread(mr1); Thread t2 = new Thread(mr2); t1.setName("线程1"); t2.setName("线程2"); t1.start(); t2.start(); } }
|
利用Callable接口和Future接口[可以获取多线程的结果]
class MyCallable implements Callable<Integer> { @Override public Integer call() { int sum = 0; for (int i = 0; i < 100; i++) { sum += i; } return sum; } } public class ThreadDemo03 { public static void main(String[] args) throws ExecutionException, InterruptedException { MyCallable mc = new MyCallable(); FutureTask<Integer> ft = new FutureTask<>(mc); Thread thread = new Thread(ft); thread.start(); Integer result = ft.get(); System.out.println("result = " + result); } }
|
常见的成员方法

setPriority(int newPriority)【设置线程的优先级】 和 final int getPriority()【获取线程的优先级】
- 优先级不是绝对的,只是表示线程有很大的概率能抢到CPU
final void setDaemon(boolean on):设置为守护线程
- 当其他非守护线程结束了,守护线程也会陆续结束
- 应用场景:线程1(聊天)、线程2(传输文件-守护线程)
class MyThread1 extends Thread{ @Override public void run() { for (int i = 0 ; i < 10 ; i++) { System.out.println(getName() + i); } } }
class MyThread2 extends Thread{ @Override public void run() { for (int i = 0 ; i < 100 ; i++) { System.out.println(getName() + i); } } } public class ThreadDemo { public static void main(String[] args) { MyThread1 t1 = new MyThread1(); MyThread2 t2 = new MyThread2();
t2.setDaemon(true);
t1.setName("非守护线程"); t2.setName("守护线程");
t1.start(); t2.start(); } }
|
线程的生命周期【5种】

线程安全的问题
线程在执行的时候会有随机性,CPU的执行权随时有可能被其他线程抢走
买票问题:三个窗口同时卖100张票
class MyThread extends Thread { static int ticket = 0; @Override public void run() { while(true) { if(ticket < 100) { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } ++ticket; System.out.println(getName() + "卖第" + ticket + "张票"); }else { break; } } } } public class ThreadDemo01 { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
|
同步代码块:把操作共享数据的代码锁起来
- 锁默认打开,有一个线程进去,锁自动关闭
- 里面的代码全部执行完毕,线程出来,锁自动打开
synchronized (锁) { 操作共享数据的代码; }
|

更新上边买票代码:
class MyThread extends Thread { static int ticket = 0; static Object obj = new Object(); @Override public void run() { while(true) { synchronized (obj) { if(ticket < 100) { try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } ++ticket; System.out.println(getName() + "卖第" + ticket + "张票"); }else { break; } } } } }
|
同步方法
把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数){ ... }
|

- 同步方法是锁住方法里面的所有代码
- 锁对象不能自己指定
更新上边买票代码:
class MyRunnable implements Runnable { int ticket = 0; @Override public void run() { while(true) { if (method()) break; } }
private synchronized boolean method() { if(ticket == 100) { return true; }else { try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } ++ticket; System.out.println(Thread.currentThread().getName() + "卖第" + ticket + "张票"); } return false; } } public class ThreadDemo02 { public static void main(String[] args) { MyRunnable mr = new MyRunnable(); Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); Thread t3 = new Thread(mr); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
|

Lock锁
void lock():获得锁
void unlock():释放锁
Lock是接口,不能直接实例化,需要采用他的实现类ReentrantLock来实例化
class MyThread1 extends Thread { static int ticket = 0; static Lock lock = new ReentrantLock(); @Override public void run() { while(true) { lock.lock(); try { if(ticket < 100) { Thread.sleep(10); ++ticket; System.out.println(getName() + "卖第" + ticket + "张票"); }else { break; } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } } } } public class ThreadDemo03 { public static void main(String[] args) { MyThread1 t1 = new MyThread1(); MyThread1 t2 = new MyThread1(); MyThread1 t3 = new MyThread1(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
|

死锁
死锁是一个错误,在写锁的时候,不要让两个锁嵌套写

等待唤醒机制
生产者和消费者
常见方法

场景:有一个桌子(Desk)、厨师(Cook)、吃货(Foodie);要求厨师做一碗,吃货吃一碗。
class Cook extends Thread { @Override public void run() { while(true) { synchronized(Desk.lock) { if(Desk.count == 0) { break; }else { if(Desk.foodFlag == 1) { try { Desk.lock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } }else { System.out.println("厨师正在做"); Desk.foodFlag = 1; Desk.lock.notifyAll(); } } } } } }
class Foodie extends Thread { @Override public void run() { while(true) { synchronized (Desk.lock) { if(Desk.count == 0) { break; }else { if(Desk.foodFlag == 0) { try { Desk.lock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } }else { Desk.count--; System.out.println("吃货正在吃,还能吃:"+ Desk.count); Desk.lock.notifyAll(); Desk.foodFlag = 0; } } } } } }
class Desk { public static int foodFlag = 0; public static int count = 10; public static Object lock = new Object(); }
public class ThreadDemo01 { public static void main(String[] args) { Cook cook = new Cook(); Foodie foodie = new Foodie(); cook.setName("厨师"); foodie.setName("吃货"); cook.start(); foodie.start(); } }
|

利用阻塞队列方式实现
阻塞队列:连接生产者和消费者之间的管道。
- put数据:放不进去,会等着,叫做阻塞
- take数据:取出第一个数据,取不到会等着,也叫阻塞
写的时候可以不用加锁,put()和take()底层就已经有锁了
阻塞队列的继承结构

class Cook extends Thread { ArrayBlockingQueue<String> queue; public Cook(ArrayBlockingQueue queue) { this.queue = queue; } @Override public void run() { while (true) { try { queue.put("面条"); System.out.println("厨师放了一碗面条"); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
class Foodie extends Thread { ArrayBlockingQueue<String> queue; public Foodie(ArrayBlockingQueue queue) { this.queue = queue; } @Override public void run() { while (true) { try { String food = queue.take(); System.out.println(food); } catch (InterruptedException e) { throw new RuntimeException(e); }
} } }
public class ThreadDemo02 { public static void main(String[] args) { ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1); Thread cook = new Cook(queue); Thread foodie = new Foodie(queue); cook.setName("厨师"); foodie.setName("吃货"); cook.start(); foodie.start(); } }
|

线程的状态【7种】

注:在Java虚拟机种只有六种状态,没有运行状态,因为线程抢到CPU的执行权进入运行状态,虚拟机就会把当前线程交给操作系统管理

线程栈

线程1和线程2的run()方法里的存储空间是相互独立的。
线程池
- 创建一个池子,池子是空的
- 提交任务,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接服用已有的线程即可。
- 如果提交任务时,池子里没有空闲的线程,也无法创建新的线程,任务就会排队等待。
Executors:线程池的工具类,通过调用方法返回不同类型的线程池对象

创建没有上限的线程池
class MyRunnable implements Runnable {
@Override public void run() { System.out.println(Thread.currentThread().getName()); } } public class ThreadDemo01 { public static void main(String[] args) throws InterruptedException { ExecutorService pool = Executors.newCachedThreadPool(); pool.submit(new MyRunnable()); Thread.sleep(1000); pool.submit(new MyRunnable()); Thread.sleep(1000); pool.submit(new MyRunnable()); Thread.sleep(1000); pool.submit(new MyRunnable()); Thread.sleep(1000); pool.submit(new MyRunnable()); pool.shutdown(); } }
|

线程是可以复用的,代码中让提交任务后,让main线程睡1秒中,此时上一个线程执行完毕,就会把线程重新放入线程池中
创建有上限的线程池
public class ThreadDemo02 { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(3); pool.submit(new MyRunnable()); pool.submit(new MyRunnable()); pool.submit(new MyRunnable()); pool.submit(new MyRunnable()); pool.submit(new MyRunnable()); pool.shutdown(); } }
|

上边的代码相当于是3个线程在执行5个任务。因为创建了多个线程,所以当代码一步一步往下走的时候,上一步可能是还没执行完的。所以运行到第四个提交任务的时候,就会有一个任务在排队了,到第五个提交任务,就有两个任务在排队。
线程池多大合适?

自定义线程池



核心线程都在处理任务,队伍中也已经排满了,此时才会创建临时线程去处理任务。
任务的执行不会按照提交的顺序去执行。

核心线程和临时线程都在工作,队伍中也排满了,此时线程池就会触发任务拒绝策略。
任务拒绝策略

代码实现

public class ThreadDemo03 { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor( 3, 6, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); } }
|