文章目录
1-线程创建的三种方法
1-1.继承Thread类(重点)
创建步骤:
- 自定义线程类继承Thread类
- 重写Run()方法,编写线程方法体
- 创建线程对象,调用start()方法
public class ThreadTest {
//下方创建了4个线程,4个线程不会共享100张票,每张票都会被打印4次,请看Runnable解决
public static void main(String[] args) {
//3.创建线程对象,调用start方法,这里创建了4个线程
new TicketWindow().start();
new TicketWindow().start();
new TicketWindow().start();
new TicketWindow().start();
}
}
//1.自定义线程类继承Thread类
class TicketWindow extends Thread{
private int tickets=100;
@Override
//2.重写run方法,编写方法体
public void run(){
while (true){
if (tickets>0){
System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
}
}
}
}
局限性:
- 自定义类要继承Thread,Java只支持单继承,万一student extends person,那么student类就没办法继承Thread,使用不了这种方法了,悲
- 而且下方的代码中100张票是不被共享的,下方创建了4个线程,4个线程不会共享100张票,每张票都会被打印4次
1-2.实现Runnable接口(重点)
Runable接口内部只有一个抽象的run()方法
创建步骤:
- 定义MyRunnable类实现Runable接口
- 实现Run()方法,编写线程执行体
- 创建线程对象,使用new Thread()构造方法传入自定义类,同时参数2可以给个别名
public class RunnableTest {
//只创建了一个自定义类,然后创建了4个线程,此时4个线程此时会共享100张票
public static void main(String[] args) {
//3.创建线程对象,使用new Thread()构造方法传入自定义类,同时参数2可以给个别名
TicketWindow2 tw2=new TicketWindow2();
new Thread(tw2,"窗口1").start();
new Thread(tw2,"窗口2").start();
new Thread(tw2,"窗口3").start();
new Thread(tw2,"窗口4").start();
}
}
//1.定义自定义类实现Runable接口
class TicketWindow2 implements Runnable{
private int tickets=10000;
@Override
//2.实现Run()方法,编写线程执行体
public void run() {
while (true){
if (tickets>0){
System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"张票");
}
}
}
}
1-3.实现Callable接口(了解)
创建步骤:
- 自定义类实现Callable接口
- 重写Call()方法
- 创建目标对象
- 使用futureTask构造方法
- 再使用Thread()构造方法再创建线程
public class CallableTest {
public static void main(String[] args) {
TicketWindow3 tw3=new TicketWindow3();
//futuretask创建多少就是多少个线程
FutureTask<Object> ft1=new FutureTask<Object>(tw3);
FutureTask<Object> ft2=new FutureTask<Object>(tw3);
FutureTask<Object> ft3=new FutureTask<Object>(tw3);
FutureTask<Object> ft4=new FutureTask<Object>(tw3);
new Thread(ft1,"窗口1").start();
new Thread(ft2,"窗口2").start();
new Thread(ft3,"窗口3").start();
new Thread(ft4,"窗口4").start();
//System.out.println(ft1.get());
}
}
class TicketWindow3 implements Callable{
private int tickets=100;
@Override
public Object call() throws Exception {
while (true){
if (tickets>0){
System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"张票");
}
}
}
}
2-Run方法和Strat方法的区别
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pDCDoeFe-1626702069138)(04-线程.assets/image-20210414201823237.png)]
3-后台线程
-
为什么使用
-
上方的例子中,main方法的代码执行完了,方法结束了,线程也会自动结束
-
main线程结束了,但是Java程序(进程)没有结束
-
对Java程序来说只要有一个前台线程在运行,进程就不会结束,如果一个进程只有后台线程运行,这个进程就会结束
-
前台线程与后台线程是相对概念,新创建的线程就是前台线程,一个线程在start方法执行前,setDaemon就会变成后台线程
-
-
使用方法
- 在start()方法之前,使用setDaemon()方法设置一个后台线程即可,setDaemon(true)表示设置为后台线程
public class Example06 {
public static void main(String[] args) {
System.out.println("main线程是后台线程吗?" + Thread.currentThread().isDaemon());
DamonThread dt = new DamonThread();
Thread td = new Thread(dt, "线程/后台线程");
System.out.println("thread线程默认是后台线程吗?" + td.isDaemon());
// 将线程td线程对象设置为后台线程
td.setDaemon(true);
System.out.println("thread线程默认是后台线程吗?" + td.isDaemon());
td.start();
// 模拟主线程main的执行任务
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
}
}
class DamonThread implements Runnable {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + "---在运行");
}
}
}
4-线程的调度,优先级
-
定义:
线程优先级表示获得CPU的执行几率大小
-
使用方法
- 在线程start()方法开启之前使用setPriority()方法传入整数即可,整数取值为1-10,越大几率越大
public class Example07 {
public static void main(String[] args) {
Thread thread1=new Thread(()->{
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"正在输出i:"+i);
}
});
Thread thread2=new Thread(()->{
for (int j=0;j<10;j++){
System.out.println(Thread.currentThread().getName()+"正在输出j:"+j);
}
},"优先级更高的线程");
//设置优先级(也是指概率),通过setPriority()方法设置可以写入数字或者提供的常量
thread1.setPriority(5);
//thread2获得运行的机会更大
thread2.setPriority(10);
thread1.start();
thread2.start();
}
}
5-线程休眠
-
定义
人为控制线程,可以使当前在执行的线程暂停,进入休眠等待状态
-
使用方法:
在start()方法之后,使用sleep()方法传入数字即可,单位为毫秒,如sleep(2000),为休眠2秒
public class Example08 {
public static void main(String[] args) {
Thread thread1=new Thread(()->{
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"正在输出i:"+i);
if (i==2){
try {
Thread.sleep(500);
}catch (Exception e){
}
}
}
});
Thread thread2=new Thread(()->{
for (int j=0;j<10;j++){
System.out.println(Thread.currentThread().getName()+"正在输出j:"+j);
}
},"优先级更高的线程");
thread1.start();
thread2.start();
}
}
6-线程让步
-
定义
与sleep相似,让当前的正在运行的线程暂停,区别在yield()方法不会阻塞线程,而是处于就绪状态(与其他线程回到起跑线),让系统重新调度一次,让步不一定成功,看CPU心情
-
使用方法
yield()方法
public class Example09 {
public static void main(String[] args) {
Thread thread1=new YieldThread("thread1");
Thread thread2=new YieldThread("thread2");
thread1.start();
thread2.start();
}
}
class YieldThread extends Thread{
public YieldThread(String name){
super(name);
}
@Override
public void run(){
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"----"+i);
if (i==2){
//yield方法会让优先级高于自己的先运行
System.out.print("线程让步:");
Thread.yield();
}
}
}
}
/*
结果为:
thread1----0
thread1----1
thread1----2
线程让步:thread2----0
thread2----1
thread2----2
线程让步:thread1----3
thread1----4
thread2----3
thread2----4
分析:
看到线程1在i==2的时候让步了,然后线程2跑了,然后线程2跑到i==2又让步
*/
7-线程插队
-
插入某线程,使其阻塞,然后插入的线程运行完毕后,被插入的才能运行
-
使用在main线程中使用thread1.join()方法的线程,插进当前main线程中,thread1完全执行完毕后main才继续执行
-
使用方法
在start之后,主要使用join()方法
public class Example10 {
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(new EmergencyThread(),"thread1");
thread1.start();
for (int i=1;i<6;i++){
System.out.println(Thread.currentThread().getName()+"输入:"+i);
if (i==2){
//在main线程中调用thread1线程,使得thread1插队运行,main要等到thread1运行完再运行
thread1.join();
}
}
// thread1.start();
}
}
class EmergencyThread implements Runnable{
@Override
public void run(){
for (int i=1;i<6;i++){
System.out.println(Thread.currentThread().getName()+"输入:"+i);
}
}
}
8-多线程同步
限制某个资源(如:100张票)在同一时刻只能被一个线程访问
8.1-线程安全
问题引出:
结果会出现负数,主要是因为sleep,让其他的线程通过判断条件,进入了run方法内,这样的线程是不安全的,我们得想些方法去处理共享资源的代码同时只能有一个线程使用
public class Example11 {
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow();
Thread thread1 = new Thread(ticketWindow, "thread1");
Thread thread2 = new Thread(ticketWindow, "thread2");
Thread thread3 = new Thread(ticketWindow, "thread3");
Thread thread4 = new Thread(ticketWindow, "thread4");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
class TicketWindow implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets > 0) {
try {
Thread.sleep(100);
}catch (Exception e){
}
System.out.println(Thread.currentThread().getName() + "出售第" + tickets-- + "张票");
}
}
}
}
/*结果:出现负数,主要是因为sleep,让其他的线程通过判断条件,进入了run方法内
thread2出售第2张票
thread1出售第2张票
thread3出售第1张票
thread4出售第1张票
thread2出售第0张票
thread1出售第-1张票
*/
8.2-同步代码块
-
作用:
使得共享资源的代码在任何时候只能有一个线程访问
-
使用方法:
- 随便定义一个对象,作为同步代码块的锁
- 将锁对象参数给同步代码块,且将关键代码放入同步代码块中
//定义任意一个对象,用作同步代码块的锁 Object lock=new Object(); //lock参数是一个锁对象,默认锁对象标志位是1,此时线程可以进入运行,标志位变化为0,其他线程无法进入。待之前的线程出来后,标志位变为1,其他线程才可以进入哦 synchronized (lock){ //关键代码存放位置 }
示例代码如下:
public class Example12 {
public static void main(String[] args) {
SaleThread2 saleThread2=new SaleThread2();
new Thread(saleThread2,"thread1").start();
new Thread(saleThread2,"thread2").start();
new Thread(saleThread2,"thread3").start();
new Thread(saleThread2,"thread4").start();
}
}
class SaleThread2 implements Runnable{
private int tickets=100;
//1.定义任意一个对象,用作同步代码块的锁
Object lock=new Object();
@Override
public void run() {
while (true){
//2.锁对象放入,代码放入,nice!
synchronized (lock){
if (tickets>0){
try{
Thread.sleep(100);
}catch (Exception e){
}
System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
}
}
}
}
}
8.3-同步方法
-
定义:
synchronized 返回值类型 方法名(参数 1,…)
主要是使用synchronized修饰的方法,synchronized就是同步代码块使用的那个修饰符啦
-
作用:
使得共享资源的代码在任何时候只能有一个线程访问
synchronized修饰的方法,同时只允许一个线程访问,其他线程来的都会阻塞
-
使用方法:
重写的run()方法里边的,有线程安全需求的方法,使用synchronized修饰就可以了,简单得一批
-
扩展思考:
同步方法也有锁,它的锁就是当前调用该方法的对象,也就是this指向的对象,确保了唯一性
静态方法的锁是该方法所在类的class对象,该对象可以使用"类名.class"取出
public class Example13 {
public static void main(String[] args) {
SaleThread3 saleThread3 = new SaleThread3();
new Thread(saleThread3, "thread1").start();
new Thread(saleThread3, "thread2").start();
new Thread(saleThread3, "thread3").start();
new Thread(saleThread3, "thread4").start();
}
}
class SaleThread3 implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
sale();
}
}
//这里就是核心了synchronized修饰的方法
private synchronized void sale() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售的票为第" + tickets-- + "张");
}
}
}
8.4-死锁问题
线程需要对方的锁,但是都拿不到,死锁就形成了
public class Example16 {
public static void main(String[] args) {
//线程1,flag为true,运行if中的内容
DeadLockThread thread1 = new DeadLockThread(true);
//线程2,flag为false,运行else中的内容
DeadLockThread thread2 = new DeadLockThread(false);
new Thread(thread1, "Chinese").start();
new Thread(thread2, "American").start();
}
}
class DeadLockThread implements Runnable {
//定义两个类作为锁
static Object chopsticks = new Object();
static Object knifeAndFork = new Object();
private boolean flag;
//有参构造方法,给flag赋值
DeadLockThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
while (true) {
//线程1,不断循环,一直拿两个锁对象,运行
synchronized (chopsticks) {
System.out.println(Thread.currentThread().getName() + "---if---chopsticks");
synchronized (knifeAndFork) {
System.out.println(Thread.currentThread().getName() + "---if---knifeAndFork");
}
}
}
} else {
while (true) {
//线程2,受到影响,被线程1拿了锁对象,然后两个线程GG,死锁了
synchronized (knifeAndFork) {
System.out.println(Thread.currentThread().getName() + "---else---knifeAndFork");
synchronized (chopsticks) {
System.out.println(Thread.currentThread().getName() + "---else---chopsticks");
}
}
}
}
}
}
8.5-同步锁
使用方法:
//1.定义一个Lock锁对象
private final Lock lock = new ReentrantLock();
//2.显示的加锁
lock.lock();
//这里就放你要锁住的代码部分
//3.解锁
lock.unlock();
//注意:一般配合try与catch,finally使用
示例:
public class Example14 {
public static void main(String[] args) {
LockThread lockThread=new LockThread();
new Thread(lockThread,"thread1").start();
new Thread(lockThread,"thread2").start();
new Thread(lockThread,"thread3").start();
new Thread(lockThread,"thread4").start();
}
}
class LockThread implements Runnable {
private int tickets = 100;
//定义一个Lock锁对象
private final Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//对代码块进行加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"张票");
} catch (Exception e) {
e.printStackTrace();
}finally {
//执行完代码块后释放锁
lock.unlock();
}
}
}
}
}
9-多线程通信
线程与线程之间的通信问题,比如生产者和消费者相互通信
以下方法一般用作线程通信,且是"锁对象.方法":
- wait()方法:使当前线程放弃同步锁并进入等待,直到其他线程进入此同步锁,并调用notify()方法,或者notifyAll()方法唤醒线程为止。wait()会释放锁,sleep()不会释放锁
- 传入参数,可以指定等待秒数
- 不传参数,线程一直等待,直到其他线程通知
- notify()方法:唤醒此同步锁上等待的第一个调用wait()方法的线程
- notifyAll()方法:唤醒此同步锁上调用的wait()方法的所有线程
问题引入,未使用线程通信:
/**
* 问题就是供求关系不匹配,生产和消费对不上,这时候就需要用线程通信
*/
public class Problem {
public static void main(String[] args) {
//定义商品
List<Object> goods = new ArrayList<>();
long start = System.currentTimeMillis();
//定义线程1,来生产商品
Thread thread1 = new Thread(() -> {
int num = 0;
while (System.currentTimeMillis()-start<=100){
goods.add("商品" + ++num);
System.out.println("生产商品"+num);
}
},"生产者");
//定义线程2,来消费商品
Thread thread2 = new Thread(() -> {
int num = 0;
while (System.currentTimeMillis()-start<=100){
goods.remove("商品"+ ++num);
System.out.println("消费商品"+num);
}
},"生产者");
// 定义了两个线程,分别是消费者和生产者,同时运行100毫秒,看看供求关系
thread1.start();
thread2.start();
}
}
//结果:生产和消费不能够统一,有时候才生产1000个东西,消费都达到1200了,显然离谱,虚空来的200个
现在使用线程的通信,解决问题:
public class Solve {
public static void main(String[] args) {
List<Object> goods = new ArrayList<>();
long start = System.currentTimeMillis();
//线程1,来生产商品
Thread thread1 = new Thread(() -> {
int num = 0;
while (System.currentTimeMillis() - start <= 100) {
//使用goods作为锁对象
synchronized (goods) {
//有商品就让生产者进入等待状态
if (goods.size() > 0) {
try {
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
goods.add("商品" + ++num);
System.out.println("生产商品" + num);
}
}
}
}, "生产者");
Thread thread2 = new Thread(() -> {
int num = 0;
while (System.currentTimeMillis() - start <= 100) {
//使用goods作为锁对象
synchronized (goods) {
// 商品不足就唤醒生产者生产
if (goods.size() <= 0) {
goods.notify();
} else {
goods.remove("商品" + ++num);
System.out.println("消费商品" + num);
}
}
}
}, "生产者");
// 定义了两个线程,分别是消费者和生产者,同时运行100毫秒,看看供求关系
thread1.start();
thread2.start();
}
}