这是我参与「第四届青训营 」笔记创作活动的的第2天
Java多线程编程| 青训营笔记
线程的创建
方法一:继承Thread类
package com.atguigu.java;
/**
* Created with IntelliJ IDEA.
*多线程的创建,方式一:继承Thread类
* 1、创建一个继承于Thread类的子类
* 2、重写Thread类中的run()方法 -->将此线程要执行的操作声明在run()方法中
* 3、创建子类对象
* 4、通过子类对象调用start()方法
*
* @Author: 谭铭豪
* @Date: 2022/07/11/14:04
* @Description: 例子:遍历100以内的偶数
*/
//1、创建一个继承于Thread类的子类
class MyThread extends Thread{
//2、重写Thread类中的run()方法 -->将此线程要执行的操作声明在run()方法中
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0){
System.out.println( Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3、创建子类对象
MyThread myThread = new MyThread();
//4、通过子类对象调用start()方法:①启动当前线程 ②调用run()方法
myThread.start();
//问题一:不能直接通过run方法创建线程,这样只是普通的调用方法,如果直接调用run,使用的则是主线程
//myThread.run();
//问题二:要启动多个线程应该是多个对象分别调用start,而不是一个对象多次调用start
MyThread myThread1 = new MyThread();
myThread1.start();
}
}
练习:Thread的匿名子类
package com.atguigu.exer;
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/11/14:51
* @Description:创建两个分线程,一个遍历100以内的偶数,一个遍历100以内的基数
*/
class EvenThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class OddThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 方式一:
// EvenThread evenThread = new EvenThread();
// OddThread oddThread = new OddThread();
// evenThread.start();
// oddThread.start();
// 方式二:创建Thread类的匿名子类
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}.start();
}
}
线程的常用方法
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/11/21:12
* @Description:测试Thread类的常用方法
* 1、start():开启一个线程,并调用run方法
* 2、run():开启新线程想要执行的操作
* 3、Thread.currentThread():返回当前运行的线程对象
* 4、getName():获取当前线程对象的线程名
* 5、setName():设置当前线程对象的线程名
* 6、yield():立即释放cpu,但是仍可能在分配cpu时再次获取到cpu继续执行
* 7、join():插队,抢占cpu直至调用join()的线程完成所有操作再继续下一个线程 注意:只有开启了的【执行了start方法的线程才有join的作用】
* 8、stop():过时方法,不推荐使用,立即停止调用线程的运行
* 9、sleep(x):x的单位是毫秒,调用sleep()方法的线程进入阻塞态,直到经过x毫秒再重新进入就绪态,没有其他线程时则是一个类似等待sleep状态
* 10、isAlive():判断调用线程是否仍然存活
*
*/
class ExerThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0){
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
if (i%20==0){
yield();
}
}
}
}
class ExerThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
ExerThread exerThread = new ExerThread();
ExerThread1 exerThread1 = new ExerThread1();
exerThread.setName("线程1");
exerThread1.setName("线程2");
exerThread.start();
for (int i = 0; i < 100; i++) {
System.out.println(i);
if (i==20){
try {
exerThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(exerThread.isAlive());
//exerThread1.start();
}
}
线程的调度
线程的优先级
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
线程调度的具体方式
package com.atguigu.java;
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/11/21:12
* @Description:线程的调度:优先级高的线程只是获取cpu的概率更高。一定要等高优先级的线程执行完才执行低优先级的说法是错误的
* 1、getPriority():获取当前线程的优先级
* 2、setPriority():设置当前线程的优先级
*
*/
class ExerThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class ExerThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
ExerThread exerThread = new ExerThread();
ExerThread1 exerThread1 = new ExerThread1();
System.out.println(exerThread.getPriority());
exerThread.setPriority(Thread.MAX_PRIORITY);
System.out.println(exerThread.getPriority());
exerThread.start();
exerThread1.start();
}
}
方法二:实现Runnable
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/12/12:03
* @Description:创建线程的方式二:实现Runnable接口
* 1、创建实现Runnable接口的实现类
* 2、重写run方法
* 3、创建该类对象
* 4、把该类对象传入Thread类构造器,创建Thread类对象
* 5、调用Thread类对象start方法
*/
//1、创建实现Runnable接口的实现类
class MyThread1 implements Runnable{
// * 2、重写run方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0){
System.out.println( Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
// * 3、创建该类对象
MyThread1 myThread1 = new MyThread1();
// * 4、把该类对象传入Thread类构造器,创建Thread类对象
// * 5、调用Thread类对象start方法
new Thread(myThread1).start();
new Thread(myThread1).start();
}
}
窗口卖票题目
对比下面两个方式,最大的差异就是:是否需要使用静态变量
- 普通变量的数目与对象个数绑定
- 静态变量个数与类个数绑定
总结:由于方式创建的拥有票数的对象不同,来定义是否需要使用静态变量
注意:以下编程方式都会存在线程同步安全问题
方式一:继承方法
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/12/11:41
* @Description:创建三个线程卖100张票,使用继承方法实现
* 可能出现的问题:
* 1、静态变量的使用
* 2、线程安全问题
*/
class Window extends Thread{
private static int ticket=100;
@Override
public void run() {
while(true){
if (ticket>0){
System.out.println(getName()+"卖票,票号为:"+ticket);
ticket--;
}else {
System.out.println("票卖完了");
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window w0 = new Window();
Window w1 = new Window();
Window w2 = new Window();
w0.start();
w1.start();
w2.start();
}
}
方式二:实现方法
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/12/12:14
* @Description:创建三个线程卖100张票,使用实现方法实现
* 可能出现的问题:
* 1、线程安全问题
*/
class Window1 implements Runnable{
private int ticket=100;
@Override
public void run() {
while(true){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
ticket--;
}else {
System.out.println("票卖完了");
break;
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 window1 = new Window1();
Thread thread = new Thread(window1);
Thread thread1 = new Thread(window1);
Thread thread2 = new Thread(window1);
thread.setName("窗口0");
thread1.setName("窗口1");
thread2.setName("窗口2");
thread.start();
thread1.start();
thread2.start();
}
}
两种创建方式的比较
开发中:优先使用实现方式
原因:
- 实现方式没有单继承的局限性
- 实现方式更加适合线程间有共享数据的情况
联系:
public class Thread implements Runnable
相同点:都要重写run方法,将需要执行的操作生命在run方法中
线程的生命周期
线程的安全问题
下面以窗口买票为例
- 重票、错票
- sleep()会使线程经过判断后进入阻塞态,使得错票率更高(判断和售出应该是捆绑操作(原子性))
- 解决方法:当一个线程在操作ticket时,其他线程不可参与
- 在Java中,我们通过同步机制,来解决Java线程安全问题
Java同步机制解决线程安全问题
方式一:同步代码块
- 同步监视器:俗称:锁
- 锁:任何类的对象都可以充当锁
- 多线程保持同步使用的锁必须是同一把锁
同步:
- 解决了线程安全问题
- 同步代码块是单线程的
synchronized (同步监视器){
//需要被同步的代码:操作共享数据的代码
//共享数据:多个线程共同操作的变量
}
以下是经过同步代码块修改后的安全线程
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/12/12:14
* @Description:同步代码块解决Runnable实现问题
*/
class Window1 implements Runnable{
private int ticket=100;
@Override
public void run() {
while(true){
synchronized (Window1.class){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
ticket--;
}else {
System.out.println("票卖完了");
break;
}
}
}
}
}
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/12/11:41
* @Description:同步代码块实现Thread继承多线程安全问题
*
*/
class Window extends Thread{
private static int ticket=100;
@Override
public void run() {
while(true){
synchronized (Window.class){
if (ticket>0){
System.out.println(getName()+"卖票,票号为:"+ticket);
ticket--;
}else {
System.out.println("票卖完了");
break;
}
}
}
}
}
注意:不要把循环放进同步代码块之中,不然一个线程会一直持有锁,直至循环退出
方式二:同步方法
在方法头载入关键字synchronized
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/12/12:14
* @Description:同步方法解决Runnable实现问题
*/
class Window1 implements Runnable{
private int ticket=100;
@Override
public synchronized void run() {//同步监视器:this
while(true){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
ticket--;
}else {
System.out.println("票卖完了");
break;
}
}
}
}
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/12/11:41
* @Description:同步方法实现继承Thread多线程安全问题
*
*/
class Window extends Thread{
private static int ticket=100;
@Override
public void run() {
while(true){
show();
}
}
// private synchronized void show(){//同步监视器:this
private static synchronized void show(){//同步监视器:class
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
ticket--;
}
}
}
同步机制解决单例模式之懒汉式的安全问题
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/12/18:35
* @Description:利用同步机制将单例模式中的懒汉式变为线程安全的
*/
class Bank{
//1、私有化构造器
private Bank(){}
private static Bank instance =null;//2、创建单例引用
public Bank getInstance(){
//注意:这里多设置一个判断条件可以大幅度提高整个多线程的运行速度
//因为这里使用同步机制,是为了阻止new操作时引用多次改变,换言之,就是第一个线程需要new对象,后面的线程直接引用对象即可
if (instance==null){
synchronized (Bank.class){
if (instance==null){
instance=new Bank();
}
}
}
return instance;
}
}
死锁
定义:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁
表现:出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续
解决:
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
死锁的演示
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/13/16:18
* @Description:死锁的演示
*/
public class ThreadTest {
public static void main(String[] args) {
final StringBuffer s1 = new StringBuffer();
final StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized(s1){
s1.append('a');
s2.append('1');
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append('b');
s2.append('2');
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized(s2){
s1.append('c');
s2.append('3');
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append('d');
s2.append('4');
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
方式三:Lock锁
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/12/12:14
* @Description:方式三:Lock锁实现同步
* 1、创建Lock的实例化对象
* 2、try出同步内容,并在内容头上锁【lock()】
* 3、finally中解锁【unlock()】
*/
class Window1 implements Runnable{
private int ticket=100;
private ReentrantLock lock=new ReentrantLock();
@Override
public void run() {//同步监视器:this
while(true){
try{
lock.lock();
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
ticket--;
}else {
System.out.println("票卖完了");
break;
}
}finally {
lock.unlock();
}
}
}
}
练习:存钱题
两人向同一个账户分别每次存1000,存三次(共存入六千元),保证线程安全
import java.util.concurrent.locks.ReentrantLock;
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/14/10:53
* @Description:模拟两个用户各向银行同一账户存储3000,每次存1000
*/
class Bank implements Runnable{
private int money=0;
private ReentrantLock lock=new ReentrantLock(true);//true,就是线程交替得到锁
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try{
lock.lock();
money+=1000;
System.out.println(Thread.currentThread().getName()+"现在余额为:"+money);
}finally {
lock.unlock();
}
}
}
}
public class BankTest {
public static void main(String[] args) {
Bank b1 = new Bank();
Thread t1 = new Thread(b1);
Thread t2 = new Thread(b1);
t1.start();
t2.start();
}
}
线程通信问题
线程通信方法
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/14/11:53
* @Description:模拟线程通信,交替打印1-100
* 1、wait():让当前线程进入阻塞态,并且释放同步锁
* 2、notify():唤醒优先级最高的线程
* 3、notifyALL():唤醒所有线程
*
* 说明:
* 1、以上三个方法必须存在于同步代码块中
* 2、调用者必须是同步监视器
* 3、定义在Object中,所以所有锁可以作为锁并且调用
*
*/
class Number implements Runnable{
private int number=1;
@Override
public void run() {
while (true){
synchronized (this){
this.notify();
if (number<=100){
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
sleep()与wait()的异同
同:都会让线程进入阻塞状态
异:
- 声明位置:sleep声明在Thread类中,wait声明在Object类中
- 调用:sleep是静态方法,随便调用;wait必须在同步代码块内由同步监视器调用
生产者消费者问题
package com.atguigu.java2;
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/14/23:23
* @Description:生产者和消费者的问题
* 1、产品Clerk,0-20
* 2、生产者Producer
* 3、消费者Consumer
*/
class Clerk{
private static int ClerkCount=0;
public synchronized void productClerk(){
if (ClerkCount<20){
ClerkCount++;
System.out.println(Thread.currentThread().getName()+":完成生产,共"+ClerkCount);
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consumeClerk(){
if (ClerkCount>0){
ClerkCount--;
System.out.println(Thread.currentThread().getName()+":完成消费,共"+ClerkCount);
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.productClerk();
}
}
}
class Consumer extends Thread{
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeClerk();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
producer.setName("生产者");
consumer.setName("消费者");
producer.start();
consumer.start();
}
}
线程创建方式三:实现Callable接口
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/15/0:05
* @Description:创建线程的方式三:实现Callable接口
* 步骤:
* 1、创建实现Callable接口实现类
* 2、重写call()方法
* 3、创建Callable接口实现类的类对象
* 4、将3中的Callable接口实现类的类对象作为参数,创建FutureTask类对象
* 5、将4中的FutureTask类对象作为参数创建Thread对象调用start()开启线程
* 6、调用4中FutureTask类对象的get()方法可以获得call()方法的返回值
*
* 实现Callable接口的注意点
* 1、通过FutureTask类的对象调用get()获得了call()的方法
* 2、可以抛出异常
* 3、支持泛型返回值
* 4、借助FutureTask类,获取返回结果
*/
class NumberThread implements Callable {
private int num=0;
@Override
public Object call() throws Exception {
for (int i = 0; i <= 4; i++) {
num+=i;
}
return num;
}
}
public class ThreadNew {
public static void main(String[] args) {
NumberThread numberThread = new NumberThread();
FutureTask future = new FutureTask(numberThread);
Thread thread = new Thread(future);
thread.start();
try {
Object num = future.get();
System.out.println("总和为:"+num);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程创建方式四:使用线程池
/**
* Created with IntelliJ IDEA.
*
* @Author: 谭铭豪
* @Date: 2022/07/15/1:12
* @Description:创建线程方式四:使用线程池
* JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
* ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
* void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行
* Runnable
* <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行
* Callable
* void shutdown() :关闭连接池
* Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
* Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
* Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
* Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
* Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运
* 行命令或者定期地执行。
*/
class NumThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class NumThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1、提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//2、执行指定线程的操作,需要提供Runnable接口的对象或Callable接口的对象
//service.submit(Callable callable);//submit传Callable接口对象
service.execute(new NumThread());//execute传Runnable接口对象
service.execute(new NumThread());
service.execute(new NumThread1());
//3、关闭线程池
service.shutdown();
}
}