参考链接:黑马全面剖析Java多线程编程
一,启动多线程的方法
1,方法一
public class MyThread extends Thread{
@Override
public void run(){
//书写线程要执行的代码
for(int i=0;i<100;i++){
System.out.println(getName()+"hello world");
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第一种启动方式
* 1,自己定义一个类继承Thread
* 2,重写run方法
* 3,创建子类的对象,并启动线程
* */
MyThread t1=new MyThread();
MyThread t2=new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
2,方法二
public class MyRun implements Runnable {
@Override
public void run(){
//书写线程要执行的代码
for(int i=0;i<100;i++){
//获取当前线程的对象
/*Thread t=Thread.currentThread();
System.out.println(getName()+"hello world");*/
System.out.println(Thread.currentThread().getName()+"hello world");
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第二种启动方式
* 1,自己定义一个Runnable接口
* 2,重写run方法
* 3,创建子类的对象
* 4,创建一个Thread类的对象,并开启线程
* */
//创建MyRun的对象
//表示多线程要执行的任务
MyRun myRun=new MyRun();
//创建线程对象
Thread t1=new Thread(myRun);
Thread t2=new Thread(myRun);
//给线程设置名字
t1.setName("线程1");
t2.setName("线程2");
//开启线程
t1.start();
t2.start();
}
}
3,方法三
public class MyCallable implements Callable<Integer> {
@Override
public Integer call(){
//求1到100之间的数值和
int sum=0;
for(int i=1;i<=100;i++){
sum+=i;
}
return sum;
}
}
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
* 多线程的第三种启动方式
* 1,自己定义一个Runnable接口
* 2,重写run方法
* 3,创建子类的对象
* 4,创建一个Thread类的对象,并开启线程
* */
//创建MyCallable的对象(表示多线程要执行的任务)
MyCallable myCallable=new MyCallable();
//创建FutureTask的对象(作用管理多线程运行的结果)
FutureTask<Integer> futureTask=new FutureTask<>(myCallable);
//创建线程的对象
Thread t1=new Thread(futureTask);
//启动线程
t1.start();
//获取多线程的运行结果
Integer result=futureTask.get();
System.out.println(result);
}
}
二,线程常见的成员方法
1,线程优先级
在Java中,线程优先级是一个用于控制线程调度的概念。Java的线程调度器会根据线程的优先级来确定哪个线程应该在执行时获得更多的CPU时间片。线程的优先级是一个整数,通常范围从1到10,其中1表示最低优先级,10表示最高优先级。默认情况下,线程的优先级是5。
注意:
- 线程优先级只是给线程调度器一个提示,它并不能确保线程一定会按照优先级顺序执行。线程调度是由操作系统和Java虚拟机决定的,因此在不同的操作系统和虚拟机上可能表现不同。
- 高优先级的线程并不一定比低优先级的线程更快完成任务,它只是有更高的几率获得CPU时间片。
- 过度依赖线程优先级可能导致不可移植性和可维护性问题,因此在实际开发中应该慎重使用线程优先级,并考虑其他更可靠的线程调度方法,如使用
Executor
框架。
2,守护线程
在Java中,守护线程(Daemon Thread)是一种特殊类型的线程,它的存在不会阻止Java虚拟机的退出。与常规线程(用户线程)不同,当所有的用户线程结束执行后,Java虚拟机会自动退出,而不会等待守护线程执行完毕。
注意:
-
守护线程通常用于在后台提供服务或执行任务,例如垃圾回收器(Garbage Collector)、定时任务调度等。它们不应该用于执行需要确保完整性的重要任务,因为它们可能会在任何时候被中断。
-
守护线程的生命周期依赖于用户线程的存在。如果所有的用户线程都结束了,守护线程会被强制终止,即使它们没有执行完任务。
-
守护线程不能用于访问关键资源,因为它们可能会在任何时候被终止,这可能导致资源的不完整性。
-
如果不明确设置线程为守护线程,默认情况下线程是用户线程。
3,出让线程
在Java中,可以通过使用yield()
方法来提示当前线程愿意出让CPU时间片给其他线程。yield()
方法是Thread
类的一个成员方法,它的作用是让当前线程主动放弃一次CPU执行的机会,将CPU时间片让给其他具有相同或更高优先级的线程。
注意:
-
yield()
方法的调用不会保证当前线程一定会让出CPU时间片,它只是一个提示。实际是否让出CPU取决于线程调度器。 -
在某些情况下,过度使用
yield()
可能会导致性能问题,因为频繁的线程切换会带来一些开销。因此,应该谨慎使用yield()
,仅在确实需要时才使用。 -
yield()
方法不会释放锁,因此如果一个线程在同步块内调用了yield()
,它并不会释放锁,其他线程依然无法获得锁。
4,插入线程
在Java中,你可以通过创建并启动新的线程来实现线程的插入(插入式多线程)。线程的插入意味着在程序运行过程中,在某个特定点创建新线程并让其执行一段代码。
注意:
-
可以在任何合适的地方创建并启动新线程,例如,在某个条件满足时、在某个事件发生时或在特定代码块中。
-
新线程的执行顺序取决于线程调度器,因此它可能会和主线程交替执行。
-
主线程可以使用
join()
方法等待新线程执行完毕,确保程序按照预期的顺序执行。
三,线程的生命周期
四,同步代码
1,同步代码块
int ticket=0;
@Override
public void run(){
//1,循环
while (true){
//2,同步代码块(同步方法)
synchronized (MyThread.class){
//3,判断共享数据是否到了末尾,如果到了末尾
if (ticket==100)
break;
//4,判断共享数据是否到了末尾,如果没到末尾
else{
ticket++;
System.out.println(Thread.currentThread().getName()+"在卖第"+ticket+"张票!");
}
}
}
}
2,同步方法
int ticket = 0;
@Override
public void run() {
//1,循环
while (true) {
//2,同步代码块(同步方法)
if (method()) break;
}
}
private synchronized boolean method() {
//3,判断共享数据是否到了末尾,如果到了末尾
if (ticket == 100)
return true;
//4,判断共享数据是否到了末尾,如果没到末尾
else {
ticket++;
System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!");
}
return false;
}
五,Lock锁
1,使用
static int ticket = 0;
static Lock lock=new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try{
if (ticket==100){
break;
}else {
Thread.sleep(100);
ticket++;
System.out.println(getName()+"在卖第"+ticket+"张票!");
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();//最后的,一定会执行的
}
}
}
2,死锁
不能让两层锁嵌套起来,不然会形成死锁。
static Object objA = new Object();
static Object objB = new Object();
@Override
public void run() {
while (true) {
if ("线程A".equals(getName())) {
synchronized (objA) {
System.out.println("线程A拿到了A锁,准备拿B锁");
synchronized (objB) {
System.out.println("线程A拿到了B锁,顺利执行完一轮");
}
}
} else if ("线程B".equals(getName())) {
synchronized (objA) {
System.out.println("线程B拿到了B锁,准备拿A锁");
synchronized (objB) {
System.out.println("线程B拿到了A锁,顺利执行完一轮");
}
}
}
}
}
六,等待唤醒机制
生产消费者模式
1,简单实现
桌子类
public class Desk {
//作用:控制消费者和生产者的执行
//是否有面条 0:没有面条 1:有面条
public static int foodFlag=0;
//总个数
public static int count=10;
//锁对象
public static Object lock =new Object();
}
消费者类
public class Foodie extends Thread {
@Override
public void run() {
/*
1,循环
2,同步代码块
3,判断共享数据是否到了末尾(到了末尾)
4,判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
*/
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;
}
}
}
}
}
}
生产者类
public 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();
}
}
}
}
}
}
测试类
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求:完成等待唤醒机制的代码
* 实现线程轮流交替执行的效果
* */
Cook cook=new Cook();
Foodie foodie=new Foodie();
cook.setName("厨师");
foodie.setName("吃货");
//开启线程
cook.start();
foodie.start();
}
}
2,阻塞队列优化
利用阻塞队列实现等待唤醒机制
生产者类
public class Cook extends Thread{
ArrayBlockingQueue<String>queue;
public Cook(ArrayBlockingQueue<String> queue){
this.queue=queue;
}
@Override
public void run(){
while (true){
//不断把面条放入阻塞队列
try {
queue.put("面条");
System.out.println("[厨师]放了一碗面条");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
消费者类
public class Foodie extends Thread {
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> 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 ThreadDemo {
public static void main(String[] args) {
/*
* 需求:利用阻塞队列实现生产消费者模式
* 细节:生产者和消费者必须使用同一个阻塞队列
* */
//1,创建阻塞队列的对象
ArrayBlockingQueue<String>queue=new ArrayBlockingQueue<>(1);
//2,创建线程的对象,并把阻塞队列传递出去
Cook cook=new Cook(queue);
Foodie foodie=new Foodie(queue);
//3,开启线程
cook.start();
foodie.start();
}
}
七,线程的状态
八,综合练习
1,练习1
基本的多线程创建和启动
编写一个Java程序,创建两个线程,一个线程打印奇数,另一个线程打印偶数,交替打印它们,直到1到10的所有数字都被打印完毕。
public class OddEvenPrinter {
public static void main(String[] args) {
Object lock = new Object();
Thread oddThread = new Thread(new OddPrinter(lock));
Thread evenThread = new Thread(new EvenPrinter(lock));
oddThread.start();
evenThread.start();
}
}
class OddPrinter implements Runnable {
private Object lock;
public OddPrinter(Object lock) {
this.lock = lock;
}
public void run() {
for (int i = 1; i <= 10; i += 2) {
synchronized (lock) {
System.out.println("Odd: " + i);
lock.notify();
try {
if (i < 9) {
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class EvenPrinter implements Runnable {
private Object lock;
public EvenPrinter(Object lock) {
this.lock = lock;
}
public void run() {
for (int i = 2; i <= 10; i += 2) {
synchronized (lock) {
System.out.println("Even: " + i);
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
2,练习2
线程安全与同步
编写一个Java程序,模拟多个线程同时访问一个共享的计数器,确保计数器的操作是线程安全的。
public class CounterExample {
public static void main(String[] args) {
Counter counter = new Counter();
int threadCount = 5;
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(new CounterTask(counter));
threads[i].start();
}
for (int i = 0; i < threadCount; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Final Count: " + counter.getCount());
}
}
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
class CounterTask implements Runnable {
private Counter counter;
public CounterTask(Counter counter) {
this.counter = counter;
}
public void run() {
for (int i = 0; i < 10000; i++) {
counter.increment();
}
}
}
九,线程池
1,创建线程池
public class ThreadDemo {
public static void main(String[] args) {
/*
public static ExecutorServer newCachedThreadPool()//创建没有上限的线程池
public static ExecutorServer newFixedThreadPool(int nThreads)//创建有上限的线程池
*/
//1,获取线程池对象
ExecutorService pool= Executors.newFixedThreadPool(3);
//2,提交任务
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//3,销毁线程池
pool.shutdown();
}
}
2,自定义线程池
public class MyThreadPool {
public static void main(String[] args) {
/*
ThreadPoolExecutor threadPoolExecutor =new ThreadPoolExecutor
(核心线程数量,最大线程数,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略)
参数一:核心线程数量 不能小于0
参数二:最大线程数 不能小于等于0,最大数量>=核心线程数量
参数三:空闲线程最大存活时间 不能小于0
参数四:单位时间 用TimeUnit指定
参数五:任务队列 不能为null
参数六:创建线程工厂 不能为null
参数七:任务的拒绝策略 不能为null
*/
ThreadPoolExecutor pool =new ThreadPoolExecutor(
3,//核心线程数量
6,//最大线程数量
60,//空闲线程最大存活时间
TimeUnit.SECONDS,//单位时间
new ArrayBlockingQueue<>(3),//任务队列
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);
}
}
3,练习3
线程池和任务调度
使用Java的Executor
框架创建一个线程池,提交一些任务,然后等待它们完成。
public class OddEvenPrinter {
public static void main(String[] args) {
Object lock = new Object();
Thread oddThread = new Thread(new OddPrinter(lock));
Thread evenThread = new Thread(new EvenPrinter(lock));
oddThread.start();
evenThread.start();
}
}
class OddPrinter implements Runnable {
private Object lock;
public OddPrinter(Object lock) {
this.lock = lock;
}
public void run() {
for (int i = 1; i <= 10; i += 2) {
synchronized (lock) {
System.out.println("Odd: " + i);
lock.notify();
try {
if (i < 9) {
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class EvenPrinter implements Runnable {
private Object lock;
public EvenPrinter(Object lock) {
this.lock = lock;
}
public void run() {
for (int i = 2; i <= 10; i += 2) {
synchronized (lock) {
System.out.println("Even: " + i);
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}