开启多线程的方法
第一种:继承Thread
继承Thread类,重写run方法
public class MyThread extends Thread { @Override public void run() { // 输出100次helloworld for (int i = 0; i < 100; i++){ System.out.println(getName() + "HelloWorld"); } } }
第二种:实现Runable
实现Runnable,重写run方法
public class MyRun implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++){ // 获取到当前线程的对象 /*Thread t = Thread.currentThread(); System.out.println(t.getName() + "HelloWorld");*/ System.out.println(Thread.currentThread().getName() + "HelloWorld"); } } }
第三种:实现Callable(有返回值)
实现Callable,重写call(是有返回值的,表示多线程运行的结果)
import java.util.concurrent.Callable; public class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { // 求1~100之间的和 int sum = 0; for (int i = 1; i <= 100; i++) { sum = sum + i; }java return sum; } }
启动多线程的方法
第一种:Thread启动方法
1、创建子类对象,这里的子类叫MyThread(MyThread:多线程的类名)
2、开启线程
public class ThreadDemo { 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启动方法
1、创建子类对象,这里的子类叫MyRun(MyRun:多线程的类名)
2、创建多线程对象Thread
3、开启线程
public class ThreadDemo { public static void main(String[] args) { // 创建MyRun的对象 // 表示多线程要执行的任务 MyRun mr = new MyRun(); // 创建多线程对象 Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); // 给线程设置名称 t1.setName("线程1"); t2.setName("线程2"); // 开启线程 t1.start(); t2.start(); } }
第三种:Callable启动方法
1、创建子类对象,这里子类叫MyCallable(MyCallable:多线程的类名)
2、创建FutureTask对象(管理多线程运行的结果)
3、创建多线程对象Thread,
4、启动线程
5、获取多线程运行结果
import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class ThreadDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { // 创建MyCallable对象(表示多线程要执行的结果) MyCallable mc = new MyCallable(); // 创建FutureTask对象(作用管理多线成运行的结果) FutureTask<Integer> ft = new FutureTask<>(mc); // 创建线程的对象java Thread t1 = new Thread(ft); // 启动线程 t1.start(); // 获取多线程运行的结果 Integer result = ft.get(); // 有异常直接抛出 System.out.println(result); } }
多线程的常用方法
1、设置线程的名称void setName(String name)
[子类对象名].setName(String name) 设置现成的名字(构造方法也可以设置名字)
细节:如果没有给线程设置名字,线程也是有默认的名字的——格式:Thread-X(X序号,从0开始)
// 测试类 public class ThreadDemo { public static void main(String[] args) { // 创建子类对象 MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); // 给线程设置名称 t1.setName("线程1"); t2.setName("线程2");java // 开启线程 t1.start(); t2.start(); } }
2、返回线程的名称String getName()
String getName() 返回此线程的名称(仅限于继承Thread的类中使用)
// 子类 线程类 public class MyThread extends Thread{ public MyThread() { } public MyThread(String name) { super(name); } @Override public void run() { System.out.println(getName()); // 返回此线程的名称 } }
// 测试类 public class ThreadDemo { public static void main(String[] args) { //1、创建线程的对象 MyThread t1 = new MyThread("飞机"); // 通过构造参数设置线程名称 MyThread t2 = new MyThread("坦克"); // 2、开启线程 t1.start(); t2.start(); } }
3、获取当前线程的对象static Thread currentThread()
Thread.currentThread(); 获取当前线程的对象(哪条线程执行到这个方法,此时获取的就是哪条线程的对象)
细节:当JVM虚拟机启动之后,会自动的启动多条线程
其中有一条线程就叫做main线程
他的作用就是去调用main方法,并执行里面的代码
以前写的所有的代码,都是运行在main线程当中
// 测试类 public class ThreadDemo { public static void main(String[] args) throws InterruptedException { // 获取当前线程对象 Thread t = Thread.currentThread(); String name = t.getName(); System.out.println(name); String nameOne = Thread.currentThread.getName(); // 获取当前线程对象的名字 System.out.println(nameOne); } }
4、让线程休眠指定的时间,单位为毫秒static void sleep(long time)
Thread.sleep(1000); 哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
细节:单位是毫秒:1 秒 = 1000毫秒
// 子类 线程类 public class MyThread extends Thread{ public MyThread() { } public MyThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { try { // 让线程休眠1000毫秒 Thread.sleep(1000); // 这个地方会有异常,要么try——catch,要么直接抛出 } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(getName() + " " + i); } } }
// 测试类 public class ThreadDemo { public static void main(String[] args) { //1、创建线程的对象 MyThread t1 = new MyThread("飞机"); // 通过构造参数设置线程名称 MyThread t2 = new MyThread("坦克"); // 2、开启线程 t1.start(); t2.start(); } }
5、设置线程的优先级setPriority(int newPriority) 获取线程的优先级 final int getPrioriry()
// 子类 线程类 public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println(Thread.currentThread().getName() + "---" + i); } } }
[子类对象名].setPriority(1~10); 设置线程的优先级
[子类对象名].getPrioriry(); 获取线程的优先级
细节:默认线程优先级是:5
// 测试类 public class ThreadDemo { public static void main(String[] args) { // 创建线程要执行的参数对象 MyRunnable mr = new MyRunnable(); // 创建线程对象 Thread t1 = new Thread(mr,"飞机"); Thread t2 = new Thread(mr,"坦克"); // 获取线程优先级 System.out.println(t1.getPriority()); System.out.println(t2.getPriority()); // 设置线程优先级(1~10,数字越大越容易抢到执行权) t1.setPriority(1); t2.setPriority(10); // 启动线程 t1.start(); t2.start(); } }
6、设置为守护线程final void setDaemon(bollean on)
当其他非守护线程执行完毕之后,守护线程会陆续的结束
当qq聊天的时候,同时给好友发送文件,当聊天界面关闭了,文件也没必要继续发送了(发送文件会停止)
// 子类 线程类 public class MyThread01 extends Thread{ @Override public void run() { for (int i = 1; i <= 10; i++) { System.out.println(getName() + "---" + i); } } }
// 子类 线程类 public class MyThread02 extends Thread{ @Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println(getName() + "---" + i); } } }
[子类对象名].setDaemon(true); 设置为守护线程
// 测试类 public class ThreadDemo { public static void main(String[] args) { // 创建子类对象 MyThread01 t1 = new MyThread01(); MyThread02 t2 = new MyThread02(); // 设置线程名称 t1.setName("女神"); t2.setName("备胎"); // 把第二个线程设置为守护线程() t2.setDaemon(true); // 启动线程 t1.start(); t2.start(); } }
7、出让线程/礼让线程public static void yield()
Thread.yield(); 表示出让当前CPU的执行权
// 子类 线程类 public class MyThread extends Thread{ @Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println(getName() + "---" + i); // 表示出让当前CPU的执行权 Thread.yield(); } } }
细节:让出CPU的执行权以后,两条线程重新开始抢夺执行权,所以让CPU执行权的线程还可能会抢到线程执行权
// 测试类 public class ThreadDemo { public static void main(String[] args) { // 创建子类对象 MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); // 给线程设置名称 t1.setName("飞机"); t2.setName("坦克"); // 启动线程 t1.start(); t2.start(); } }
8、插入线程/插队线程public final void join()
// 子类 线程类 public class MyThread extends Thread{ @Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println(getName() + "---" + i); } } }
[子类对象名].join(); 表示把这个线程插入到当前线程之前
// 测试类 public class ThreadDemo { public static void main(String[] args) throws InterruptedException { // 创建子类对象 MyThread t = new MyThread(); // 设置线程名称 t.setName("土豆"); // 启动线程 t.start(); // 表示把t这个线程插入到当前线程之前 // t:土豆 // 当前线程:main线程 t.join(); // 执行在main线程当中的 for (int i = 1; i <= 10; i++) { System.out.println("main线程" + i); } } }
线程安全的问题
线程安全性问题通常出现在多线程环境中,当多个线程同时访问和修改同一份数据时,如果没有适当的同步控制,可能会导致数据的不一致性,这就是线程安全性问题。
常见的安全性问题:
第一种:竞态条件
电影院售票:电影院有三个窗口共卖100张票
重复的票:由于线程在执行过程中随时会被别的线程抢走执行权,所以可能还没来得及输出结果,就被别的线程抢走了执行权,然后别的线程又执行,该条线程抢到执行权以后,就会打印重复的票。
超出范围的票:当售票到99张以后,窗口1抢到执行权,还没来得及输出,又被窗口二抢到执行权,这时候的值就变成了101,就会打印超出范围的票。
解决方法:使用同步代码块或同步方法
第二种:死锁
两个人抢筷子吃饭:两个人抢筷子吃饭,一次只能抢一只筷子,当一个人抢到一双筷子,另一个人抢一个筷子,这时候双方都会等对方先放下筷子,就会造成死锁。
解决方法:控制锁顺序,避免嵌套
第三种:活锁
夫妻用勺子吃饭:一对夫妻用一把勺子吃饭,双方都很有礼貌,总是让对方先吃,当发现对方饿了,就会把勺子让给对方,这会导致出现活锁。
解决方法:synchronized同步代码块,同步方法,Lock锁
synchronized同步代码块
同步代码块,当线程抢到执行权,进入同步代码块后,只有执行完同步代码块中的代码,别的线程才能重新抢夺CPU执行权
package com.zsh.threaddemo.threadtest01; public class MyThread extends Thread{ // 表示这个类所有的对象,都共享num数据 static int num = 0; // 锁对象,一定要是唯一的 static Object obj = new Object(); @Override public void run() { while (true){ // 同步代码块 synchronized (obj){ // obj:锁对象,任意对象都可以,但必须是唯一的(一般情况下用类名.class) if (num < 100){ num++; System.out.println(getName() + "正在出售第" + num + "张票!!!"); try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } }else { break; } } } } }
package com.zsh.threaddemo.threadtest01; public class ThreadDemo { public static void main(String[] args) { /* * 某电影院目前正在上映一部国产大片,共有100张票,而且有3个售票口,请写出程序模拟该电影售票 * */ // 创建子类对象 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(); } }
同步方法
细节:
如果不知道怎么写同步方法,那就先写同步代码块,
选中同步代码块中的代码按Ctrl+Alt+M自动创建方法,在修饰符和返回类型之间加上synchronized关键字即可
package com.zsh.threaddemo.threadtest02; public class MyRunnable implements Runnable{ int num = 0; @Override public void run() { // 循环 while (true){ // 同步代码块(同步方法) if (method()) break; try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } } } private synchronized boolean method() { if(num == 100){ return true; }else { num++; System.out.println(Thread.currentThread().getName() + "正在出售第" + num + "张票!!!"); } return false; } }
package com.zsh.threaddemo.threadtest02; public class ThreadDemo { public static void main(String[] args) { /* * 某电影院目前正在上映一部国产大片,共有100张票,而且有3个售票口,请写出程序模拟该电影售票 * (使用同步方法写) * */ // 创建MyRunnable对象,表示多线程要执行的任务 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锁
Lock锁提供了比Java内置的synchronized
关键字更灵活、更强大的同步控制功能。
lock.lock(); 获取锁
lock.unlock(); 释放锁
细节:创建Lock时,不能直接new Lock要new Lock的实现类RenntrantLock
package com.zsh.threaddemo.threadtest03; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyThread extends Thread{ static int num = 0; // 为了保证多个线程用的是同一个锁,所以加static静态方法 static Lock lock = new ReentrantLock(); // 不能直接new Lock要new Lock的实现类RenntrantLock @Override public void run() { while (true){ // 上锁 lock.lock(); try { if(num == 100){ break; }else { num++; System.out.println(getName() + "正在出售第" + num + "张票!!!"); } } finally { // 开锁 lock.unlock(); try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } }
package com.zsh.threaddemo.threadtest03; public class ThreadDemo { public static void main(String[] args) { /* * Lock锁 * */ 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(); } }
Lock锁的规范用法
要使用try,finally,来保证释放锁
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyClass { private final Lock lock = new ReentrantLock(); // 使用final修饰Lock锁 public void doSomething() { lock.lock(); // 获取锁 try { // 在这里执行同步操作 } finally { lock.unlock(); // 释放锁 } } }
线程池
线程池的概念
线程池是一个容器,可以保存一些线程对象,这些线程可以反复使用。
线程池的优势
降低资源消耗,重复利用线程池中的线程,不需要每次都创建、销毁。
便于线程管理,线程池可以集中管理并发线程的数量。
提交Runnable任务
// 测试类 package com.zsh.demo10多线程; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Demo { public static void main(String[] args) { // 创建线程池 ExecutorService pool = Executors.newFixedThreadPool(3); System.out.println("pool = " + pool); // 提交Runnable任务 MyRunnable mr = new MyRunnable(); pool.submit(mr); pool.submit(mr); pool.submit(mr); pool.submit(mr); pool.submit(mr); // 关闭线程池 pool.shutdown(); } } // 子类 package com.zsh.demo10多线程; public class MyRunnable implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName() + "任务执行完成"); } }
提交Callable任务
好处:有返回值。
可以抛异常。
// 测试类 package com.zsh.demo10多线程.callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Demo { public static void main(String[] args) throws ExecutionException, InterruptedException { // 创建线程池 ExecutorService pool = Executors.newFixedThreadPool(3); // 创建Callable任务 MyCallable mc1 = new MyCallable(100); Future<Integer> future = pool.submit(mc1); MyCallable mc2 = new MyCallable(200); Future<Integer> future2 = pool.submit(mc2); // 拿到返回值 Integer ret1 = future.get(); System.out.println("结果1:" + ret1); Integer ret2 = future2.get(); System.out.println("结果2:" + ret2); // 关闭线程池 pool.shutdown(); } } // 子类 package com.zsh.demo10多线程.callable; import java.util.concurrent.Callable; public class MyCallable implements Callable<Integer> { // 求1-n的值 int n; public MyCallable(int n) { this.n = n; } @Override public Integer call() throws Exception { int sum = 0; for (int i = 1; i <= n; i++) { sum += i; } System.out.println(Thread.currentThread().getName() + "执行任务完毕!"); return sum; } }